Enhance application with new features, improved error handling, and performance optimizations. Key updates include: added data validation, retry strategies for HTTP requests, detailed logging, and support for RabbitMQ exports. Updated dependencies and enhanced README documentation for better setup instructions.
This commit is contained in:
@@ -8,7 +8,7 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from src.core.models import ProductVariant, LogRecordModel # [FIX] Импорт моделей
|
||||
from core.models import ProductVariant, LogRecordModel # [FIX] Импорт моделей
|
||||
|
||||
# [CONTRACT] DatabaseManager
|
||||
# @description: Контекстный менеджер для управления соединением с SQLite.
|
||||
@@ -146,33 +146,84 @@ def init_database(db_path: Path, run_id: str):
|
||||
# - `data` должен быть списком словарей, каждый из которых соответствует ProductVariant.
|
||||
# - `db_path` должен указывать на существующую и инициализированную БД.
|
||||
# @post: Данные из `data` вставлены в таблицу `products`.
|
||||
def save_data_to_db(data: List[Dict], db_path: Path, run_id: str):
|
||||
def save_data_to_db(data: List[Dict], db_path: Path, run_id: str) -> bool:
|
||||
"""
|
||||
[ENHANCED] Сохраняет данные в базу данных с улучшенной обработкой ошибок.
|
||||
|
||||
Args:
|
||||
data: Список словарей с данными для сохранения
|
||||
db_path: Путь к файлу базы данных
|
||||
run_id: Идентификатор запуска для логирования
|
||||
|
||||
Returns:
|
||||
bool: True если сохранение прошло успешно, False в противном случае
|
||||
"""
|
||||
log_prefix = f"save_data_to_db(id={run_id})"
|
||||
|
||||
# [ENHANCEMENT] Валидация входных данных
|
||||
if not data:
|
||||
logging.warning(f"{log_prefix} - [CONTRACT_VIOLATION] Данные для сохранения отсутствуют. Пропуск сохранения.")
|
||||
return
|
||||
return False
|
||||
|
||||
if not isinstance(data, list):
|
||||
logging.error(f"{log_prefix} - [TYPE_ERROR] Данные должны быть списком, получено: {type(data)}")
|
||||
return False
|
||||
|
||||
logging.info(f"{log_prefix} - Начало сохранения {len(data)} записей в БД: {db_path}")
|
||||
|
||||
# [PRECONDITION] Проверка формата данных (хотя ProductVariant.model_dump() должен гарантировать)
|
||||
if not all(isinstance(item, dict) and all(k in item for k in ['name', 'volume', 'price']) for item in data):
|
||||
required_fields = ['name', 'volume', 'price']
|
||||
if not all(isinstance(item, dict) and all(k in item for k in required_fields) for item in data):
|
||||
logging.error(f"{log_prefix} - [CONTRACT_VIOLATION] Некорректный формат данных для сохранения в БД.", extra={"sample_data": data[:1]})
|
||||
raise ValueError("Данные для сохранения в БД не соответствуют ожидаемому формату ProductVariant.")
|
||||
return False
|
||||
|
||||
try:
|
||||
# [ENHANCEMENT] Проверка существования файла БД
|
||||
if not db_path.exists():
|
||||
logging.warning(f"{log_prefix} - Файл БД не существует: {db_path}")
|
||||
return False
|
||||
|
||||
# [CONTEXT_MANAGER] Используем with-statement для безопасного соединения и коммита
|
||||
with sqlite3.connect(db_path) as con:
|
||||
cur = con.cursor()
|
||||
products_to_insert = []
|
||||
for item in data:
|
||||
# Преобразование к int и обработка возможных ошибок приведения типа
|
||||
skipped_count = 0
|
||||
|
||||
for i, item in enumerate(data):
|
||||
# [ENHANCEMENT] Детальная валидация каждого элемента
|
||||
try:
|
||||
price_int = int(item['price'])
|
||||
except (ValueError, TypeError) as e:
|
||||
logging.error(f"{log_prefix} - [DATA_CLEANUP_FAILED] Некорректное значение цены для '{item.get('name')}': {item.get('price')}. Пропуск записи. Ошибка: {e}")
|
||||
# [COHERENCE_CHECK_FAILED] Данные не соответствуют схеме
|
||||
continue # Пропускаем эту запись, но продолжаем для остальных
|
||||
products_to_insert.append(
|
||||
(run_id, item['name'], item['volume'], price_int)
|
||||
)
|
||||
# Проверка типов данных
|
||||
if not isinstance(item['name'], str) or not item['name'].strip():
|
||||
logging.warning(f"{log_prefix} - [INVALID_NAME] Элемент {i}: некорректное имя '{item.get('name')}'")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
if not isinstance(item['volume'], str):
|
||||
logging.warning(f"{log_prefix} - [INVALID_VOLUME] Элемент {i}: некорректный объем '{item.get('volume')}'")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
# Преобразование к int и обработка возможных ошибок приведения типа
|
||||
try:
|
||||
price_int = int(item['price'])
|
||||
if price_int <= 0:
|
||||
logging.warning(f"{log_prefix} - [INVALID_PRICE] Элемент {i}: некорректная цена {price_int}")
|
||||
skipped_count += 1
|
||||
continue
|
||||
except (ValueError, TypeError) as e:
|
||||
logging.error(f"{log_prefix} - [DATA_CLEANUP_FAILED] Некорректное значение цены для '{item.get('name')}': {item.get('price')}. Пропуск записи. Ошибка: {e}")
|
||||
skipped_count += 1
|
||||
continue # Пропускаем эту запись, но продолжаем для остальных
|
||||
|
||||
products_to_insert.append(
|
||||
(run_id, item['name'], item['volume'], price_int)
|
||||
)
|
||||
|
||||
except KeyError as e:
|
||||
logging.error(f"{log_prefix} - [MISSING_FIELD] Элемент {i} не содержит обязательное поле: {e}")
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
if products_to_insert:
|
||||
cur.executemany(
|
||||
"INSERT INTO products (run_id, name, volume, price) VALUES (?, ?, ?, ?)",
|
||||
@@ -180,85 +231,21 @@ def save_data_to_db(data: List[Dict], db_path: Path, run_id: str):
|
||||
)
|
||||
con.commit()
|
||||
logging.info(f"{log_prefix} - [COHERENCE_CHECK_PASSED] {len(products_to_insert)} записей успешно сохранено в базу данных.")
|
||||
if skipped_count > 0:
|
||||
logging.warning(f"{log_prefix} - Пропущено {skipped_count} некорректных записей.")
|
||||
return True
|
||||
else:
|
||||
logging.warning(f"{log_prefix} - После фильтрации не осталось валидных записей для сохранения.")
|
||||
return False
|
||||
|
||||
except sqlite3.Error as e:
|
||||
logging.error(f"{log_prefix} - [COHERENCE_CHECK_FAILED] Ошибка SQLite при сохранении данных: {e}", exc_info=True)
|
||||
raise ConnectionError(f"Ошибка БД при сохранении: {e}") from e
|
||||
return False
|
||||
except PermissionError as e:
|
||||
logging.error(f"{log_prefix} - [PERMISSION_ERROR] Нет прав на запись в БД {db_path}: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.critical(f"{log_prefix} - [CRITICAL] Непредвиденная ошибка при сохранении данных в БД: {e}", exc_info=True)
|
||||
raise
|
||||
return False
|
||||
|
||||
# [CONTRACT] save_data_to_db
|
||||
# @description: Сохраняет список объектов ProductVariant (представленных как словари) в таблицу `products`.
|
||||
# @pre:
|
||||
# - `data` должен быть списком словарей, каждый из которых соответствует ProductVariant.
|
||||
# - `db_path` должен указывать на существующую и инициализированную БД.
|
||||
# @post: Данные из `data` вставлены в таблицу `products`.
|
||||
def save_data_to_db(data: List[Dict], db_path: Path, run_id: str):
|
||||
log_prefix = f"save_data_to_db(id={run_id})"
|
||||
if not data:
|
||||
logging.warning(f"{log_prefix} - [CONTRACT_VIOLATION] Данные для сохранения отсутствуют. Пропуск сохранения.")
|
||||
return
|
||||
logging.info(f"{log_prefix} - Начало сохранения {len(data)} записей в БД: {db_path}")
|
||||
# [PRECONDITION] Проверка формата данных (хотя ProductVariant.model_dump() должен гарантировать)
|
||||
if not all(isinstance(item, dict) and all(k in item for k in ['name', 'volume', 'price']) for item in data):
|
||||
logging.error(f"{log_prefix} - [CONTRACT_VIOLATION] Некорректный формат данных для сохранения в БД.", extra={"sample_data": data[:1]})
|
||||
raise ValueError("Данные для сохранения в БД не соответствуют ожидаемому формату ProductVariant.")
|
||||
|
||||
try:
|
||||
# [CONTEXT_MANAGER] Используем with-statement для безопасного соединения и коммита
|
||||
with sqlite3.connect(db_path) as con:
|
||||
cur = con.cursor()
|
||||
products_to_insert = []
|
||||
for item in data:
|
||||
# Преобразование к int и обработка возможных ошибок приведения типа
|
||||
try:
|
||||
price_int = int(item['price'])
|
||||
except (ValueError, TypeError) as e:
|
||||
logging.error(f"{log_prefix} - [DATA_CLEANUP_FAILED] Некорректное значение цены для '{item.get('name')}': {item.get('price')}. Пропуск записи. Ошибка: {e}")
|
||||
# [COHERENCE_CHECK_FAILED] Данные не соответствуют схеме
|
||||
continue # Пропускаем эту запись, но продолжаем для остальных
|
||||
products_to_insert.append(
|
||||
(run_id, item['name'], item['volume'], price_int)
|
||||
)
|
||||
if products_to_insert:
|
||||
cur.executemany(
|
||||
"INSERT INTO products (run_id, name, volume, price) VALUES (?, ?, ?, ?)",
|
||||
products_to_insert
|
||||
)
|
||||
con.commit()
|
||||
logging.info(f"{log_prefix} - [COHERENCE_CHECK_PASSED] {len(products_to_insert)} записей успешно сохранено в базу данных.")
|
||||
else:
|
||||
logging.warning(f"{log_prefix} - После фильтрации не осталось валидных записей для сохранения.")
|
||||
|
||||
except sqlite3.Error as e:
|
||||
logging.error(f"{log_prefix} - [COHERENCE_CHECK_FAILED] Ошибка SQLite при сохранении данных: {e}", exc_info=True)
|
||||
raise ConnectionError(f"Ошибка БД при сохранении: {e}") from e
|
||||
except Exception as e:
|
||||
logging.critical(f"{log_prefix} - [CRITICAL] Непредвиденная ошибка при сохранении данных в БД: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def save_data_to_db(data: List[Dict], db_path: Path, run_id: str):
|
||||
# ... (код функции save_data_to_db без изменений) ...
|
||||
log_prefix = f"save_data_to_db(id={run_id})"
|
||||
if not data:
|
||||
logging.warning(f"{log_prefix} - [CONTRACT_VIOLATION] Данные для сохранения отсутствуют.")
|
||||
return
|
||||
logging.info(f"{log_prefix} - Начало сохранения {len(data)} записей в БД: {db_path}")
|
||||
try:
|
||||
con = sqlite3.connect(db_path)
|
||||
cur = con.cursor()
|
||||
products_to_insert = [
|
||||
(run_id, item['name'], item['volume'], int(item['price'])) for item in data
|
||||
]
|
||||
cur.executemany(
|
||||
"INSERT INTO products (run_id, name, volume, price) VALUES (?, ?, ?, ?)",
|
||||
products_to_insert
|
||||
)
|
||||
con.commit()
|
||||
con.close()
|
||||
logging.info(f"{log_prefix} - [COHERENCE_CHECK_PASSED] Данные успешно сохранены в базу данных.")
|
||||
except Exception as e:
|
||||
logging.error(f"{log_prefix} - [COHERENCE_CHECK_FAILED] Ошибка при сохранении в БД: {e}")
|
||||
# [REFACTORING_COMPLETE] Дублированные функции удалены, улучшена обработка ошибок
|
||||
Reference in New Issue
Block a user