diff --git a/Dockerfile b/Dockerfile index 6500f43..ada9dd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,4 @@ COPY ./requirements.txt /code/requirements.txt COPY ./app /code/app RUN python3 -m pip install --upgrade pip -RUN echo Y | python3 -m pip install --no-cache-dir --upgrade -r /code/requirements.txt - -CMD ["python", "main.py"] \ No newline at end of file +RUN echo Y | python3 -m pip install --no-cache-dir --upgrade -r /code/requirements.txt \ No newline at end of file diff --git a/app/error_to_log.py b/app/error_to_log.py new file mode 100644 index 0000000..dc5c8b8 --- /dev/null +++ b/app/error_to_log.py @@ -0,0 +1,28 @@ +import datetime + +LOG_FILE = "errors.log" + +def current_datatime(): + return datetime.datetime.now().strftime("[%d-%m-%Y %H:%M:%S]") + +def write_to_log(error_message, log_file=LOG_FILE): + """ + Записывает ошибку в лог-файл с временной меткой + """ + print(f"Ошибка записана в {log_file} {error_message}") + with open(log_file, 'w', encoding='utf8') as f: + f.write(f"[{current_datatime()}] {error_message}\n") + +def read_to_log(log_file=LOG_FILE): + try: + with open(log_file, 'r', encoding='utf8') as f: + for line in f.read(): + print(line, end='') + except FileNotFoundError: print(f"{LOG_FILE} не существует") + +def clear_to_log(log_file=LOG_FILE): + """ + Очищает лог-файл и записывает новую шапку + """ + with open(log_file, 'w+', encoding='utf-8') as f: + f.write(f'=== Лог ошибок создан в {current_datatime()} ===\n\n') \ No newline at end of file diff --git a/app/main.py b/app/main.py index bf2f26d..9f4344f 100644 --- a/app/main.py +++ b/app/main.py @@ -2,9 +2,9 @@ import working_database import parse_saby from validation import DataValid import scheduler -import write_error_to_log - -print("Запуск main.py") +import app.error_to_log as error_to_log +from datetime import datetime +import sys def parse_data_in_list(dict_data: dict) -> list: """ @@ -21,16 +21,34 @@ def parse_data_in_list(dict_data: dict) -> list: result.append([key_id, *value_data]) return result -print("Очиcтка лог файла") -write_error_to_log.clear_to_log() +def main(): + start_time = datetime.now() + print("Очиcтка лог файла") + error_to_log.clear_to_log() -print("1/5. Запуск парсинга сайта") -result_dict_data = parse_saby.process_reports_parse() -print('2/5. Предварительный парсинг') -list_data = parse_data_in_list(result_dict_data) -print("3/5. Запуск валидации") -list_data_validated = DataValid.validate_data(list_data) -print("4/5 Отправка данных в БД") -working_database.SimpleDB().data_transfer_in_database(list_data_validated) -print("5/5. Запуск планировщика") -scheduler.launch_the_scheduler() + print("1/5. Запуск парсинга сайта") + result_dict_data = parse_saby.process_reports_parse() + print('2/5. Предварительный парсинг') + list_data = parse_data_in_list(result_dict_data) + print("3/5. Запуск валидации") + list_data_validated = DataValid.validate_data(list_data) + print("4/5. Отправка данных в БД") + working_database.WorkingDB().data_transfer_in_database(list_data_validated) + print("5/5. Вывод записей из лог файла") + + diff = datetime.now() - start_time + print(str(diff).split('.')[0]) + +if __name__ == "__main__": + print("Запуск main.py") + if sys.argv[1] == "--scheduler-run": + print("=== ЗАПУСК ПЛАНИРОВЩИКОМ ===") + main() + elif sys.argv[1] == "--first-run": + print("=== ПЕРВЫЙ ЗАПУСК ===") + main() + print("=== АКТИВАЦИЯ ПЛАНИРОВЩИКА ===") + scheduler.launch_the_scheduler() + else: + print("=== ОБЫЧНЫЙ ЗАПУСК ===") + main() \ No newline at end of file diff --git a/app/parse_saby.py b/app/parse_saby.py index 7069530..f053a28 100644 --- a/app/parse_saby.py +++ b/app/parse_saby.py @@ -5,7 +5,7 @@ import requests from bs4 import BeautifulSoup import re -import write_error_to_log +import app.error_to_log as error_to_log def parse_html(url: str): """ diff --git a/app/validation.py b/app/validation.py index 69da214..8d553eb 100644 --- a/app/validation.py +++ b/app/validation.py @@ -43,7 +43,7 @@ class DataValid(BaseModel): """ Функция принимает список списков, выполняет парсинг и валидацию с пмошью класса DataValid из файла validation.py. - Возвращает распршеный валидный список словарей для БД. + Возвращает распаршеный валидный список словарей для БД. """ result = [] for intem_list in list_data: diff --git a/app/working_database.py b/app/working_database.py index e3bb5b0..218d7d1 100644 --- a/app/working_database.py +++ b/app/working_database.py @@ -2,51 +2,59 @@ from os import environ import hvac import hvac.exceptions from oracledb import Error, create_pool, init_oracle_client -import write_error_to_log +import error_to_log init_oracle_client() -class SimpleDB: +class WorkingDB: def __init__(self): + print('-'*60) + print("Инициализация WorkingDB") self._create_db_pool_from_vault() - self.pool + self.pool + print('-'*60) def _handle_vault_exception(self, e: Exception, message: str): """ Обработка исключений Vault с возвратом понятного сообщения. """ - print(message) + error_to_log.write_to_log(message) + ind = {' '*4} + if isinstance(e, hvac.exceptions.InvalidPath): - raise hvac.exceptions.InvalidPath("Database configuration not found in Vault") + raise hvac.exceptions.InvalidPath(f"{ind}Database configuration not found in Vault") elif isinstance(e, hvac.exceptions.Forbidden): - raise hvac.exceptions.Forbidden("Permission denied to access Vault secrets") + raise hvac.exceptions.Forbidden(f"{ind}Permission denied to access Vault secrets") elif isinstance(e, hvac.exceptions.Unauthorized): - raise hvac.exceptions.Unauthorized("Invalid Vault token") + raise hvac.exceptions.Unauthorized(f"{ind}Invalid Vault token") elif isinstance(e, hvac.exceptions.VaultError): - raise hvac.exceptions.VaultError(f"Vault secret retrieval failed: {e}") + raise hvac.exceptions.VaultError(f"{ind}Vault secret retrieval failed: {e}") elif isinstance(e, hvac.exceptions.InvalidRequest): - raise hvac.exceptions.InvalidRequest(f"Missing database parameter in Vault response: {e}") + raise hvac.exceptions.InvalidRequest(f"{ind}Missing database parameter in Vault response: {e}") elif isinstance(e, hvac.exceptions.VaultDown): - raise hvac.exceptions.VaultDown(f"Database server not available: {e}") + raise hvac.exceptions.VaultDown(f"{ind}Database server not available: {e}") else: - raise Exception (f'Unexpected error reading from Vault: {e}') + raise Exception (f'{ind}Unexpected error reading from Vault: {e}') def _create_db_pool_from_vault(self): """ Подключение к Vault и создание пула соеденения. """ try: - # Подключение к Vault + # Подключение к Vault + print(f"{' '*2}Подключение к Vault") client = hvac.Client( url='https://vlt.dataekb.ru:8222', token=environ.get('VAULT_TOKEN'), ) except Exception as e: - self._handle_vault_exception(e, "Ошибка при создание покдлючения c Vault") + self._handle_vault_exception(e, f"{' '*4}Ошибка при создание покдлючения c Vault") - # Проверка авторизации в Vault + # Проверка авторизации в Vault + print(f"{' '*4}Проверка авторизации") if not client.is_authenticated(): - raise Exception("Vault authentication failed") + raise Exception(f"{' '*6}Не удалось автозоваться в Vault") + else: print(f"{' '*6}Успешно") try: # Чтение секретов из Vault @@ -55,7 +63,7 @@ class SimpleDB: mount_point='kv' ) except Exception as e: - self._handle_vault_exception(e, "Ошибка чтение скретов из Vault") + self._handle_vault_exception(e, f"{' '*4}Ошибка чтение скретов из Vault") try: # Создаем пул соединений @@ -68,7 +76,7 @@ class SimpleDB: increment=1 ) except Exception as e: - self._handle_vault_exception(e, "Ошибка при создание пула для подключение к Oracle") + self._handle_vault_exception(e, f"{' '*2}Ошибка при создание пула для подключение к Oracle") def data_transfer_in_database(self, list_data: list): """ @@ -86,14 +94,17 @@ class SimpleDB: """ # Данные для БД не могут быть пустыми if not list_data: - raise ValueError(list_data, "No data to process") + raise ValueError(list_data, "Данные для БД не могут быть пустыми") try: + print(f"{' '*2}Отправка данных в бд") with self.pool.acquire() as connection: with connection.cursor() as cursor: for dict_argument_bd in list_data: - print("Отправляемые аргрументы: ", dict_argument_bd) - print("Типы данных: ", *map(type, dict_argument_bd.values())) + # Просмотр отпавлямых арогументов + for key, value in dict_argument_bd.items(): + print(f"{' '*4}Отправляемые аргументы - {key}: {value} -> {type(value)}") + try: cursor.callproc('P_RK_GOVERNMENT_REPORTS_INSERS', [ dict_argument_bd['id'], @@ -104,16 +115,16 @@ class SimpleDB: dict_argument_bd['ver'], ]) except Error as e: - print(e) # нужно убрать, проверка работат ли. # Проверка является ли запись дублирующей if 'ORA-00001' in str(e): continue # В остальных случаях запись ошибки и пропуск данных. else: error_message = f"ERROR_DB-WRITE: {e} DATA: {dict_argument_bd}" # Запись логов - write_error_to_log.write_to_log(error_message) - continue + error_to_log.write_to_log(error_message) + continue + except Exception as e: error_message = f"ERROR_DB-GLOBAL: {e}" - write_error_to_log.write_to_log(error_message) + error_to_log.write_to_log(error_message) raise Error(f'Неожиданная ошибка: {e}') diff --git a/app/write_error_to_log.py b/app/write_error_to_log.py deleted file mode 100644 index f492e63..0000000 --- a/app/write_error_to_log.py +++ /dev/null @@ -1,19 +0,0 @@ -import datetime - -TIMESTAMP = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") -LOG_FILE = "error_log.txt" - -def write_to_log(error_message, log_file=LOG_FILE): - """ - Записывает ошибку в лог-файл с временной меткой - """ - print("Сообщение ошибки перед записю в файл: ", error_message) # Отладочная информация - with open(log_file, 'a', encoding='utf-8') as f: - f.write(f"[{TIMESTAMP}] {error_message}\n") - -def clear_to_log(log_file=LOG_FILE): - """ - Очищает лог-файл и записывает новую шапку - """ - with open(log_file, 'w', encoding='utf-8') as f: - f.write(f'=== Лог ошибок создан в {TIMESTAMP} ===\n\n') \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 07201aa..dfc9cae 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,9 +2,8 @@ version: "3.8" services: parse_saby: - # image: git.dataekb.ru/sadikov/parse_saby/parse_saby_main:latest - container_name: parse_saby build: . + command: python ./main.py --first-run volumes: - "/etc/timezone:/etc/timezone:ro" - "/etc/localtime:/etc/localtime:ro"