From 9e145bc6dfedd38c84c5f76f9a8c45de2aeed1f3 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Sun, 26 Apr 2020 18:29:30 +0300 Subject: [PATCH 1/6] 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/6] 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" From ab9f6ea3b1645ababc60f24e17bcf4473909c156 Mon Sep 17 00:00:00 2001 From: Alexander Skiba Date: Sat, 9 May 2020 18:59:32 +0300 Subject: [PATCH 3/6] add decorator, cli, sql --- HW3.py | 263 +++++++++++++++++++++++++++++++ README.md | 17 +- cli.py | 45 ++++++ conf.py | 8 + homework/__init__.py | 0 homework/config.py | 13 -- homework/patient.csv | 16 -- homework/patient.py | 240 ---------------------------- log.py | 38 +++++ tests/__init__.py | 0 tests/constants.py | 6 - tests/test_patient.py | 195 ----------------------- tests/test_patient_collection.py | 79 ---------- 13 files changed, 362 insertions(+), 558 deletions(-) create mode 100644 HW3.py create mode 100644 cli.py create mode 100644 conf.py delete mode 100644 homework/__init__.py delete mode 100644 homework/config.py delete mode 100644 homework/patient.csv delete mode 100644 homework/patient.py create mode 100644 log.py delete mode 100644 tests/__init__.py delete mode 100644 tests/constants.py delete mode 100644 tests/test_patient.py delete mode 100644 tests/test_patient_collection.py diff --git a/HW3.py b/HW3.py new file mode 100644 index 0000000..5a789b8 --- /dev/null +++ b/HW3.py @@ -0,0 +1,263 @@ +import logging +import re +import psycopg2 +from log import logging_decor +from conf import DATA_BASE, LOGIN, PASSWD, HST, NPORT + +conn = psycopg2.connect(database=DATA_BASE, user=LOGIN, + password=PASSWD, host=HST, port=NPORT) + +cur = conn.cursor() +# cur.execute("DROP TABLE IF EXISTS patients") +cur.execute("CREATE TABLE IF NOT EXISTS patients (patient_id SERIAL PRIMARY KEY,first_name VARCHAR(64), " + "last_name VARCHAR(64), birth_date VARCHAR(64), phone VARCHAR(64), " +"document_type VARCHAR(64), document_id VARCHAR(64))") +conn.commit() + + + +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] # вернем значение атрибута по ключу(названию атрибута) + + @logging_decor + 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 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__: # изменяем только в том случае, когда объект существует + pass + # 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: + 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 # указатель на текущую строку + + @logging_decor + 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 + + 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}') + #ретурн строка выше + + @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): + data = [self.first_name, + self.last_name, + self.birth_date, + self.phone, + self.document_type, + self.document_id] + if self._saved: # Если пациент уже существует + #получаем его индекс cur.fetchall()[self._row_idx][0] и меняем данные пациента + + # + # cur.execute("SELECT * FROM patients") #смотрим на пациентов + # cur.fetchall()[self._row_idx][0] # индекс пациента + # где-нибудь здесь получим индекс и сделаем UPDATE распакуем пациента в БД + cur.execute(f"UPDATE patients SET first_name = '{self.first_name}', last_name = '{self.last_name}'," + f" birth_date = '{self.birth_date}'," + f"phone = '{self.phone}', document_type = '{self.document_type}'," + f" document_id = '{self.document_id}' WHERE patient_id = {self._row_idx}") + conn.commit() + + # элементу списка со всеми пациентами присваются данные конкретного пациента + + else: + self._saved = True # Теперь пациент существует + self._row_idx = Patient.current_row_idx # конкретном объекту присвается номер строки в файле + Patient.current_row_idx += 1 # после этого для следующего объекта счетчик +1 + + # БД covid + cur.execute("INSERT INTO patients (first_name, last_name, birth_date, " + "phone, document_type, document_id) VALUES (%s, %s, %s, %s, %s, %s)", + (self.first_name, + self.last_name, + self.birth_date, + self.phone, + self.document_type, + self.document_id)) # распакуем пациента + conn.commit() + 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, path_to_file): + # self.path_to_file = path_to_file + + + def __iter__(self): + #создать курсор, установить размер буфера на одну запись и итерироваться по курсору + cur.execute("SELECT * FROM patients ORDER BY patient_id") + for i in cur.fetchall(): + # print(i) + row = [i[1], i[2], i[3], i[4], i[5], i[6]] + p = Patient(*row) + # p._saved = True # пациент сохранен + # p._row_idx = i # в строке i + yield p + + def limit(self, num): + cur.execute("SELECT * FROM patients ORDER BY patient_id") + for i in cur.fetchall(): + # print(i) + if i[0]> num: + break + + row = [i[1], i[2], i[3], i[4], i[5], i[6]] + p = Patient(*row) + # p._saved = True # пациент сохранен + # p._row_idx = i # в строке i + yield p + + # def __str__(self): + # return (f'{Patient.first_name} {Patient.last_name} {Patient.birth_date} ' + # f'{Patient.phone} {Patient.document_type} {Patient.document_id}') + + +if __name__ == "__main__": + + + # p = Patient('Vasya', 'Pupkin','08-04-1994','89126116381','паспорт','5162333666') + # p.save() + # p.birth_date = '12-10-1994' + # p.save() + # + # p = Patient('Pip', 'Lil','08-04-1995','89126116456','паспорт','5162444666') + # p.save() + # p = Patient('Lil', 'Nas','08-04-1984','89126456891','паспорт','5162555666') + # p.save() + # + p = Patient('kek', 'shpek', '08-04-1995', '89126116456', 'паспорт', '5162444666') + p.save() + p = Patient('mem', 'kekov', '08-04-1984', '89126456891', 'паспорт', '5162555666') + p.save() + + p = Patient('Peton', 'ivanov', '08-04-1984', '89126456891', 'паспорт', '5162555666') + p.save() + p = Patient('Vasiliii', 'Petrov', '08-04-1984', '89126456891', 'паспорт', '5162555666') + p.save() + # p = Patient('Bill', 'Gates', '08-04-1984', '89126456891', 'паспорт', '51555666') + # p.save() + + # pc = PatientCollection() + # for i in pc: + # print(i) + # print() + # + # + # for x in pc.limit(5): + # print(x) diff --git a/README.md b/README.md index 316aa05..ca42fe1 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ -# python_developer_hw2 -Репозиторий для проверки домашки №2 по курсу "Разработчик Python" + Для работы необходимо в файле conf.py заменить данные на свои + Запуск из консоли: +Создание python cli.py create Имя Фамилия --birth-date 1990-01-01 --phone +7-916-000-00-00 --document-type паспорт --document-number 0000000000 +Вывод на экран первых 10 пользователей: python cli.py show +Вывод на экран произвольного количества пользователей: python cli.py show 8 +Вывод на экран количества сохраненных пользователей: python cli.py count -## Проверка ДЗ - -1. В файле homework/config.py заполняете все константы своими значениями -2. Размещаете классы Patient и PatientCollection в файле homework/patient.py -3. ```pytest tests``` - -Если всё ок, то Pull-request в этот репозиторий \ No newline at end of file +P.S. + Перед созданием пациента логируется изменение полей, как сделать в декораторе так, чтобы логирование шло именно при реальном изменении, пока не придумал. diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..65088c1 --- /dev/null +++ b/cli.py @@ -0,0 +1,45 @@ +import click +from HW3 import Patient, PatientCollection + +@click.group() +def cli(): + """Создаем пациентов через консоль""" +@click.command('create') +@click.argument('first-name') +@click.argument('last-name') +@click.option('--birth-date', help='The birth date format "yyyy-mm-dd"') +@click.option('--phone', help='The phone format "8-ddd-ddd-dd-dd"') +@click.option('--document-type', help='The document type must be "Паспорт", "Заграничный паспорт",' + ' "Водительские права"') +@click.option('--document-number', help='The document id must have 10 digits for russian passport or driving licence' + 'or 9 digits for international passport') + +def create_patient(first_name, last_name, birth_date, phone, document_type, document_number): + p = Patient(first_name, last_name, birth_date, phone, document_type, document_number) + p.save() + + +@click.command('show') +@click.argument('num', default=10) +def show_patients(num): + pc = PatientCollection() + for m in pc.limit(num): + click.echo(m) + + +@click.command('count') +# @click.argument('num', default=10) +def count_patients(): + pc = PatientCollection() + count=0 + for m in pc: + count+=1 + click.echo(count) + + + +cli.add_command(create_patient) +cli.add_command(show_patients) +cli.add_command(count_patients) +cli() + diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..85f680c --- /dev/null +++ b/conf.py @@ -0,0 +1,8 @@ +DATA_BASE = 'covid' +LOGIN = 'aleksandrskiba' +PASSWD = '31471' +HST = 'localhost' +NPORT = 5432 + + + diff --git a/homework/__init__.py b/homework/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/homework/config.py b/homework/config.py deleted file mode 100644 index cc15ede..0000000 --- a/homework/config.py +++ /dev/null @@ -1,13 +0,0 @@ -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 = "0000000000" # Здесь запишите номер парспорта 0000 000000 в том формате, в котором вы его храните - -INTERNATIONAL_PASSPORT_TYPE = "Заграничный паспорт" # тип документа, если это загран -INTERNATIONAL_PASSPORT_FORMAT = "000000000" # формат хранения заграна для номера 00 0000000 - -DRIVER_LICENSE_TYPE = "Водительские права" # тип документа, если это водительское удостоверение -DRIVER_LICENSE_FORMAT = "0000000000" # формат хранения номера ВУ diff --git a/homework/patient.csv b/homework/patient.csv deleted file mode 100644 index 6be2626..0000000 --- a/homework/patient.csv +++ /dev/null @@ -1,16 +0,0 @@ -Имя;Фамилия;Дата рождения;Телефон;Вид документа;Номер документа -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 deleted file mode 100644 index 80d9cf0..0000000 --- a/homework/patient.py +++ /dev/null @@ -1,240 +0,0 @@ -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): - 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: - 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 - - 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}') - - @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): - 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, 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() - - p = Patient('Alexey', 'Putin','08-06-1984','89126756891','паспорт','5162755666') - p.save() \ No newline at end of file diff --git a/log.py b/log.py new file mode 100644 index 0000000..8653b02 --- /dev/null +++ b/log.py @@ -0,0 +1,38 @@ +import logging +import functools + +# Определим логгер +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) + +logger_info = logging.getLogger('info_logger') +logger_error = logging.getLogger('error_logger') + +def logging_decor(func): + @functools.wraps(func) + def decorated(self,*args): + try: + result = func(self, *args) + except TypeError as ex: + logger_error.error("TypeError {0}".format(ex)) + raise ex + except ValueError as ex: + logger_error.error("ValueError {0}".format(ex)) + raise ex + except AttributeError as ex: + logger_error.error("Попытка изменить имя или фамилию {0}".format(ex)) + raise ex + names_dict = {'__init__': 'Создан объект','__set__':'Изменено' } + logger_info.info(f'{names_dict[func.__name__]} {args}') + return result + return decorated diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/constants.py b/tests/constants.py deleted file mode 100644 index 6107319..0000000 --- a/tests/constants.py +++ /dev/null @@ -1,6 +0,0 @@ -from homework.config import PHONE_FORMAT, DRIVER_LICENSE_TYPE, DRIVER_LICENSE_FORMAT, PASSPORT_TYPE - -GOOD_PARAMS = ("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, DRIVER_LICENSE_TYPE, DRIVER_LICENSE_FORMAT) -OTHER_GOOD_PARAMS = ("Нурсултан", "Назарбаев", "1900-01-01", "+7-916-111-11-11", PASSPORT_TYPE, "1111 111111") -WRONG_PARAMS = ("098098", "56876576558", "ABCDEF", "sdfsdfsdf", "sdfsdfsdfs", "sdflsdfiuh") -PATIENT_FIELDS = ("first_name", "last_name", "birth_date", "phone", "document_type", "document_id") \ No newline at end of file diff --git a/tests/test_patient.py b/tests/test_patient.py deleted file mode 100644 index a442599..0000000 --- a/tests/test_patient.py +++ /dev/null @@ -1,195 +0,0 @@ -import functools -import os -from datetime import datetime -import itertools - -import pytest - -from homework.config import GOOD_LOG_FILE, ERROR_LOG_FILE, CSV_PATH, PHONE_FORMAT, PASSPORT_TYPE, PASSPORT_FORMAT, \ - INTERNATIONAL_PASSPORT_FORMAT, INTERNATIONAL_PASSPORT_TYPE, DRIVER_LICENSE_TYPE, DRIVER_LICENSE_FORMAT -from homework.patient import Patient -from tests.constants import GOOD_PARAMS, OTHER_GOOD_PARAMS, WRONG_PARAMS, PATIENT_FIELDS - - -def get_len(file): - with open(file, encoding='utf-8') as f: - return len(f.readlines()) - - -def check_log_size(log, increased=False): - def deco(func): - log_map = {"error": ERROR_LOG_FILE, "good": GOOD_LOG_FILE, "csv": CSV_PATH} - log_path = log_map.get(log, log) - @functools.wraps(func) - def wrapper(*args, **kwargs): - log_len = get_len(log_path) - result = func(*args, **kwargs) - new_len = get_len(log_path) - assert new_len > log_len if increased else new_len == log_len, f"Wrong {log} file length" - return result - return wrapper - return deco - - -def setup_module(__main__): - for file in [GOOD_LOG_FILE, ERROR_LOG_FILE, CSV_PATH]: - with open(file, 'w', encoding='utf-8') as f: - f.write('') - - -def teardown_module(__name__): - for file in [GOOD_LOG_FILE, ERROR_LOG_FILE, CSV_PATH]: - os.remove(file) - - -@check_log_size("error") -@check_log_size("good", increased=True) -def test_creation_all_good_params(): - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, PASSPORT_TYPE, PASSPORT_FORMAT) - assert patient.first_name == "Кондрат", "Wrong attribute first_name" - assert patient.last_name == "Коловрат", "Wrong attribute last_name" - assert patient.birth_date == "1978-01-31" or patient.birth_date == datetime(1978, 1, 31), \ - "Wrong attribute birth_date" - assert patient.phone == PHONE_FORMAT, "Wrong attribute phone" - assert patient.document_type == PASSPORT_TYPE, "Wrong attribute document_type" - assert patient.document_id == PASSPORT_FORMAT, "Wrong attribute document_id" - - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, INTERNATIONAL_PASSPORT_TYPE, - INTERNATIONAL_PASSPORT_FORMAT) - assert patient.document_type == INTERNATIONAL_PASSPORT_TYPE, "Wrong attribute document_type" - assert patient.document_id == INTERNATIONAL_PASSPORT_FORMAT, "Wrong attribute document_id" - - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, DRIVER_LICENSE_TYPE, DRIVER_LICENSE_FORMAT) - assert patient.document_type == DRIVER_LICENSE_TYPE, "Wrong attribute document_type" - assert patient.document_id == DRIVER_LICENSE_FORMAT, "Wrong attribute document_id" - - -@pytest.mark.parametrize('default,new', itertools.permutations(["(916)", "916", "-916-"], 2)) -@check_log_size("error") -@check_log_size("good", increased=True) -def test_creation_acceptable_phone(default, new): - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT.replace(default, new), - PASSPORT_TYPE, PASSPORT_FORMAT) - assert patient.phone == PHONE_FORMAT, "Wrong attribute phone" - - -@pytest.mark.parametrize('passport', ("00 00 000 000", "0000-000000", "0 0 0 0 0 0 0 0 0 0", "0000/000-000")) -@check_log_size("error") -@check_log_size("good", increased=True) -def test_creation_acceptable_passport(passport): - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, PASSPORT_TYPE, passport) - assert patient.document_id == PASSPORT_FORMAT, "Wrong attribute document_id" - - -@pytest.mark.parametrize('passport', ("00 0000000", "00-0000000", "0 0 0 0 0 0 0 0 0", "00/000-0000")) -@check_log_size("error") -@check_log_size("good", increased=True) -def test_creation_acceptable_international_passport(passport): - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, INTERNATIONAL_PASSPORT_TYPE, passport) - assert patient.document_id == INTERNATIONAL_PASSPORT_FORMAT, "Wrong attribute document_id" - - -@pytest.mark.parametrize('driver_license', ("00 00 000 000", "0000-000000", "0 0 0 0 0 0 0 0 0 0", "0000/000000")) -@check_log_size("error") -@check_log_size("good", increased=True) -def test_creation_acceptable_driver_license(driver_license): - patient = Patient("Кондрат", "Коловрат", "1978-01-31", PHONE_FORMAT, DRIVER_LICENSE_TYPE, driver_license) - assert patient.document_id == DRIVER_LICENSE_FORMAT, "Wrong attribute document_id" - - -# неверный тип -@pytest.mark.parametrize("i", list(range(len(GOOD_PARAMS)))) -@check_log_size("error", increased=True) -@check_log_size("good") -def test_creation_wrong_type_params(i): - try: - Patient(*GOOD_PARAMS[:i], 1.8, *GOOD_PARAMS[i+1:]) - assert False, f"TypeError for {PATIENT_FIELDS[i]} not invoked" - except TypeError: - assert True - - -# неверные значения -@pytest.mark.parametrize("i", list(range(len(GOOD_PARAMS)))) -@check_log_size("error", increased=True) -@check_log_size("good") -def test_creation_wrong_params(i): - try: - Patient(*GOOD_PARAMS[:i], WRONG_PARAMS[i], *GOOD_PARAMS[i+1:]) - assert False, f"ValueError for {PATIENT_FIELDS[i]} not invoked" - except ValueError: - assert True - - -# метод create -@check_log_size("error") -@check_log_size("good", increased=True) -def test_create_method_good_params(): - patient = Patient.create(*GOOD_PARAMS) - for param, field in zip(GOOD_PARAMS, PATIENT_FIELDS): - assert getattr(patient, field) in (param, datetime(1978, 1, 31)), f"Wrong attribute {field}" - - -# обновление параметров -@pytest.mark.parametrize("patient,field,param", zip( - [Patient(*OTHER_GOOD_PARAMS)] * len(PATIENT_FIELDS[:2]), - PATIENT_FIELDS[:2], - GOOD_PARAMS[:2] -)) -@check_log_size("error", increased=True) -@check_log_size("good") -def test_names_assignment(patient, field, param): - try: - setattr(patient, field, param) - assert False, f"Attribute error should be invoked for {field} changing" - except AttributeError: - assert True - - -@pytest.mark.parametrize("patient,field,param", zip( - [Patient(*OTHER_GOOD_PARAMS)] * len(PATIENT_FIELDS[2:]), - PATIENT_FIELDS[2:], - GOOD_PARAMS[2:] -)) -@check_log_size("error") -@check_log_size("good", increased=True) -def test_good_params_assignment(patient, field, param): - setattr(patient, field, param) - assert getattr(patient, field) == param, f"Attribute {field} did not change" - - -@pytest.mark.parametrize("patient,field,param", zip( - [Patient(*OTHER_GOOD_PARAMS)] * len(PATIENT_FIELDS[2:]), - PATIENT_FIELDS[2:], - [1.4] * len(PATIENT_FIELDS[2:]) -)) -@check_log_size("error", increased=True) -@check_log_size("good") -def test_wrong_type_assignment(patient, field, param): - try: - setattr(patient, field, param) - assert False, f"TypeError for {field} assignment not invoked" - except TypeError: - assert True - - -@pytest.mark.parametrize("patient,field,param", zip( - [Patient(*OTHER_GOOD_PARAMS)] * len(PATIENT_FIELDS[2:]), - PATIENT_FIELDS[2:], - WRONG_PARAMS[2:] -)) -@check_log_size("error", increased=True) -@check_log_size("good") -def test_wrong_value_assignment(patient, field, param): - try: - setattr(patient, field, param) - assert False, f"ValueError for {field} assignment not invoked" - except ValueError: - assert True - - -# метод save -@check_log_size("csv", increased=True) -def test_save(): - patient = Patient(*GOOD_PARAMS) - patient.save() diff --git a/tests/test_patient_collection.py b/tests/test_patient_collection.py deleted file mode 100644 index 0f6a2c3..0000000 --- a/tests/test_patient_collection.py +++ /dev/null @@ -1,79 +0,0 @@ -import os - -import pytest - -from homework.config import PASSPORT_TYPE, CSV_PATH -from homework.patient import PatientCollection, Patient -from tests.constants import PATIENT_FIELDS - -GOOD_PARAMS = ( - ("Кондрат", "Рюрик", "1971-01-11", "79160000000", PASSPORT_TYPE, "0228 000000"), - ("Евпатий", "Коловрат", "1972-01-11", "79160000001", PASSPORT_TYPE, "0228 000001"), - ("Ада", "Лавлейс", "1978-01-21", "79160000002", PASSPORT_TYPE, "0228 000002"), - ("Миртл", "Плакса", "1880-01-11", "79160000003", PASSPORT_TYPE, "0228 000003"), - ("Евлампия", "Фамилия", "1999-01-21", "79160000004", PASSPORT_TYPE, "0228 000004"), - ("Кузя", "Кузьмин", "2000-01-21", "79160000005", PASSPORT_TYPE, "0228 000005"), - ("Гарри", "Поттер", "2020-01-11", "79160000006", PASSPORT_TYPE, "0228 000006"), - ("Рон", "Уизли", "1900-04-20", "79160000007", PASSPORT_TYPE, "0228 000007"), - ("Билл", "Гейтс", "1978-12-31", "79160000008", PASSPORT_TYPE, "0228 000008"), - ("Владимир", "Джугашвили", "1912-01-31", "79160000009", PASSPORT_TYPE, "0228 000009"), - ("Вован", "ДеМорт", "1978-11-30", "79160000010", PASSPORT_TYPE, "0228 000010"), - ("Гопник", "Районный", "1978-01-25", "79160000011", PASSPORT_TYPE, "0228 000011"), - ("Фёдор", "Достоевский", "1978-01-05", "79160000012", PASSPORT_TYPE, "0228 000012"), -) - - -@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: - Patient(*params).save() - yield - os.remove(CSV_PATH) - - -@pytest.mark.usefixtures('prepare') -def test_collection_iteration(): - collection = PatientCollection(CSV_PATH) - for i, patient in enumerate(collection): - true_patient = Patient(*GOOD_PARAMS[i]) - for field in PATIENT_FIELDS: - assert getattr(patient, field) == getattr(true_patient, field), f"Wrong attr {field} for {GOOD_PARAMS[i]}" - - -@pytest.mark.usefixtures('prepare') -def test_limit_usual(): - collection = PatientCollection(CSV_PATH) - try: - len(collection.limit(8)) - assert False, "Iterator should not have __len__ method" - except (TypeError, AttributeError): - assert True - for i, patient in enumerate(collection.limit(8)): - true_patient = Patient(*GOOD_PARAMS[i]) - for field in PATIENT_FIELDS: - assert getattr(patient, field) == getattr(true_patient, field), f"Wrong attr {field} for {GOOD_PARAMS[i]} in limit" - - -@pytest.mark.usefixtures('prepare') -def test_limit_add_record(): - collection = PatientCollection(CSV_PATH) - limit = collection.limit(len(GOOD_PARAMS) + 10) - for _ in range(len(GOOD_PARAMS)): - next(limit) - new_patient = Patient("Митрофан", "Космодемьянский", "1999-10-15", "79030000000", PASSPORT_TYPE, "4510 000444") - new_patient.save() - last_patient = next(limit) - for field in PATIENT_FIELDS: - assert getattr(new_patient, field) == getattr(last_patient, field), f"Wrong attr {field} for changed limit" - - -@pytest.mark.usefixtures('prepare') -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('\n') - assert len([_ for _ in limit]) == 0, "Limit works wrong for empty file" From 93a8d4969a4d872af3e1fdf5f27e02528ee30b05 Mon Sep 17 00:00:00 2001 From: alexanderskiba <58416654+alexanderskiba@users.noreply.github.com> Date: Sat, 9 May 2020 19:01:53 +0300 Subject: [PATCH 4/6] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ca42fe1..3773959 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,13 @@  Для работы необходимо в файле conf.py заменить данные на свои +  Запуск из консоли: + Создание python cli.py create Имя Фамилия --birth-date 1990-01-01 --phone +7-916-000-00-00 --document-type паспорт --document-number 0000000000 + Вывод на экран первых 10 пользователей: python cli.py show + Вывод на экран произвольного количества пользователей: python cli.py show 8 + Вывод на экран количества сохраненных пользователей: python cli.py count P.S. From b889b4a1711d0e5899087a56d0e74d23f3017c65 Mon Sep 17 00:00:00 2001 From: alexanderskiba <58416654+alexanderskiba@users.noreply.github.com> Date: Sat, 9 May 2020 19:06:27 +0300 Subject: [PATCH 5/6] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 3773959..9e19b22 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,14 @@ P.S.  Перед созданием пациента логируется изменение полей, как сделать в декораторе так, чтобы логирование шло именно при реальном изменении, пока не придумал. + +P.P.S так же необходимо проделать: +pip3 install psycopg2-binary +В Linux может потребоваться sudo apt install libpq-dev +Ну и сам postgres: + +Create the file /etc/apt/sources.list.d/pgdg.list and add a line for the repository +deb http://apt.postgresql.org/pub/repos/apt/ YOUR_UBUNTU_VERSION_HERE-pgdg main +Import the repository signing key, and update the package lists +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - +sudo apt-get update From 91481a26f0ebc49e03b382ebd0313bcc6c300b68 Mon Sep 17 00:00:00 2001 From: alexanderskiba <58416654+alexanderskiba@users.noreply.github.com> Date: Sat, 9 May 2020 19:07:14 +0300 Subject: [PATCH 6/6] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9e19b22..ab58471 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,18 @@ P.S.  Перед созданием пациента логируется изменение полей, как сделать в декораторе так, чтобы логирование шло именно при реальном изменении, пока не придумал. P.P.S так же необходимо проделать: + pip3 install psycopg2-binary + В Linux может потребоваться sudo apt install libpq-dev + Ну и сам postgres: Create the file /etc/apt/sources.list.d/pgdg.list and add a line for the repository + deb http://apt.postgresql.org/pub/repos/apt/ YOUR_UBUNTU_VERSION_HERE-pgdg main + Import the repository signing key, and update the package lists + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt-get update