-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathclasses.py
More file actions
365 lines (298 loc) · 12.7 KB
/
classes.py
File metadata and controls
365 lines (298 loc) · 12.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
from collections import UserDict
from constants import RED, GRAY, CYAN, MAGENTA, RESET, LEN_OF_NAME_FIELD
from datetime import datetime
import os.path
import pickle
import re
class PhoneError(Exception):
...
class EmailError(Exception):
...
class BDayError(Exception):
...
class NoContactError(Exception):
...
class Field:
def __init__(self, value) -> None:
self.__value = None
self.value = value
@property
def value(self):
return self.__value
@value.setter
def value(self, new_value):
self.__value = new_value
def __str__(self) -> str:
return str(self.value)
def __repr__(self) -> str:
return str(self)
class Name(Field):
def __init__(self, value: str) -> None:
super().__init__(str(value).title())
class Phone(Field):
def __eq__(self, __value: object) -> bool:
return self.value == __value.value
@property
def value(self):
return self.__value
@value.setter
def value(self, phone: str):
new_phone = str(phone).strip()
for char in "+( )-.":
new_phone = new_phone.replace(char, "")
if len(new_phone) >= 9 and new_phone.isdigit():
new_phone = "+380" + new_phone[-9:]
else:
raise PhoneError(f"{RED}{phone} - incorrect phone number{RESET}")
self.__value = new_phone
class Email(Field):
def __eq__(self, __value: object) -> bool:
return self.value == __value.value
def find_all_emails(text):
result = re.findall(r"[A-z.]+\w+@[A-z]+\.[A-z]{2,}", text)
return result
@property
def value(self):
return self.__value
@value.setter
def value(self, email: str):
new_email = str(email).strip()
if re.findall(r"[A-z.]+\w+@[A-z]+\.[A-z]{2,}", new_email):
self.__value = new_email
else:
raise EmailError(
f"{RED}{new_email} - invalid email, the email must contains only letters, digits, @ and .{RESET}"
)
def __str__(self) -> str:
return f"{self.value}" if self.value else ""
class BirthDay(Field):
@property
def value(self):
return self.__value
@value.setter
def value(self, value):
if isinstance(value, datetime):
self.__value = value
else:
try:
self.__value = datetime.strptime(str(value), "%Y-%m-%d").date()
except ValueError:
self.__value = datetime.strptime(str(value), "%d-%m-%Y").date()
else:
raise BDayError(f"{RED}{self.__value} - incorrect date{RESET}")
def __str__(self) -> str:
return str(self.value)
class Address(Field):
def __init__(self, value: str) -> None:
super().__init__(str(value).title())
def __str__(self) -> str:
return f"{GRAY}address: {RESET}{self.value}" if self.value else ""
class Record:
def __init__(
self,
name: Name,
phone: Phone | None = None,
birthday: BirthDay | None = None,
email: Email | None = None,
address: Address | None = None,
) -> None:
self.name = Name(name)
self.phones = [Phone(phone)] if phone else []
self.emails = [Email(email)] if email else []
self.birthday = BirthDay(birthday) if birthday else None
self.address = Address(address) if address else None
def add_birthday(self, birthday: BirthDay):
self.birthday = BirthDay(birthday)
return f"the date of birth for contact {self.name} is set to {self.datetime_to_str(self.birthday)} \n\t{self}"
def add_address(self, address: Address):
self.address = Address(address)
return (
f"the address for contact {self.name} is set to {self.address} \n\t{self}"
)
def add_phone(self, phone: Phone) -> str:
phone = Phone(phone)
if phone in self.phones:
return f"{RED}number {phone} is already present in {self.name}'s contact list {RESET} \n\t{self}"
self.phones.append(phone)
return f"phone number {phone} has been added to {self.name}'s contact list \n\t{self}"
def add_email(self, email: Email) -> str:
email = Email(email)
if email in self.emails:
return f"{RED}email {email} is already present in {self.name}'s contact list {RESET} \n\t{self}"
self.emails.append(email)
return f"email {email} has been added to {self.name}'s contact list \n\t{self}"
def datetime_to_str(self, date):
date_to_str = str(date)
return (
date_to_str
if all([date_to_str[2] == "-", date_to_str[5] == "-"])
else date_to_str[8:] + "-" + date_to_str[5:7] + "-" + date_to_str[:4]
)
def days_to_birthday(self, birthday: BirthDay):
today_date = datetime.today().date()
try:
birth_date = datetime.strptime(str(birthday), "%Y-%m-%d").date()
except ValueError:
birth_date = datetime.strptime(str(birthday), "%d-%m-%Y").date()
bd_next = datetime(
day=birth_date.day, month=birth_date.month, year=today_date.year
)
age = today_date.year - birth_date.year
if today_date > bd_next.date():
bd_next = datetime(
day=birth_date.day, month=birth_date.month, year=today_date.year + 1
)
age += 1
days_until = (bd_next.date() - today_date).days
return days_until, age
def edit_phone(self, old_phone: Phone, new_phone: Phone) -> str:
old_phone = Phone(old_phone)
new_phone = Phone(new_phone)
if old_phone == new_phone:
return f"{RED}you are trying to replace the phone number {old_phone} with the same one {new_phone}{RESET} \n\t{self}"
if old_phone in self.phones:
self.phones[self.phones.index(old_phone)] = new_phone
return f"phone number {old_phone} has been successfully changed to {new_phone} for contact {self.name} \n\t{self}"
return f"{RED}phone number {old_phone} is not among the contact numbers of {self.name}{RESET} \n\t{self}"
def edit_email(self, old_email: Email, new_email: Email) -> str:
old_email = Email(old_email)
new_email = Email(new_email)
if old_email == new_email:
return f"{RED}you are trying to replace the email {old_email} with the same one {new_email}{RESET} \n\t{self}"
if old_email in self.emails:
self.emails[self.emails.index(old_email)] = new_email
return f"email {old_email} has been successfully changed to {new_email} for contact {self.name} \n\t{self}"
return f"{RED}email {old_email} is not among the contact e-mails of {self.name}{RESET} \n\t{self}"
def find_phone(self, phone: Phone):
phone = Phone(phone)
for ph in self.phones:
if ph == phone:
return f"phone number {phone} found among {self.name}'s contact numbers"
return f"{RED}phone number {phone} not found {RESET}"
def find_email(self, email: Email):
email = Email(email)
for em in self.emails:
if em == email:
return f"email {email} found among {self.name}'s contact numbers"
return f"{RED}email {email} not found {RESET}"
def remove_phone(self, phone: Phone):
phone = Phone(phone)
if phone not in self.phones:
return f"{RED}phone number {phone} is not among the contact numbers of {self.name} {RESET}\n\t{self}"
self.phones.remove(phone)
return f"phone number {phone} has been removed from {self.name}'s contact list \n\t{self}"
def remove_email(self, email: Email):
email = Email(email)
if email not in self.emails:
return f"{RED}email {email} is not among the contact numbers of {self.name} {RESET}\n\t{self}"
self.emails.remove(email)
return (
f"email {email} has been removed from {self.name}'s contact list \n\t{self}"
)
def remove_address(self):
self.address = None
return f"address of {self.name} has been removed \n\t{self}"
def seek_phone(self, phone: Phone):
for p in self.phones:
ph = p.value[:]
if str(phone) in str(ph):
return True
else:
return False
def seek_email(self, email: Email):
for e in self.emails:
em = e.value[:]
if str(email) in str(em):
return True
else:
return False
def __str__(self) -> str:
blanks = (
GRAY + "." * (LEN_OF_NAME_FIELD - len(str(self.name))) + " " + RESET
)
name_str = f"{self.name} {blanks}: "
phone_str = f"{', '.join(str(p) for p in self.phones)}"
if phone_str != "": # так, це милиця - не чіпати!
phone_str += " "
if self.birthday:
days_to_bd = int(self.days_to_birthday(self.birthday)[0])
data_bd_str = self.datetime_to_str(self.birthday)
years_bd = self.days_to_birthday(self.birthday)[1]
if days_to_bd == 0:
bd_str = f"{MAGENTA}birthday: {RESET}{data_bd_str} {MAGENTA}(today is {years_bd}th birthday){RESET}"
else:
color_bd = (
CYAN if (self.days_to_birthday(self.birthday)[0] <= 7) else GRAY
)
bd_str = f"{color_bd}birthday: {RESET}{data_bd_str} {color_bd}({days_to_bd} days until the {years_bd}th birthday){RESET}"
else:
bd_str = ""
emails_str = (
f"{GRAY}e-mail{'s' if len(self.emails) > 1 else ''}: {RESET}"
+ ", ".join(str(e) for e in self.emails)
+ " "
if self.emails
else ""
)
address_str = str(self.address) if self.address else ""
new_line = (
"\n" + (" " * (LEN_OF_NAME_FIELD + 4))
if all([phone_str.strip() + bd_str != "", emails_str + address_str != ""])
else ""
)
return name_str + phone_str + bd_str + new_line + emails_str + address_str
def __repr__(self) -> str:
return str(self)
class AddressBook(UserDict):
def add_record(self, record: Record):
self.data[record.name.value] = record
return f"contact {record.name} has been successfully added \n\t{record}"
def change_name(self, name: Name, new_name: Name):
name = Name(name)
for key in self.data:
if str(key) == str(name):
rec: Record = self.get(key)
new_record = Record(Name(new_name))
new_record.birthday = rec.birthday
new_record.phones = rec.phones
new_record.emails = rec.emails
new_record.address = rec.address
self.add_record(new_record)
self.delete_record(name)
return f"the name of the contact {Name(name)} has been changed to {Name(new_name)} \n\t{new_record}"
def delete_record(self, name: Name):
# if name in self.data:
# del self.data[name]
name = Name(name)
for key, item in self.data.items():
if str(key) == str(name):
del self.data[key]
return f"contact {name} has been successfully deleted"
return f"{RED}contact {name} not found{RESET}"
def find_name(self, name: Name):
# self.data.get(name, None)
name = Name(name)
for key, item in self.data.items():
if str(key) == str(name):
return f"contact {name} found \n\t{item}"
return f"{RED}contact {name} not found{RESET}"
def iterator(self, n=None):
counter = 0
while counter < len(self):
yield list(self.values())[counter : counter + n]
counter += n
def read_contacts_from_file(self, fn):
if os.path.exists(fn):
with open(fn, "rb") as fh:
self = pickle.load(fh)
self.data = dict(sorted(self.items()))
print(f"{GRAY}the contact book has been successfully restored{RESET}")
return self
def write_contacts_to_file(self, fn):
with open(fn, "wb") as fh:
pickle.dump(self, fh)
return f"{GRAY}the contact book has been saved successfully{RESET}"
def __getitem__(self, key: str) -> Record:
return self.data[key]
def __str__(self):
return "\n".join([str(r) for r in self.data.values()])