Skip to content

Commit 1e6cad4

Browse files
Merge pull request #4 from BudjakovDmitry/homework_03
Homework 03
2 parents 84f032f + fc98c77 commit 1e6cad4

20 files changed

Lines changed: 1983 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,5 @@ dmypy.json
139139

140140
# Cython debug symbols
141141
cython_debug/
142+
143+
*.json

homework_03/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
FROM python:3.9-buster

homework_03/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Телефонный справочник
2+
3+
## Запуск
4+
5+
```shell
6+
cd homework_03
7+
python main.py
8+
```
9+
10+
## Доступные команды
11+
12+
* `help` - получение справки;
13+
* `all` - посмотреть список контактов;
14+
* `add` - добавить контакт;
15+
* `find` - поиск в контактах;
16+
* `edit` - редактировать контакт;
17+
* `delete` - удалить контакт;
18+
* `save` - сохранить контакты в файл;
19+
* `show_storage` - показать контакты в "сыром" виде, т.е. как они хранятся в файле;
20+
* `exit` - выход.
21+
22+
## Запуск тестов
23+
24+
```shell
25+
cd homework_03
26+
python -m unittest
27+
28+
# вариант с расширенным выводом
29+
python -m unittest -v
30+
```

homework_03/__init__.py

Whitespace-only changes.

homework_03/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from src.controller import PhonebookController
2+
3+
4+
if __name__ == '__main__':
5+
phonebook = PhonebookController()
6+
phonebook.run()

homework_03/src/controller.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
"""Phonebook controller"""
2+
3+
from src.model import Contact, ContactNotFound, PhonebookModel
4+
from src.view import Choices, CLEAR_SYMBOL, Commands, ErrorView, InputView, OutputView
5+
6+
7+
class FieldRequired(Exception):
8+
"""Raises if required field does not pass."""
9+
def __init__(self, field_name: str):
10+
self.field_name = field_name
11+
12+
def __str__(self):
13+
return f"Field {self.field_name} is required"
14+
15+
16+
class CommandNotFound(Exception):
17+
"""Raises if there is unknown command."""
18+
def __init__(self, cmd: str):
19+
self.cmd = cmd
20+
21+
def __str__(self):
22+
return f"Command {self.cmd} not found"
23+
24+
25+
class PhonebookController:
26+
"""
27+
PhonebookController acts as intermediary between views and models.
28+
It handles user input and updates the models accordingly.
29+
"""
30+
31+
def __init__(self):
32+
self.phonebook = PhonebookModel()
33+
34+
@staticmethod
35+
def _get_required_field(field: str) -> str:
36+
"""
37+
Get required field from view. If value is empty raises FileRequired exception.
38+
:param field: field name
39+
"""
40+
value = InputView.ask_required_field(field)
41+
if not value:
42+
raise FieldRequired(field)
43+
return value
44+
45+
@staticmethod
46+
def _get_command_or_raise() -> str:
47+
"""Waits for user's command and return it."""
48+
command = InputView.ask_command()
49+
if command and command not in Commands.values():
50+
raise CommandNotFound(command)
51+
return command
52+
53+
@classmethod
54+
def _get_required_integer_field(cls, field: str) -> int:
55+
"""
56+
Get required field from view, convert to int if it is possible. If not, raises
57+
ValueError.
58+
:param field: field name
59+
"""
60+
value = cls._get_required_field(field)
61+
if not value.isdigit():
62+
raise ValueError(f"Incorrect value for contact ID. It must be an integer")
63+
return int(value)
64+
65+
def _add_contact(self):
66+
"""Add contact to phonebook."""
67+
contact: Contact = self.phonebook.add_contact(
68+
name=self._get_required_field("Name"),
69+
phone=self._get_required_field("Phone"),
70+
comment=InputView.ask_optional_field("Comment"),
71+
)
72+
OutputView.new_contact(
73+
contact_id=contact.id,
74+
name=contact.name,
75+
phone=contact.phone,
76+
comment=contact.comment,
77+
)
78+
79+
def _print_contacts(self):
80+
"""Print all contacts."""
81+
for contact in self.phonebook.contacts():
82+
OutputView.contact_info(
83+
contact_id=contact.id,
84+
name=contact.name,
85+
phone=contact.phone,
86+
comment=contact.comment,
87+
)
88+
89+
def _find_contact(self):
90+
"""Find contact via its ID."""
91+
search_string = self._get_required_field("Search")
92+
contacts = self.phonebook.find_contacts(search_string)
93+
for contact in contacts:
94+
OutputView.contact_info(
95+
contact_id=contact.id,
96+
name=contact.name,
97+
phone=contact.phone,
98+
comment=contact.comment,
99+
)
100+
101+
def _edit_contact(self) -> Contact:
102+
"""Update contact info."""
103+
contact_id = self._get_required_integer_field("ID")
104+
contact: Contact = self.phonebook.get(contact_id)
105+
106+
err_msg = "Can't clear field {field}. It can not be empty."
107+
new_name = InputView.update_field("name")
108+
if new_name == CLEAR_SYMBOL:
109+
raise ValueError(err_msg.format(field="name"))
110+
contact.update_name(new_name)
111+
112+
new_phone = InputView.update_field("phone")
113+
if new_phone == CLEAR_SYMBOL:
114+
raise ValueError(err_msg.format(field="phone"))
115+
contact.update_phone(new_phone)
116+
117+
comment = InputView.update_field("comment")
118+
if comment == CLEAR_SYMBOL:
119+
contact.clear_comment()
120+
else:
121+
contact.update_comment(comment)
122+
123+
return contact
124+
125+
def _delete_contact(self):
126+
"""Delete contact."""
127+
contact_id = self._get_required_integer_field("Contact ID")
128+
self.phonebook.delete_contact(contact_id)
129+
130+
def _show_storage(self):
131+
"""Print raw contacts data from storage."""
132+
data = self.phonebook.raw_storage()
133+
OutputView.print_raw(data)
134+
135+
136+
def run(self):
137+
"""Main program cycle."""
138+
command = None
139+
while command != Commands.EXIT:
140+
try:
141+
command = self._get_command_or_raise()
142+
except CommandNotFound as err:
143+
ErrorView.unknown_command(err.cmd)
144+
continue
145+
146+
if command == Commands.ADD:
147+
try:
148+
self._add_contact()
149+
except FieldRequired as err:
150+
ErrorView.required_field(err.field_name)
151+
finally:
152+
continue
153+
154+
elif command == Commands.FIND_CONTACT:
155+
try:
156+
self._find_contact()
157+
except FieldRequired as err:
158+
ErrorView.required_field(err.field_name)
159+
finally:
160+
continue
161+
162+
elif command == Commands.EDIT_CONTACT:
163+
try:
164+
contact = self._edit_contact()
165+
except FieldRequired as err:
166+
ErrorView.required_field(err.field_name)
167+
except ValueError as err:
168+
ErrorView.wrong_value(err.args[0])
169+
except ContactNotFound:
170+
ErrorView.not_found("contact")
171+
else:
172+
OutputView.contact_info(
173+
contact_id=contact.id_,
174+
name=contact.name,
175+
phone=contact.phone,
176+
comment=contact.comment,
177+
)
178+
finally:
179+
continue
180+
181+
elif command == Commands.DELETE_CONTACT:
182+
try:
183+
self._delete_contact()
184+
except FieldRequired as err:
185+
ErrorView.required_field(err.field_name)
186+
except ValueError as err:
187+
ErrorView.wrong_value(err.args[0])
188+
finally:
189+
continue
190+
191+
elif command == Commands.SHOW_ALL:
192+
self._print_contacts()
193+
elif command == Commands.SHOW_STORAGE:
194+
self._show_storage()
195+
elif command == Commands.SAVE:
196+
self.phonebook.save()
197+
elif command == Commands.HELP:
198+
OutputView.help()
199+
200+
if self.phonebook.has_unsaved_changes():
201+
save = InputView.ask_to_save_changes() or Choices.DEFAULT
202+
if save == Choices.YES:
203+
self.phonebook.save()

0 commit comments

Comments
 (0)