Refactor project structure and add new features
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Модуль context_managers.py содержит контекстные менеджеры для управления ресурсами.
|
||||
"""
|
||||
|
||||
import os
|
||||
import contextlib
|
||||
from typing import Generator, BinaryIO
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('app.context_managers')
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def open_file(file_path: str, mode: str = 'rb') -> Generator[BinaryIO, None, None]:
|
||||
"""
|
||||
Контекстный менеджер для безопасного открытия и закрытия файлов.
|
||||
|
||||
Args:
|
||||
file_path: Путь к файлу.
|
||||
mode: Режим открытия файла.
|
||||
|
||||
Yields:
|
||||
Файловый объект.
|
||||
"""
|
||||
file_obj = None
|
||||
try:
|
||||
file_obj = open(file_path, mode)
|
||||
yield file_obj
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при работе с файлом {file_path}: {e}")
|
||||
raise
|
||||
finally:
|
||||
if file_obj:
|
||||
file_obj.close()
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def audio_file(file_path: str) -> Generator[BinaryIO, None, None]:
|
||||
"""
|
||||
Контекстный менеджер для работы с аудиофайлами.
|
||||
|
||||
Args:
|
||||
file_path: Путь к аудиофайлу.
|
||||
|
||||
Yields:
|
||||
Файловый объект в бинарном режиме.
|
||||
"""
|
||||
with open_file(file_path, 'rb') as f:
|
||||
yield f
|
||||
@@ -0,0 +1,48 @@
|
||||
"""
|
||||
Модуль decorators.py содержит общие декораторы для использования в приложении.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import functools
|
||||
from flask import request
|
||||
|
||||
# Получаем логгер из централизованной настройки
|
||||
logger = logging.getLogger('app.utils')
|
||||
|
||||
|
||||
def log_invalid_file_request(func):
|
||||
"""
|
||||
Декоратор для логирования запросов с невалидными файлами.
|
||||
|
||||
Args:
|
||||
func: Декорируемая функция.
|
||||
|
||||
Returns:
|
||||
Обернутая функция с логированием ошибок валидации файлов.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
# Получение информации о запросе
|
||||
client_ip = request.environ.get('HTTP_X_FORWARDED_FOR', request.environ.get('REMOTE_ADDR', 'unknown'))
|
||||
endpoint = request.endpoint or 'unknown'
|
||||
method = request.method or 'unknown'
|
||||
|
||||
# Получение имени файла из запроса
|
||||
filename = 'unknown'
|
||||
if 'file' in request.files:
|
||||
filename = request.files['file'].filename
|
||||
elif request.is_json:
|
||||
data = request.get_json()
|
||||
if data and 'file' in data:
|
||||
filename = data.get('filename', 'base64_data')
|
||||
|
||||
# Логирование обращения к API с невалидным файлом
|
||||
logger.warning(f"Обращение к эндпоинту {method} {endpoint} с невалидным файлом '{filename}' "
|
||||
f"от клиента {client_ip}. Ошибка: {str(e)}")
|
||||
|
||||
# Пробрасываем исключение дальше
|
||||
raise
|
||||
return wrapper
|
||||
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Модуль history_logger.py содержит класс HistoryLogger для журналирования результатов
|
||||
транскрибации.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
import random
|
||||
import string
|
||||
from typing import Dict, Any, Optional
|
||||
import logging
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('app.history')
|
||||
|
||||
class HistoryLogger:
|
||||
"""Класс для сохранения истории транскрибации."""
|
||||
|
||||
def __init__(self, config: Dict):
|
||||
"""
|
||||
Инициализация логгера истории.
|
||||
|
||||
Args:
|
||||
config: Словарь с конфигурацией.
|
||||
"""
|
||||
self.config = config
|
||||
self.history_enabled = config.get("enable_history", False)
|
||||
self.history_root = os.path.join(os.path.dirname(os.path.dirname(__file__)), "history")
|
||||
|
||||
# Создаем корневую директорию истории, если она не существует
|
||||
if self.history_enabled and not os.path.exists(self.history_root):
|
||||
os.makedirs(self.history_root)
|
||||
logger.info(f"Создана директория для истории транскрибации: {self.history_root}")
|
||||
|
||||
def save(self, result: Dict[str, Any], original_filename: str) -> Optional[str]:
|
||||
"""
|
||||
Сохраняет результат транскрибации в файл истории.
|
||||
|
||||
Args:
|
||||
result: Результат транскрибации.
|
||||
original_filename: Исходное имя аудиофайла.
|
||||
|
||||
Returns:
|
||||
Путь к сохраненному файлу истории или None, если сохранение отключено.
|
||||
"""
|
||||
if not self.history_enabled:
|
||||
logger.debug("История транскрибации отключена в конфигурации")
|
||||
return None
|
||||
|
||||
try:
|
||||
# Получаем текущую дату и время
|
||||
now = datetime.datetime.now()
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
|
||||
# Получаем текущий таймстамп в миллисекундах
|
||||
timestamp_ms = int(datetime.datetime.now().timestamp() * 1000)
|
||||
|
||||
# Генерируем 4-символьную случайную метку
|
||||
random_tag = ''.join(random.choices(string.ascii_lowercase + string.digits, k=4))
|
||||
|
||||
# Получаем только имя файла без пути
|
||||
base_filename = os.path.basename(original_filename)
|
||||
|
||||
# Создаем имя файла истории
|
||||
history_filename = f"{timestamp_ms}_{base_filename}_{random_tag}.json"
|
||||
|
||||
# Путь к директории для текущей даты
|
||||
date_dir = os.path.join(self.history_root, date_str)
|
||||
|
||||
# Создаем директорию для текущей даты, если она не существует
|
||||
if not os.path.exists(date_dir):
|
||||
os.makedirs(date_dir)
|
||||
|
||||
# Полный путь к файлу истории
|
||||
history_path = os.path.join(date_dir, history_filename)
|
||||
|
||||
# Сохраняем результат в JSON файл
|
||||
with open(history_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"Результат транскрибации сохранен в историю: {history_path}")
|
||||
return history_path
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при сохранении истории транскрибации: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user