Spaces:
Paused
Paused
| # database.py | |
| """ | |
| Модуль для работы с файловой базой данных (JSON) для хранения истории эмоций. | |
| ФИНАЛЬНАЯ ВЕРСИЯ: Решает проблему `PermissionError` на платформе | |
| Hugging Face Spaces, корректно обрабатывая права на примонтированную | |
| директорию `/data`. | |
| Принцип работы: | |
| 1. При запуске код проверяет, существует ли директория `/data` и доступна ли | |
| она для записи. | |
| 2. Если да, то база данных будет размещена в ней (постоянное хранилище). | |
| 3. Если нет (например, Persistent Storage не подключен), используется | |
| временная директория /tmp, чтобы избежать падения приложения. | |
| 4. В функции сохранения `save_emotion` более не делается попытка создать | |
| саму директорию `/data`, что и было причиной ошибки. | |
| """ | |
| import json | |
| import os | |
| import logging | |
| from datetime import datetime | |
| from typing import Any, Dict, List, Optional | |
| from filelock import FileLock | |
| logger = logging.getLogger(__name__) | |
| # --- Динамическая конфигурация путей к файлам --- | |
| DB_FILE = "history.json" | |
| LOCK_FILE = "history.json.lock" | |
| # Проверяем, запущено ли приложение на Hugging Face Spaces. | |
| if os.environ.get('SPACE_ID'): | |
| persistent_storage_path = "/data" | |
| # Проверяем, существует ли путь и есть ли у нас права на запись в него. | |
| if os.path.exists(persistent_storage_path) and os.access(persistent_storage_path, os.W_OK): | |
| # Если да, используем постоянное хранилище. | |
| DB_FILE = os.path.join(persistent_storage_path, "history.json") | |
| LOCK_FILE = "/tmp/history.json.lock" | |
| logger.info(f"Обнаружено доступное постоянное хранилище. База данных: {DB_FILE}") | |
| else: | |
| # Если нет, выводим предупреждение и используем временное хранилище. | |
| logger.warning("Постоянное хранилище в /data не найдено или недоступно. " | |
| "История будет теряться при перезапусках.") | |
| DB_FILE = "/tmp/history.json" | |
| LOCK_FILE = "/tmp/history.json.lock" | |
| def _read_data() -> Dict[str, Any]: | |
| """Вспомогательная функция для безопасного чтения данных из JSON-файла.""" | |
| if not os.path.exists(DB_FILE): | |
| return {} | |
| try: | |
| if os.path.getsize(DB_FILE) > 0: | |
| with open(DB_FILE, "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| except (json.JSONDecodeError, FileNotFoundError): | |
| return {} | |
| return {} | |
| def save_emotion(user_id: int, emotion: str) -> None: | |
| """Сохраняет запись об эмоции пользователя в базу данных.""" | |
| lock = FileLock(LOCK_FILE, timeout=10) | |
| with lock: | |
| data = _read_data() | |
| user_id_str = str(user_id) | |
| if user_id_str not in data: | |
| data[user_id_str] = [] | |
| record = { | |
| "emotion": emotion, | |
| "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), | |
| } | |
| data[user_id_str].append(record) | |
| MAX_HISTORY_PER_USER = 200 | |
| if len(data[user_id_str]) > MAX_HISTORY_PER_USER: | |
| data[user_id_str] = data[user_id_str][-MAX_HISTORY_PER_USER:] | |
| try: | |
| # ИСПРАВЛЕНИЕ: Мы больше не пытаемся создать саму директорию /data. | |
| # Наша логика при запуске уже подтвердила, что она существует | |
| # и доступна. Мы можем сразу открывать файл для записи. | |
| # Функция open() сама создаст файл history.json, если его нет. | |
| with open(DB_FILE, "w", encoding="utf-8") as f: | |
| json.dump(data, f, ensure_ascii=False, indent=4) | |
| except Exception as e: | |
| logger.error(f"КРИТИЧЕСКАЯ ОШИБКА: Не удалось сохранить данные в файл {DB_FILE}: {e}", exc_info=True) | |
| def get_user_history(user_id: int, limit: Optional[int] = 10) -> List[Dict[str, str]]: | |
| """Возвращает историю эмоций пользователя из базы данных.""" | |
| lock = FileLock(LOCK_FILE, timeout=10) | |
| with lock: | |
| data = _read_data() | |
| user_id_str = str(user_id) | |
| user_history = data.get(user_id_str, []) | |
| if limit is None: | |
| return user_history | |
| return user_history[-limit:] |