diff --git a/homework/cli.py b/homework/cli.py new file mode 100644 index 0000000..a480649 --- /dev/null +++ b/homework/cli.py @@ -0,0 +1,106 @@ +from patient import Patient, PatientCollection +import click +import sqlite3 + + +class OptionEatAll(click.Option): + + def __init__(self, *args, **kwargs): + self.save_other_options = kwargs.pop('save_other_options', True) + nargs = kwargs.pop('nargs', -1) + assert nargs == -1, 'nargs, if set, must be -1 not {}'.format(nargs) + super(OptionEatAll, self).__init__(*args, **kwargs) + self._previous_parser_process = None + self._eat_all_parser = None + + def add_to_parser(self, parser, ctx): + + def parser_process(value, state): + # method to hook to the parser.process + done = False + value = [value] + if self.save_other_options: + # grab everything up to the next option + while state.rargs and not done: + for prefix in self._eat_all_parser.prefixes: + if state.rargs[0].startswith(prefix): + done = True + if not done: + value.append(state.rargs.pop(0)) + else: + # grab everything remaining + value += state.rargs + state.rargs[:] = [] + value = tuple(value) + + # call the actual process + self._previous_parser_process(value, state) + + retval = super(OptionEatAll, self).add_to_parser(parser, ctx) + for name in self.opts: + our_parser = parser._long_opt.get(name) or parser._short_opt.get(name) + if our_parser: + self._eat_all_parser = our_parser + self._previous_parser_process = our_parser.process + our_parser.process = parser_process + break + return retval + + +@click.group() +def cli(): + pass + + +@click.command() +@click.argument('f_name') +@click.argument('s_name') +@click.option('--birth-date', cls=OptionEatAll, type= str) +@click.option('--phone', cls=OptionEatAll, type=str) +@click.option('--document-type', cls=OptionEatAll, type=str) +@click.option('--document-number', cls=OptionEatAll, type=str) +def create(f_name, s_name, birth_date, phone, document_type, document_number): + birth_date = ''.join(birth_date) + phone = ''.join(phone) + document_type = ' '.join(document_type) + document_number = ''.join(document_number) + patient = Patient(f_name, s_name, birth_date, phone, document_type, document_number) + patient.save() + del patient + + +@click.command() +@click.argument('limit', default=10, type=int) +def show(limit): + collection = PatientCollection('covid_19_db.db') + for i in collection.limit(limit): + print(i) + + +@click.command() +def count(): + collection = PatientCollection('covid_19_db.db') + for i in collection.limit(None): + print(i) + + +cli.add_command(create) +cli.add_command(show) +cli.add_command(count) + +if __name__ == '__main__': + conn = sqlite3.connect('covid_19_db.db') + cursor = conn.cursor() + cursor.execute("""CREATE TABLE IF NOT EXISTS covid_members + (first_name TEXT not null, + last_name TEXT not null, + birth_date TEXT not null, + phone TEXT not null, + document_type TEXT not null, + document_id TEXT not null UNIQUE + );""") + + conn.commit() + cursor.close() + conn.close() + cli() diff --git a/homework/config.py b/homework/config.py index 955b991..f841177 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 = "success.log" +ERROR_LOG_FILE = "errors.log" +CSV_PATH = "covid_19_db.db" 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/logg_cvd19.py b/homework/logg_cvd19.py new file mode 100644 index 0000000..1a87041 --- /dev/null +++ b/homework/logg_cvd19.py @@ -0,0 +1,68 @@ +import logging +import sqlite3 + +logger_s = logging.getLogger("covid_19_success") +logger_s.setLevel(logging.INFO) +logger_e = logging.getLogger("covid_19_errors") +logger_e.setLevel(logging.ERROR) + +formatter = logging.Formatter("%(filename)s[LINE:%(lineno)d]# %(levelname)-8s [%(asctime)s] %(message)s") + +handler_success = logging.FileHandler('success.log', 'a', 'utf-8') +handler_errors = logging.FileHandler('errors.log', 'a', 'utf-8') +handler_success.setFormatter(formatter) +handler_errors.setFormatter(formatter) +logger_s.addHandler(handler_success) +logger_e.addHandler(handler_errors) + + +def decorated_log(func): + def wrapper(*args, **kwargs): + key = None + if func.__name__ == '__init__': + key = 'init' + if func.__name__ == 'save': + key = 'save' + try: + result = func(*args, **kwargs) + if result: + return result + except AttributeError: + logger_e.error('F_name or L_name can not be changed') + raise AttributeError("F_name or L_name can not be changed") + except TypeError: + logger_e.error('Ошибка с типом данных') + raise TypeError("Ошибка с типом данных") + except UnicodeError: + logger_e.error('Something wrong with your decoder in SAVE') + raise UnicodeError('Something wrong with your decoder in SAVE') + except ValueError: + logger_e.error('Ошибка с типом данных') + raise ValueError("Ошибка с данными") + except sqlite3.IntegrityError: + logger_e.error('Данные с таким документом уже существуют') + raise sqlite3.IntegrityError("Данные с таким документом уже существуют") + except sqlite3.OperationalError: + logger_e.error('Проблемы с подключением к базе') + raise sqlite3.OperationalError("Проблемы с подключением к базе") + except sqlite3.DatabaseError: + logger_e.error('Ошибка внутри базы') + raise sqlite3.DatabaseError("Ошибка внутри базы") + except IsADirectoryError: + logger_e.error('Cant write in directory. Problem in SAVE') + raise IsADirectoryError('Cant write in directory. Problem in SAVE') + except PermissionError: + logger_e.error('U cant write in this file. Problem in SAVE') + raise PermissionError('U cant write in this file. Problem in SAVE') + except OSError: + logger_e.error('Some System Error. Problem in SAVE') + raise OSError('Some System Error. Problem in SAVE') + except RuntimeError: + logger_e.error('Something unexpected. Problem in SAVE') + raise RuntimeError('Something unexpected. Problem in SAVE') + else: + if key == 'init': + logger_s.info('Пациент создан') + if key == 'save': + logger_s.info('Пациент сохранен') + return wrapper diff --git a/homework/model_cvd19.py b/homework/model_cvd19.py new file mode 100644 index 0000000..cd4d489 --- /dev/null +++ b/homework/model_cvd19.py @@ -0,0 +1,122 @@ +from abc import ABCMeta +from logg_cvd19 import decorated_log + + +# TODO Do not logg every time creation of patient +class AutoStorageDescriptor: + def __set_name__(self, owner, name): + self.name = name + + def __set__(self, instance, value): + instance.__dict__[self.name] = value + + def __get__(self, instance, owner): + if instance is None: + return self + else: + return instance.__dict__[self.name] + + +class Validated(AutoStorageDescriptor): + def __set__(self, instance, value): + value = self.validate(instance, value) + super(Validated, self).__set__(instance, value) + + @classmethod + def validate(cls, instance, value): + raise NotImplementedError("Validate method is not implemented") + + +class NameValidator(Validated): + @decorated_log + def validate(self, instance, value): + if not isinstance(value, str): + raise TypeError("Wrong type for f_name or l_name") + if value.isalpha(): + return value.capitalize() + else: + raise ValueError("Wrong value for f_name or s_name") + + +class NonBlank(NameValidator): + @decorated_log + def validate(self, instance, value): + if self.name in instance.__dict__ and getattr(instance, self.name): + raise AttributeError("F_name or L_name can not be changed") + return super(NonBlank, self).validate(instance, value) + + +class RenameOther(Validated): + @decorated_log + def validate(self, instance, value): + if self.name in instance.__dict__ and getattr(instance, self.name): + instance.logger_s.info(f"{self.name} было изменено") + return value + return value + + +class PhoneValidator(RenameOther): + white_lst = [str(i) for i in range(0, 10)] + + @decorated_log + def validate(self, instance, value): + if not isinstance(value, str): + raise TypeError("Wrong type for Phone") + for symbol in value: + if symbol not in self.white_lst: + value = value.replace(symbol, '') + if value.find('8') == 0: + value = value.replace(value[0], '7', 1) + if len(value) != 11: + raise ValueError("Wrong attribute phone") + return RenameOther.validate(self, instance, value) + + +class BirthValidator(RenameOther): + @decorated_log + def validate(self, instance, value): + if not isinstance(value, str): + raise TypeError("Wrong type for birthday") + for symbol in value: + if symbol.isalpha(): + raise ValueError("Wrong value for birthday") + if str(symbol) == ' ' or symbol == '-': + value = value.replace(symbol, '.') + birth_lst = value.split('.') + value = f'{birth_lst[0].zfill(4)}-' \ + f'{birth_lst[1].zfill(2)}-' \ + f'{birth_lst[2].zfill(2)}' + return RenameOther.validate(self, instance, value) + + +class DocTypeValidator(RenameOther): + white_lst = ['Паспорт', 'Заграничный паспорт', 'Водительские права'] + + @decorated_log + def validate(self, instance, value): + if not isinstance(value, str): + raise TypeError("Wrong type for doc_type") + else: + if value.capitalize() in self.white_lst: + value = value.capitalize() + return RenameOther.validate(self, instance, value) + else: + raise ValueError("Wrong doc_type value") + + +class DocIDValidator(RenameOther): + doc_dict = {'Паспорт': 10, 'Водительские права': 10, 'Заграничный паспорт': 9} + white_lst = [str(i) for i in range(0, 10)] + + @decorated_log + def validate(self, instance, value): + if not isinstance(value, str): + raise TypeError("Wrong type for doc_id") + for symbol in value: + if symbol not in self.white_lst: + value = value.replace(symbol, '') + if len(value) == self.doc_dict[getattr(instance, 'document_type')]: + return RenameOther.validate(self, instance, value) + else: + raise ValueError("Wrong doc_id value") + diff --git a/homework/patient.py b/homework/patient.py index dad2526..bf14fff 100644 --- a/homework/patient.py +++ b/homework/patient.py @@ -1,17 +1,87 @@ -class Patient: - def __init__(self, *args, **kwargs): - pass +import csv +import logging +import model_cvd19 as model +from logg_cvd19 import logger_s, logger_e, handler_errors, handler_success, decorated_log +import sqlite3 - def create(*args, **kwargs): - raise NotImplementedError() +FILENAME = 'covid_19_db.db' + +class Patient(object): + first_name = model.NonBlank() + last_name = model.NonBlank() + birth_date = model.BirthValidator() + phone = model.PhoneValidator() + document_type = model.DocTypeValidator() + document_id = model.DocIDValidator() + + @decorated_log + def __init__(self, first_name, last_name, birth_date, phone, document_type, document_id): + self.logger_s = logging.getLogger('covid_19_success') + self.logger_e = logging.getLogger("covid_19_errors") + + if first_name and last_name and birth_date and phone and document_type and 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 + + def __str__(self): + return f'{self.first_name} {self.last_name} {self.birth_date} {self.phone} {self.document_type} ' \ + f'{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) + + @decorated_log def save(self): - pass + conn = sqlite3.connect('covid_19_db.db') + cursor = conn.cursor() + + data = [self.first_name, self.last_name, self.birth_date, self.phone, self.document_type, self.document_id] + cursor.execute("INSERT INTO covid_members VALUES (?, ?, ?, ?, ?, ?)", data) + + conn.commit() + cursor.close() + conn.close() + + def __del__(self): + handler_errors.close() + handler_success.close() + + +class PatientCollection(object): + def __init__(self, path): + self.path = path + self.id = 1 + self.count = None + self.conn = sqlite3.connect(self.path) + self.cursor = self.conn.cursor() + def __iter__(self): + return self -class PatientCollection: - def __init__(self, log_file): - pass + @decorated_log + def __next__(self): + if self.count: + if self.id == self.count+1: + self.cursor.close() + self.conn.close() + raise StopIteration + read_line = self.cursor.execute(f"SELECT first_name, last_name, birth_date, phone, document_type, document_id" + f" FROM covid_members WHERE ROWID = {self.id}") + data = list(read_line) + data = list(*data) + if not data: + self.cursor.close() + self.conn.close() + raise StopIteration + self.id += 1 + return Patient(*data) - def limit(self, n): - raise NotImplementedError() + def limit(self, count): + self.count = count + return self.__iter__() diff --git a/tests/test_patient.py b/tests/test_patient.py index 125bd59..19287a4 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" @@ -192,4 +192,4 @@ def test_wrong_type_assignment(patient, field, param): @check_log_size("csv", increased=True) def test_save(): patient = Patient(*GOOD_PARAMS) - patient.save() + patient.save() \ No newline at end of file diff --git a/tests/test_patient_collection.py b/tests/test_patient_collection.py index 9536eff..0099d1f 100644 --- a/tests/test_patient_collection.py +++ b/tests/test_patient_collection.py @@ -75,4 +75,4 @@ def test_limit_remove_records(): limit = collection.limit(4) with open(CSV_PATH, 'w', encoding='utf-8') as f: f.write('') - assert len([_ for _ in limit]) == 0, "Limit works wrong for empty file" + assert len([_ for _ in limit]) == 0, "Limit works wrong for empty file" \ No newline at end of file