-
Notifications
You must be signed in to change notification settings - Fork 18
rabotaet #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
rabotaet #12
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,13 @@ | ||
| GOOD_LOG_FILE = "good_log.txt" | ||
| ERROR_LOG_FILE = "error_log.txt" | ||
| CSV_PATH = "csv.csv" | ||
| GOOD_LOG_FILE = "info.log" | ||
| ERROR_LOG_FILE = "error.log" | ||
| CSV_PATH = "patient.csv" | ||
| PHONE_FORMAT = "79160000000" # Здесь запишите телефон +7-916-000-00-00 в том формате, в котором вы храните телефоны | ||
|
|
||
| PASSPORT_TYPE = "паспорт" # тип документа, когда он паспорт | ||
| PASSPORT_FORMAT = "0000 000000" # Здесь запишите номер парспорта 0000 000000 в том формате, в котором вы его храните | ||
| PASSPORT_TYPE = "Паспорт" # тип документа, когда он паспорт | ||
| PASSPORT_FORMAT = "0000000000" # Здесь запишите номер парспорта 0000 000000 в том формате, в котором вы его храните | ||
|
|
||
| INTERNATIONAL_PASSPORT_TYPE = "заграничный паспорт" # тип документа, если это загран | ||
| INTERNATIONAL_PASSPORT_FORMAT = "00 0000000" # формат хранения заграна для номера 00 0000000 | ||
| INTERNATIONAL_PASSPORT_TYPE = "Заграничный паспорт" # тип документа, если это загран | ||
| INTERNATIONAL_PASSPORT_FORMAT = "000000000" # формат хранения заграна для номера 00 0000000 | ||
|
|
||
| DRIVER_LICENSE_TYPE = "водительское удостоверение" # тип документа, если это водительское удостоверение | ||
| DRIVER_LICENSE_FORMAT = "00 00 000000" # формат хранения номера ВУ | ||
| DRIVER_LICENSE_TYPE = "Водительские права" # тип документа, если это водительское удостоверение | ||
| DRIVER_LICENSE_FORMAT = "0000000000" # формат хранения номера ВУ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0223123456 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 | ||
| Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа | ||
| vasya;pupkin;1994-10-12;79156905353;Паспорт;0123456789 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,240 @@ | ||
| import logging | ||
| import csv | ||
| import re | ||
| import os | ||
|
|
||
| # Определим логгер | ||
| def setup_logger(logger_name, log_file, level=logging.INFO): | ||
| l = logging.getLogger(logger_name) | ||
| formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') | ||
| fileHandler = logging.FileHandler(log_file, 'w') | ||
| fileHandler.setFormatter(formatter) | ||
|
|
||
| l.setLevel(level) | ||
| l.addHandler(fileHandler) | ||
|
|
||
| setup_logger('info_logger', 'info.log', logging.INFO) | ||
| setup_logger('error_logger', 'error.log', logging.ERROR) | ||
|
|
||
|
|
||
| class LogAndChange(object): | ||
| """Дескриптор данных, в котором организована проверка полей | ||
| из конструктора объекта Patient а также их изменение | ||
| и логирование(логгер определим вне классов)""" | ||
|
|
||
| # Проверка даты | ||
| def _check_date(self,val): | ||
| if len(val) == 10: # Количество символов, включая знаки препинания и цифры, в верном формате равно 10 | ||
| if not re.match('(\d{4})-(\d{2})-(\d{2})', val): # проверка на НЕсоответсвие паттерну 1994-10-12 | ||
| new_date = re.split('\.|/|-', val) # Сформируем список из чисел по разделителям . / - | ||
| if len(new_date[2]) == 4:# если год стоит на последнем месте | ||
| new_date = new_date[::-1] #исправим это | ||
| new_string = '-'.join(new_date) # соберем все вместе в правильно формате 1994-10-12 | ||
| val = new_string | ||
| else: | ||
| self._error_logger.error('Неверный формат даты') | ||
| raise ValueError('Неверный формат даты') | ||
| return val | ||
|
|
||
| # Проверка типа документа | ||
| def _check_document_type(self, val): | ||
| _valid_document_types = { | ||
| 'Паспорт' : ['Паспорт', 'паспорт'], | ||
| 'Водительские права' : ['Водительские права', 'водительские права', 'права'], | ||
| 'Заграничный паспорт' : ['Заграничный паспорт', 'заграничный паспорт', 'загран'], | ||
| } | ||
| found = False # флаг найденного значения | ||
| for key, valid_list in _valid_document_types.items(): | ||
| if val in valid_list: # Проверка нахождения подаваемого значения в списке допустимых значений | ||
| val = key # В случае успеха приведем значение к человеческому виду | ||
| found = True# соответственно флаг теперь тру | ||
| if not found: # Если подаваемого значения в списке нет, то исключение | ||
| self._error_logger.error('Неверный тип документа') | ||
| raise ValueError('Неверный тип документа') | ||
| return val | ||
|
|
||
| # Проверка номера телефона | ||
| def _check_phone(self,val): | ||
| val = re.sub(r'[^0-9]+', r'', val) # для начала удалим все лишнее(все то, что не цифра) | ||
| if len(val) != 11: #количество цифр вместе с восьмеркой или семеркой | ||
| self._error_logger.error('Неверное количество цифр в номере телефона') | ||
| raise ValueError('Неверное количество цифр в номере телефона') | ||
| return val | ||
|
|
||
| # Проверка номера документа | ||
| def _check_doc_id(self, val, obj): # в doc_type будем передавать значение из dict объекта по ключу document_type | ||
| _valid_len = { | ||
| 'Паспорт' : 10, | ||
| 'Водительские права' : 10, | ||
| 'Заграничный паспорт' : 9, | ||
| } | ||
| doc_type = obj.__dict__.get('document_type') | ||
| v_len = _valid_len[doc_type] # Получим валидную длину указанного(doc_type) документа | ||
| val = re.sub(r'[^0-9]+', r'', val) # удалим все лишнее и непонятное | ||
|
|
||
| if len(val) != v_len: | ||
| self._error_logger.error('Неверное количество цифр в номере документа') | ||
| raise ValueError('Неверное количество цифр в номере документа ') | ||
| return val | ||
|
|
||
| def _check_name(self, val, obj): | ||
| if not val.isalpha(): | ||
| self._error_logger.error(f'Невалидное {self.name} {val}') | ||
| raise ValueError(f'Невалидное {self.name} {val}') | ||
| if self.name in obj.__dict__: #Если такое имя уже существует у объекта (позволяет избежать записи | ||
| self._error_logger.error(f'Попытка изменить {self.name}') # о смене фамилии или имени с None на текущую) | ||
| raise AttributeError(f'Попытка изменить {self.name}') | ||
| return val | ||
|
|
||
| def __init__(self, name='название_атрибута'): | ||
| self._info_logger = logging.getLogger('info_logger') | ||
| self._error_logger = logging.getLogger('error_logger') | ||
| self.name = name | ||
|
|
||
| def __get__(self, obj, objtype): | ||
| return obj.__dict__[self.name] # вернем значение атрибута по ключу(названию атрибута) | ||
|
|
||
| def __set__(self, obj, val): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. в этой функции довольно много повторяющегося кода: лучше как-то параметризовать
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Исправлено |
||
| if not isinstance(val, str): # | ||
| self._error_logger.error(f'Неверный тип {val} - {type(val)}') | ||
| raise TypeError(f'Неверный тип {val} - {type(val)}') | ||
|
|
||
| if self.name in ['first_name', 'last_name']: | ||
| val = self._check_name(val, obj) | ||
| elif self.name == 'birth_date': | ||
| val = self._check_date(val) | ||
| elif self.name == 'phone': | ||
| val = self._check_phone(val) | ||
| elif self.name == 'document_type': | ||
| val = self._check_document_type(val) | ||
| elif self.name == 'document_id': | ||
| val = self._check_doc_id(val, obj) #передаем объект для получения типа документа | ||
|
|
||
| if self.name in obj.__dict__: # изменяем только в том случае, когда объект существует | ||
| self._info_logger.info( | ||
| f"Изменяю {self.name} у пациента {obj.first_name} {obj.last_name} c {obj.__dict__.get(self.name)} на {val}") | ||
| obj.__dict__[self.name] = val | ||
|
|
||
|
|
||
|
|
||
| class Patient: | ||
| def __init__(self, *args, **kwargs): | ||
| pass | ||
| header = ['Имя', 'Фамилия', 'Дата рождения', 'Телефон', 'Вид документа', 'Номер документа'] | ||
| is_header_written = False # флаг, определяющий существование заголовка | ||
| first_name = LogAndChange('first_name') | ||
| last_name = LogAndChange('last_name') | ||
| birth_date = LogAndChange('birth_date') | ||
| phone = LogAndChange('phone') | ||
| document_type = LogAndChange('document_type') | ||
| document_id = LogAndChange('document_id') | ||
| current_row_idx = 1 #указатель на текущую строку | ||
|
|
||
| def __init__(self,first_name,last_name, birth_date, phone, document_type, document_id): | ||
| self._info_logger = logging.getLogger('info_logger') | ||
|
|
||
| if not(first_name and last_name and birth_date | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. при том, что все аргументы конструктора - позиционные и обязательные, эта проверка не нужна
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Здесь идет проверка не на пустоту аргумента, а на пустоту строки, листа, None итд |
||
| and phone and document_type and document_id): | ||
| raise TypeError('Заполнены не все поля') | ||
|
|
||
| self.first_name = first_name | ||
| self.last_name = last_name | ||
| self.birth_date = birth_date | ||
| self.phone = phone | ||
| self.document_type = document_type | ||
| self.document_id = document_id | ||
|
|
||
| self._saved = False # флаг, позволяющий узнать сохранен ли объект | ||
| # в начале объектов у нас нет | ||
| self._row_idx = None # индекс объекта | ||
| self._info_logger.info(f'Создан объект {self.first_name} {self.last_name} ' | ||
| f'{self.birth_date} {self.phone} {self.document_type} {self.document_id}') | ||
|
|
||
| def create(*args, **kwargs): | ||
| raise NotImplementedError() | ||
| @staticmethod | ||
| def create(first_name, last_name, birth_date, phone, document_type, document_id): | ||
| return Patient(first_name, last_name, birth_date, phone, document_type, document_id) | ||
|
|
||
| # Сохраняем пациента в csv таблицу | ||
| def save(self): | ||
| pass | ||
| data = [self.first_name, | ||
| self.last_name, | ||
| self.birth_date, | ||
| self.phone, | ||
| self.document_type, | ||
| self.document_id] | ||
| if self._saved: #Если пациент уже существует | ||
| with open('patient.csv', 'r', newline='') as f: | ||
| reader = csv.reader(f, delimiter=';') | ||
| rows = [row for row in reader] # список списков с полями пациента | ||
| rows[self._row_idx] = data # элементу списка со всеми пациентами присваются данные конкретного пациента | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. кажется, этот атрибут всегда будет равен количеству сохраненных в текущей сессии объектов
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. увидел использование этого атрибута в PatientCollection. предыдущий комментарий - про тот случай, когда Patient при готовом файле создается впервые
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Атрибут self._row_idx хранит в себе индекс строки, в которой записан текущий объект пациента |
||
| with open('patient.csv', 'w', newline='') as f: | ||
| writer = csv.writer(f, delimiter=';') | ||
| writer.writerows(rows) # записываем данные в файл с измененными данными одного пациента | ||
| else: | ||
| self._saved = True # Теперь пациент существует | ||
| self._row_idx = Patient.current_row_idx # конкретном объекту присвается номер строки в файле | ||
| Patient.current_row_idx += 1 # после этого для следующего объекта счетчик +1 | ||
|
|
||
| with open('patient.csv', "a", newline='') as csv_file: | ||
| writer = csv.writer(csv_file, delimiter=';') | ||
| if not Patient.is_header_written: # Пишем заголовок в файл, он должен быть записан всего один раз, поэтому вот так | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. этот атрибут будет работать верно только при единственном запуске программы. Кажется, что, если запустить этот код для уже готового файла в первый раз, то что-то пойдет не так
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Атрибут Patient.is_header_written - флаг показывающий, что записана шапка таблицы. |
||
| writer.writerow(Patient.header) | ||
| Patient.is_header_written = True | ||
| writer.writerow(data) | ||
|
|
||
| def __str__(self): | ||
| return (f'{self.first_name} {self.last_name} {self.birth_date} ' | ||
| f'{self.phone} {self.document_type} {self.document_id}') | ||
|
|
||
|
|
||
| class PatientCollection: | ||
| def __init__(self, log_file): | ||
| pass | ||
| def __init__(self, path_to_file): | ||
| self.path_to_file = path_to_file | ||
| with open(self.path_to_file) as csv_fd: | ||
| reader = csv.reader(csv_fd, delimiter=';') | ||
| next(reader) # пропускаем заголовок | ||
| for ind, row in enumerate(reader,1): # считаем количество записей в файле | ||
| pass | ||
| Patient.current_row_idx = ind | ||
| Patient.is_header_written = True | ||
|
|
||
| def __iter__(self): | ||
| with open(self.path_to_file, 'rb', buffering=0) as fp: | ||
| next(fp) # пропускаем заголовок | ||
| for i, line in enumerate(fp, 1): | ||
| row = line.decode().split(';') | ||
| p = Patient(*row) | ||
| p._saved = True # пациент сохранен | ||
| p._row_idx = i # в строке i | ||
| yield p | ||
|
|
||
| def limit(self,num): | ||
| with open(self.path_to_file, 'rb', buffering=0) as fp: | ||
| next(fp) # пропускаем заголовок | ||
| for i, l in enumerate(fp,1): | ||
| if i > num: | ||
| break | ||
| row = l.decode().split(';') | ||
| p = Patient(*row) | ||
| p._saved = True # пациент сохранен | ||
| p._row_idx = i # в строке i | ||
| yield p | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| # код для создания файла | ||
| # p = Patient('Vasya', 'Pupkin','08-04-1994','89126116381','паспорт','5162333666') | ||
| # p.save() | ||
| # p = Patient('Pip', 'Lil','08-04-1995','89126116456','паспорт','5162444666') | ||
| # p.save() | ||
| # p = Patient('Lil', 'Nas','08-04-1984','89126456891','паспорт','5162555666') | ||
| # p.save() | ||
|
|
||
| # код для проверки сохранения изменений и записи нового пациента | ||
| if os.path.isfile('patient.csv'): | ||
| pc = PatientCollection('patient.csv') | ||
|
|
||
| for p_ in pc.limit(1): | ||
| p_.birth_date = '11-01-1151' | ||
| p_.save() | ||
|
|
||
| def limit(self, n): | ||
| raise NotImplementedError() | ||
| p = Patient('Alexey', 'Putin','08-06-1984','89126756891','паспорт','5162755666') | ||
| p.save() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
константы лучше хранить в переменных
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Мне кажется, удобнее будет хранить в словаре, потому что константы семантически связаны и нет смысла записывать каждую в отдельную переменную, так будет сложнее поддерживать