From 9e145bc6dfedd38c84c5f76f9a8c45de2aeed1f3 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Sun, 26 Apr 2020 18:29:30 +0300 Subject: [PATCH 1/2] rabotaet --- homework/config.py | 18 ++-- homework/patient.py | 223 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 223 insertions(+), 18 deletions(-) diff --git a/homework/config.py b/homework/config.py index 955b991..cc15ede 100644 --- a/homework/config.py +++ b/homework/config.py @@ -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" # формат хранения номера ВУ diff --git a/homework/patient.py b/homework/patient.py index dad2526..578bcf7 100644 --- a/homework/patient.py +++ b/homework/patient.py @@ -1,17 +1,222 @@ +import logging +import csv +import re + +# Определим логгер +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, doc_type): # в doc_type будем передавать значение из dict объекта по ключу document_type + _valid_len = { + 'Паспорт' : 10, + 'Водительские права' : 10, + 'Заграничный паспорт' : 9, + } + 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 __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): + if not isinstance(val, str): + self._error_logger.error(f'Неверный тип {val} - {type(val)}') + raise TypeError(f'Неверный тип {val} - {type(val)}') + if self.name == 'birth_date': + val = self._check_date(val) + if self.name == 'phone': + val = self._check_phone(val) + if self.name == 'document_type': + val = self._check_document_type(val) + if self.name == 'document_id': + val = self._check_doc_id(val, obj.__dict__.get('document_type')) #передаем номер документа и тип документа из dict объекта + if self.name == 'first_name': + if not val.isalpha(): + self._error_logger.error('Невалидное имя {val}') + raise ValueError(f'Невалидное имя {val}') + if self.name in obj.__dict__: #Если такое имя уже существует у объекта (позволяет избежать записи + self._error_logger.error('Попытка изменить имя') # о смене фамилии или имени с None на текущую) + raise AttributeError('Попытка изменить имя') + elif self.name == 'last_name': + if not val.isalpha(): + self._error_logger.error('Невалидная фамилия {val}') + raise ValueError(f'Невалидная фамилия {val}') + if self.name in obj.__dict__: # Если такая фамилия уже существует у объекта + self._error_logger.error('Попытка изменить фамилию') + raise AttributeError('Попытка изменить фамилию') + else: + 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 + 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 - def create(*args, **kwargs): - raise NotImplementedError() + 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}') + @classmethod + def create(cls, first_name, last_name, birth_date, phone, document_type, document_id): + return cls(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 # элементу списка со всеми пациентами присваются данные конкретного пациента + 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: # Пишем заголовок в файл, он должен быть записан всего один раз, поэтому вот так + 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 + self.patient_list = [] + with open(self.path_to_file) as csv_fd: + reader = csv.reader(csv_fd, delimiter=';') + next(reader) # пропускаем заголовок + for ind, row in enumerate (reader,1): + p = Patient(*row) # Распакуем данные пациента из файла для создания объекта + p._saved = True # Теперь объект сохранен + p._row_idx = ind # Индекс объекта + self.patient_list.append(p) # Добавим пациента в список + Patient.current_row_idx = len(self.patient_list) + Patient.is_header_written = True + + def __iter__(self): + with open(self.path_to_file, 'rb', buffering=0) as fp: + # next(fp) + for line in fp: + row = line.decode().split(';') + p = Patient(*row) + yield p + + def limit(self,num): + with open(self.path_to_file, 'rb', buffering=0) as fp: + + for i, l in enumerate(fp): + if i > num: + break + row = l.decode().split(';') + p = Patient(*row) + yield p - def limit(self, n): - raise NotImplementedError() From cb6e043c40606685820e7779d2511ffb4225e937 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Mon, 4 May 2020 16:57:38 +0300 Subject: [PATCH 2/2] fixes --- homework/patient.csv | 16 +++++ homework/patient.py | 100 ++++++++++++++++++------------- tests/test_patient_collection.py | 3 +- 3 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 homework/patient.csv diff --git a/homework/patient.csv b/homework/patient.csv new file mode 100644 index 0000000..6be2626 --- /dev/null +++ b/homework/patient.csv @@ -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 diff --git a/homework/patient.py b/homework/patient.py index 578bcf7..80d9cf0 100644 --- a/homework/patient.py +++ b/homework/patient.py @@ -1,6 +1,7 @@ import logging import csv import re +import os # Определим логгер def setup_logger(logger_name, log_file, level=logging.INFO): @@ -61,12 +62,13 @@ def _check_phone(self,val): return val # Проверка номера документа - def _check_doc_id(self, val, doc_type): # в doc_type будем передавать значение из dict объекта по ключу document_type + 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) # удалим все лишнее и непонятное @@ -75,6 +77,15 @@ def _check_doc_id(self, val, doc_type): # в doc_type будем передав 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') @@ -84,44 +95,31 @@ def __get__(self, obj, objtype): return obj.__dict__[self.name] # вернем значение атрибута по ключу(названию атрибута) def __set__(self, obj, val): - if not isinstance(val, str): + if not isinstance(val, str): # self._error_logger.error(f'Неверный тип {val} - {type(val)}') raise TypeError(f'Неверный тип {val} - {type(val)}') - if self.name == 'birth_date': + + if self.name in ['first_name', 'last_name']: + val = self._check_name(val, obj) + elif self.name == 'birth_date': val = self._check_date(val) - if self.name == 'phone': + elif self.name == 'phone': val = self._check_phone(val) - if self.name == 'document_type': + elif self.name == 'document_type': val = self._check_document_type(val) - if self.name == 'document_id': - val = self._check_doc_id(val, obj.__dict__.get('document_type')) #передаем номер документа и тип документа из dict объекта - if self.name == 'first_name': - if not val.isalpha(): - self._error_logger.error('Невалидное имя {val}') - raise ValueError(f'Невалидное имя {val}') - if self.name in obj.__dict__: #Если такое имя уже существует у объекта (позволяет избежать записи - self._error_logger.error('Попытка изменить имя') # о смене фамилии или имени с None на текущую) - raise AttributeError('Попытка изменить имя') - elif self.name == 'last_name': - if not val.isalpha(): - self._error_logger.error('Невалидная фамилия {val}') - raise ValueError(f'Невалидная фамилия {val}') - if self.name in obj.__dict__: # Если такая фамилия уже существует у объекта - self._error_logger.error('Попытка изменить фамилию') - raise AttributeError('Попытка изменить фамилию') - else: - 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}") + 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: header = ['Имя', 'Фамилия', 'Дата рождения', 'Телефон', 'Вид документа', 'Номер документа'] is_header_written = False # флаг, определяющий существование заголовка - first_name = LogAndChange('first_name') last_name = LogAndChange('last_name') birth_date = LogAndChange('birth_date') @@ -150,9 +148,9 @@ def __init__(self,first_name,last_name, birth_date, phone, document_type, docume self._info_logger.info(f'Создан объект {self.first_name} {self.last_name} ' f'{self.birth_date} {self.phone} {self.document_type} {self.document_id}') - @classmethod - def create(cls, first_name, last_name, birth_date, phone, document_type, document_id): - return cls(first_name, last_name, birth_date, phone, document_type, document_id) + @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): @@ -190,33 +188,53 @@ def __str__(self): class PatientCollection: def __init__(self, path_to_file): self.path_to_file = path_to_file - self.patient_list = [] with open(self.path_to_file) as csv_fd: reader = csv.reader(csv_fd, delimiter=';') next(reader) # пропускаем заголовок - for ind, row in enumerate (reader,1): - p = Patient(*row) # Распакуем данные пациента из файла для создания объекта - p._saved = True # Теперь объект сохранен - p._row_idx = ind # Индекс объекта - self.patient_list.append(p) # Добавим пациента в список - Patient.current_row_idx = len(self.patient_list) + 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 line in 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: - - for i, l in enumerate(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() + + p = Patient('Alexey', 'Putin','08-06-1984','89126756891','паспорт','5162755666') + p.save() \ No newline at end of file diff --git a/tests/test_patient_collection.py b/tests/test_patient_collection.py index 9536eff..0f6a2c3 100644 --- a/tests/test_patient_collection.py +++ b/tests/test_patient_collection.py @@ -25,6 +25,7 @@ @pytest.fixture() def prepare(): + Patient.is_header_written = False with open(CSV_PATH, 'w', encoding='utf-8') as f: f.write('') for params in GOOD_PARAMS: @@ -74,5 +75,5 @@ def test_limit_remove_records(): collection = PatientCollection(CSV_PATH) limit = collection.limit(4) with open(CSV_PATH, 'w', encoding='utf-8') as f: - f.write('') + f.write('\n') assert len([_ for _ in limit]) == 0, "Limit works wrong for empty file"