From e430a043eeacd3ea0d5c8fd88f313e388b0db902 Mon Sep 17 00:00:00 2001 From: exeynod Date: Tue, 28 Apr 2020 12:54:25 +0300 Subject: [PATCH 1/3] All test passed --- homework/config.py | 14 +-- homework/log.py | 19 +++ homework/patient.py | 198 +++++++++++++++++++++++++++++-- tests/test_patient.py | 6 +- tests/test_patient_collection.py | 16 +-- 5 files changed, 227 insertions(+), 26 deletions(-) create mode 100644 homework/log.py diff --git a/homework/config.py b/homework/config.py index 955b991..093bc24 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" -PHONE_FORMAT = "79160000000" # Здесь запишите телефон +7-916-000-00-00 в том формате, в котором вы храните телефоны +GOOD_LOG_FILE = "good_log.log" +ERROR_LOG_FILE = "error_log.log" +CSV_PATH = "data.csv" +PHONE_FORMAT = "9160000000" # Здесь запишите телефон +7-916-000-00-00 в том формате, в котором вы храните телефоны PASSPORT_TYPE = "паспорт" # тип документа, когда он паспорт -PASSPORT_FORMAT = "0000 000000" # Здесь запишите номер парспорта 0000 000000 в том формате, в котором вы его храните +PASSPORT_FORMAT = "0000000000" # Здесь запишите номер парспорта 0000 000000 в том формате, в котором вы его храните INTERNATIONAL_PASSPORT_TYPE = "заграничный паспорт" # тип документа, если это загран -INTERNATIONAL_PASSPORT_FORMAT = "00 0000000" # формат хранения заграна для номера 00 0000000 +INTERNATIONAL_PASSPORT_FORMAT = "000000000" # формат хранения заграна для номера 00 0000000 DRIVER_LICENSE_TYPE = "водительское удостоверение" # тип документа, если это водительское удостоверение -DRIVER_LICENSE_FORMAT = "00 00 000000" # формат хранения номера ВУ +DRIVER_LICENSE_FORMAT = "0000000000" # формат хранения номера ВУ diff --git a/homework/log.py b/homework/log.py new file mode 100644 index 0000000..b982659 --- /dev/null +++ b/homework/log.py @@ -0,0 +1,19 @@ +import logging + +# info_logger_setup +info_logger = logging.getLogger('Info_Logger') +info_logger.setLevel(logging.INFO) +fh_info = logging.FileHandler('good_log.log', encoding='utf-8') +fh_info.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +fh_info.setFormatter(formatter) +info_logger.addHandler(fh_info) + +# error_logger_setup +error_logger = logging.getLogger('Error_Logger') +error_logger.setLevel(logging.ERROR) +fh_error = logging.FileHandler('error_log.log', encoding='utf-8') +fh_error.setLevel(logging.ERROR) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +fh_error.setFormatter(formatter) +error_logger.addHandler(fh_error) \ No newline at end of file diff --git a/homework/patient.py b/homework/patient.py index dad2526..b90d5fc 100644 --- a/homework/patient.py +++ b/homework/patient.py @@ -1,17 +1,199 @@ +from homework.log import error_logger, info_logger +import csv + +csv_path = 'data.csv' + + class Patient: - def __init__(self, *args, **kwargs): - pass + @staticmethod + def valid_date(date): + import time + try: + time.strptime(date, '%Y-%m-%d') + res = '-' + except ValueError: + try: + time.strptime(date, '%Y.%m.%d') + res = '.' + except ValueError: + error_logger.error(f'{date}: invalid date format') + res = None + return res + + @staticmethod + def valid_phone(phone): + res = phone.replace('+', '').replace('-', '').replace('(', '').replace(')', '').replace(' ', '') + if len(res) == 11 and res[0] in {'7', '8'}: + res = res[1:] + return res + if len(res) == 10: + return res + error_logger.error(f'{phone} Invalid phone format') + return None + + @staticmethod + def valid_document(document_type, document_id): + document = document_id.replace(' ', '').replace('-', '').replace('/', '') + if not document.isdigit(): + error_logger.error(f'{document_id}: invalid document id') + return False + if document_type in {'водительское удостоверение', 'паспорт'}: + return len(document) == 10 + if document_type == 'заграничный паспорт': + return len(document) == 9 + error_logger.error(f'{document_id}: invalid document id') + return False + + def __init__(self, fname, lname, birth_date, phone, document_type, document_id): + for string in (fname, lname, birth_date, phone, document_id, document_type): + if not isinstance(string, str): + error_logger.error(f'{string} must be string') + raise TypeError + for word in (fname, lname): + if not word.isalpha(): + error_logger.error(f'{word} must be an alpha') + raise ValueError + self.__fname = fname + self.__lname = lname + sep = self.valid_date(birth_date) + if sep: + self._bd = '-'.join(birth_date.split(sep)) + else: + raise ValueError('Invalid date format') + self._ph = self.valid_phone(phone) + if not self.phone: + raise ValueError('Invalid phone format') + if not self.valid_document(document_type, document_id): + raise ValueError('Invalid document id') + if document_type not in {'паспорт', 'заграничный паспорт', 'водительское удостоверение'}: + raise ValueError('Invalid document type') + self._dt = document_type + self._did = document_id.replace(' ', '').replace('-', '').replace('/', '') + info_logger.info('Successfully added new patient') + + @property + def document_type(self): + return self._dt + + @document_type.setter + def document_type(self, val): + if not isinstance(val, str): + error_logger.error(f'{val} must be string') + raise TypeError + if val not in {'паспорт', 'заграничный паспорт', 'водительское удостоверение'}: + error_logger.error(f'{val}: invalid document type') + raise ValueError + info_logger.info(f'{self._dt} will be changed to {val}') + self._dt = val + + @property + def document_id(self): + return self._did + + @document_id.setter + def document_id(self, val): + if not isinstance(val, str): + error_logger.error(f'{val} must be string') + raise TypeError + if not self.valid_document(self._dt, val): + error_logger.error(f'{val} invalid document id') + raise ValueError('Wrong document') + info_logger.info(f'{self._did} will be changed to {val}') + self._did = val - def create(*args, **kwargs): - raise NotImplementedError() + @property + def first_name(self): + return self.__fname + + @first_name.setter + def first_name(self, val): + error_logger.error('First name assignment forbidden') + raise AttributeError('First name assignment forbidden') + + @property + def last_name(self): + return self.__lname + + @last_name.setter + def last_name(self, val): + error_logger.error('Last name assignment forbidden') + raise AttributeError('Last name assignment forbidden') + + @property + def birth_date(self): + return self._bd + + @birth_date.setter + def birth_date(self, val): + if not isinstance(val, str): + error_logger.error(f'{val} must be string') + raise TypeError('Invalid date type. Need str') + sep = self.valid_date(val) + if sep: + self._bd = '-'.join(val.split(sep)) + else: + error_logger.error(f'{val}: invalid date format') + raise ValueError('Invalid date format') + info_logger.info(f'{self._bd} will be changed to {val}') + + @property + def phone(self): + return self._ph + + @phone.setter + def phone(self, val): + if not isinstance(val, str): + error_logger.error(f'{val} must be string') + raise TypeError('Invalid phone type. Need str') + self._ph = self.valid_phone(val) + if not self._ph: + error_logger.error(f'{val}: invalid phone format') + raise ValueError('Invalid phone format') + info_logger.info(f'{self._ph} will be changed to {val}') + + def create(fname, lname, birth_date, phone, document_type, document_id): + return Patient(fname, lname, birth_date, phone, document_type, document_id) def save(self): - pass + try: + with open(csv_path, "a", newline='', encoding='utf-8') as csv_file: + writer = csv.writer(csv_file, delimiter='|') + writer.writerow([self.first_name, self.last_name, self.birth_date, self.phone, + self.document_type, self.document_id]) + except FileExistsError: + error_logger.error(f'Raise FileExistsError in save() with {self}') + except FileNotFoundError: + error_logger.error(f'Raise FileNotFoundError in save() with {self}') + except IsADirectoryError: + error_logger.error(f'Raise IsADirectoryError in save() with {self}') + except PermissionError: + error_logger.error(f'Raise PermissionError in save() with {self}') + else: + info_logger.info(f'patient {self} was successfully added to file') class PatientCollection: - def __init__(self, log_file): - pass + def __init__(self, data_file): + file = open(data_file, encoding='utf-8') + reader = csv.reader(file, delimiter='|') + self.data = [] + for row in reader: + self.data.append(Patient(*row)) def limit(self, n): - raise NotImplementedError() + counter = 0 + with open(csv_path, 'r', encoding='utf-8') as file: + while True: + line = file.readline() + if not line or counter == n: + break + yield Patient(*line.strip().split('|')) + counter += 1 + + def __iter__(self): + with open(csv_path, 'r', encoding='utf-8') as file: + while True: + line = file.readline() + if not line: + break + yield Patient(*line.strip().split('|')) \ No newline at end of file diff --git a/tests/test_patient.py b/tests/test_patient.py index 125bd59..a442599 100644 --- a/tests/test_patient.py +++ b/tests/test_patient.py @@ -31,13 +31,13 @@ def wrapper(*args, **kwargs): return deco -def setup(): +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(): +def teardown_module(__name__): for file in [GOOD_LOG_FILE, ERROR_LOG_FILE, CSV_PATH]: os.remove(file) @@ -180,7 +180,7 @@ def test_wrong_type_assignment(patient, field, param): )) @check_log_size("error", increased=True) @check_log_size("good") -def test_wrong_type_assignment(patient, field, param): +def test_wrong_value_assignment(patient, field, param): try: setattr(patient, field, param) assert False, f"ValueError for {field} assignment not invoked" diff --git a/tests/test_patient_collection.py b/tests/test_patient_collection.py index d1036e5..9536eff 100644 --- a/tests/test_patient_collection.py +++ b/tests/test_patient_collection.py @@ -7,17 +7,17 @@ from tests.constants import PATIENT_FIELDS GOOD_PARAMS = ( - ("Кондрат", "Рюрик", "1971-01-31", "79160000000", PASSPORT_TYPE, "0228 000000"), - ("Евпатий", "Коловрат", "1972-01-31", "79160000001", PASSPORT_TYPE, "0228 000001"), + ("Кондрат", "Рюрик", "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-31", "79160000003", PASSPORT_TYPE, "0228 000003"), - ("Евлампия", "Фамилия", "1999-01-31", "79160000004", PASSPORT_TYPE, "0228 000004"), - ("Кузя", "Кузьмин", "2000-01-31", "79160000005", PASSPORT_TYPE, "0228 000005"), - ("Гарри", "Поттер", "2020-01-31", "79160000006", PASSPORT_TYPE, "0228 000006"), - ("Рон", "Уизли", "1900-04-31", "79160000007", PASSPORT_TYPE, "0228 000007"), + ("Миртл", "Плакса", "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-31", "79160000010", PASSPORT_TYPE, "0228 000010"), + ("Вован", "ДеМорт", "1978-11-30", "79160000010", PASSPORT_TYPE, "0228 000010"), ("Гопник", "Районный", "1978-01-25", "79160000011", PASSPORT_TYPE, "0228 000011"), ("Фёдор", "Достоевский", "1978-01-05", "79160000012", PASSPORT_TYPE, "0228 000012"), ) From 63340421a2ef3b33d10de14737ecd865541efcd2 Mon Sep 17 00:00:00 2001 From: exeynod Date: Thu, 30 Apr 2020 12:06:41 +0300 Subject: [PATCH 2/3] =?UTF-8?q?=D0=9D=D0=B5=20=D1=81=D0=BE=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D0=BC=20=D0=BF=D0=BE=D0=BD=D1=8F=D0=BB=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BA=20=D0=B7=D0=B0=D0=BA=D1=80=D1=8B=D1=82=D1=8C=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=20=D0=B2=20=D1=81=D0=B2=D0=BE=D0=B5=D0=BC=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D0=B5,=20=D0=BF=D0=BE=D1=8D=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D1=83=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8B=D0=BB=D1=8C,=20=D1=87=D1=82=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=20=D0=B8=D0=B7=D0=B1=D0=B5=D0=B6=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=D0=B4=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=83=D0=BF=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_patient.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_patient.py b/tests/test_patient.py index a442599..f811c96 100644 --- a/tests/test_patient.py +++ b/tests/test_patient.py @@ -39,7 +39,10 @@ def setup_module(__main__): def teardown_module(__name__): for file in [GOOD_LOG_FILE, ERROR_LOG_FILE, CSV_PATH]: - os.remove(file) + try: + os.remove(file) + except: + print('Error access') @check_log_size("error") From 3fb767dc29f1a29513a1ed508c16ccd821901660 Mon Sep 17 00:00:00 2001 From: exeynod Date: Sat, 2 May 2020 11:56:05 +0300 Subject: [PATCH 3/3] Refactored code --- homework/patient.py | 250 ++++++++++++++++------------------------ homework/vald_values.py | 39 +++++++ 2 files changed, 139 insertions(+), 150 deletions(-) create mode 100644 homework/vald_values.py diff --git a/homework/patient.py b/homework/patient.py index b90d5fc..b214865 100644 --- a/homework/patient.py +++ b/homework/patient.py @@ -1,158 +1,115 @@ +from itertools import islice +import homework.vald_values as valid from homework.log import error_logger, info_logger import csv csv_path = 'data.csv' -class Patient: - @staticmethod - def valid_date(date): - import time - try: - time.strptime(date, '%Y-%m-%d') - res = '-' - except ValueError: - try: - time.strptime(date, '%Y.%m.%d') - res = '.' - except ValueError: - error_logger.error(f'{date}: invalid date format') - res = None - return res +class BasicDescriptor: + def __init__(self, name): + self.name = name - @staticmethod - def valid_phone(phone): - res = phone.replace('+', '').replace('-', '').replace('(', '').replace(')', '').replace(' ', '') - if len(res) == 11 and res[0] in {'7', '8'}: - res = res[1:] - return res - if len(res) == 10: - return res - error_logger.error(f'{phone} Invalid phone format') - return None + def __get__(self, instance, owner): + return instance.__dict__[self.name] + + def __set__(self, instance, value): + pass @staticmethod - def valid_document(document_type, document_id): - document = document_id.replace(' ', '').replace('-', '').replace('/', '') - if not document.isdigit(): - error_logger.error(f'{document_id}: invalid document id') - return False - if document_type in {'водительское удостоверение', 'паспорт'}: - return len(document) == 10 - if document_type == 'заграничный паспорт': - return len(document) == 9 - error_logger.error(f'{document_id}: invalid document id') - return False - - def __init__(self, fname, lname, birth_date, phone, document_type, document_id): - for string in (fname, lname, birth_date, phone, document_id, document_type): - if not isinstance(string, str): - error_logger.error(f'{string} must be string') - raise TypeError - for word in (fname, lname): - if not word.isalpha(): - error_logger.error(f'{word} must be an alpha') - raise ValueError - self.__fname = fname - self.__lname = lname - sep = self.valid_date(birth_date) - if sep: - self._bd = '-'.join(birth_date.split(sep)) - else: - raise ValueError('Invalid date format') - self._ph = self.valid_phone(phone) - if not self.phone: - raise ValueError('Invalid phone format') - if not self.valid_document(document_type, document_id): - raise ValueError('Invalid document id') - if document_type not in {'паспорт', 'заграничный паспорт', 'водительское удостоверение'}: - raise ValueError('Invalid document type') - self._dt = document_type - self._did = document_id.replace(' ', '').replace('-', '').replace('/', '') - info_logger.info('Successfully added new patient') - - @property - def document_type(self): - return self._dt - - @document_type.setter - def document_type(self, val): - if not isinstance(val, str): - error_logger.error(f'{val} must be string') - raise TypeError - if val not in {'паспорт', 'заграничный паспорт', 'водительское удостоверение'}: - error_logger.error(f'{val}: invalid document type') - raise ValueError - info_logger.info(f'{self._dt} will be changed to {val}') - self._dt = val - - @property - def document_id(self): - return self._did - - @document_id.setter - def document_id(self, val): - if not isinstance(val, str): - error_logger.error(f'{val} must be string') - raise TypeError - if not self.valid_document(self._dt, val): - error_logger.error(f'{val} invalid document id') - raise ValueError('Wrong document') - info_logger.info(f'{self._did} will be changed to {val}') - self._did = val - - @property - def first_name(self): - return self.__fname - - @first_name.setter - def first_name(self, val): - error_logger.error('First name assignment forbidden') - raise AttributeError('First name assignment forbidden') - - @property - def last_name(self): - return self.__lname - - @last_name.setter - def last_name(self, val): - error_logger.error('Last name assignment forbidden') - raise AttributeError('Last name assignment forbidden') - - @property - def birth_date(self): - return self._bd - - @birth_date.setter - def birth_date(self, val): + def check_type(val): if not isinstance(val, str): - error_logger.error(f'{val} must be string') - raise TypeError('Invalid date type. Need str') - sep = self.valid_date(val) - if sep: - self._bd = '-'.join(val.split(sep)) + s = f'{val} must be a string not {type(val)}' + error_logger.error(s) + raise TypeError(s) + + +class ImmutableData(BasicDescriptor): + def __set__(self, instance, value): + self.check_type(value) + if self.name not in instance.__dict__: + if value.isalpha(): + instance.__dict__[self.name] = value + else: + s = f'{value} must be an alpha' + error_logger.error(s) + raise ValueError(s) + else: + s = 'Name changes are forbidden' + error_logger.error(s) + raise AttributeError(s) + + +class MutableData(BasicDescriptor): + def __init__(self, name): + super().__init__(name) + if self.name == 'phone': + self.validate = valid.valid_phone + elif self.name == 'birth_date': + self.validate = valid.valid_date + elif self.name == 'document_type': + self.validate = valid.valid_document_type + else: + self.validate = valid.valid_document + + def __set__(self, instance, value): + self.check_type(value) + new_val = self.validate(value) + if new_val: + if self.name in instance.__dict__: + info_logger.info(f'{self.name} set to {new_val}') + instance.__dict__[self.name] = new_val else: - error_logger.error(f'{val}: invalid date format') - raise ValueError('Invalid date format') - info_logger.info(f'{self._bd} will be changed to {val}') + s = f'{value} invalid data format' + error_logger.error(s) + raise ValueError(s) + + +class DocDescriptor(BasicDescriptor): + def __set__(self, instance, value): + if self.name == "document_id": + self.check_type(value) + new_doc = valid.valid_document((instance.document_type, value)) + if new_doc: + if self.name in instance.__dict__: + info_logger.info(f'{self.name} set to {new_doc}') + instance.__dict__[self.name] = new_doc + else: + s = 'Invalid document id' + error_logger.error(s) + raise ValueError(s) + elif self.name == "document_type": + self.check_type(value) + new_type = valid.valid_document_type(value) + if new_type: + if self.name in instance.__dict__: + info_logger.info("Type was changed") + instance.__dict__[self.name] = value + else: + error_logger.error(f"Invalid document type: {value}") + raise ValueError("Invalid document type") - @property - def phone(self): - return self._ph - @phone.setter - def phone(self, val): - if not isinstance(val, str): - error_logger.error(f'{val} must be string') - raise TypeError('Invalid phone type. Need str') - self._ph = self.valid_phone(val) - if not self._ph: - error_logger.error(f'{val}: invalid phone format') - raise ValueError('Invalid phone format') - info_logger.info(f'{self._ph} will be changed to {val}') +class Patient: + first_name = ImmutableData('first_name') + last_name = ImmutableData('last_name') + birth_date = MutableData('birth_date') + phone = MutableData('phone') + document_id = DocDescriptor('document_id') + document_type = DocDescriptor('document_type') + + def __init__(self, first_name, last_name, birth_date, phone, document_type, document_id): + 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 + info_logger.info(f'{first_name} {last_name} patient created') - def create(fname, lname, birth_date, phone, document_type, document_id): - return Patient(fname, lname, 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) def save(self): try: @@ -181,14 +138,7 @@ def __init__(self, data_file): self.data.append(Patient(*row)) def limit(self, n): - counter = 0 - with open(csv_path, 'r', encoding='utf-8') as file: - while True: - line = file.readline() - if not line or counter == n: - break - yield Patient(*line.strip().split('|')) - counter += 1 + return islice(self, n) def __iter__(self): with open(csv_path, 'r', encoding='utf-8') as file: @@ -196,4 +146,4 @@ def __iter__(self): line = file.readline() if not line: break - yield Patient(*line.strip().split('|')) \ No newline at end of file + yield Patient(*line.strip().split('|')) diff --git a/homework/vald_values.py b/homework/vald_values.py new file mode 100644 index 0000000..8cb0611 --- /dev/null +++ b/homework/vald_values.py @@ -0,0 +1,39 @@ +def valid_date(date): + import time + try: + time.strptime(date, '%Y-%m-%d') + res = '-' + except ValueError: + try: + time.strptime(date, '%Y.%m.%d') + res = '.' + except ValueError: + return None + return '-'.join(date.split(res)) + + +def valid_phone(phone): + res = phone.replace('+', '').replace('-', '').replace('(', '').replace(')', '').replace(' ', '') + if len(res) == 11 and res[0] in {'7', '8'}: + res = res[1:] + return res + if len(res) == 10: + return res + return None + + +def valid_document(document): + document_id = document[1].replace(' ', '').replace('-', '').replace('/', '') + if not document_id.isdigit(): + return None + if document[0] in {'водительское удостоверение', 'паспорт'} and len(document_id) == 10: + return document_id + if document[0] == 'заграничный паспорт' and len(document_id) == 9: + return document_id + return None + + +def valid_document_type(document_type): + if document_type in {'водительское удостоверение', 'паспорт', 'заграничный паспорт'}: + return document_type + return None