0xArctic commited on
Commit
16bccf2
·
verified ·
1 Parent(s): b41b3d0
Files changed (1) hide show
  1. database.py +50 -52
database.py CHANGED
@@ -3,54 +3,66 @@
3
  """
4
  Модуль для работы с файловой базой данных (JSON) для хранения истории эмоций.
5
 
6
- Ключевые особенности:
7
- - Адаптация под Hugging Face Spaces: пути к файлу БД и файлу блокировки
8
- разделены для решения проблем с правами доступа.
9
- - Отказоустойчивость: код автоматически создает необходимую директорию для
10
- базы данных при первом запуске, предотвращая `FileNotFoundError`.
11
- - Потокобезопасность: используется `filelock` для предотвращения гонки
12
- состояний при одновременной записи.
 
 
 
 
 
13
  """
14
  import json
15
  import os
 
16
  from datetime import datetime
17
  from typing import Any, Dict, List, Optional
18
  from filelock import FileLock
19
 
20
- # --- Конфигурация путей к файлам ---
 
 
 
 
 
21
 
22
  # Проверяем, запущено ли приложение на Hugging Face Spaces.
23
  if os.environ.get('SPACE_ID'):
24
- # На сервере HF Spaces:
25
- # 1. Файл базы данных (постоянный) храним в /data для персистентности.
26
- DB_FILE = "/data/history.json"
27
- # 2. Файл блокировки (временный) храним в /tmp, где всегда есть права на запись.
28
- LOCK_FILE = "/tmp/history.json.lock"
29
- else:
30
- # При локальном запуске оба файла находятся в текущей директории.
31
- DB_FILE = "history.json"
32
- LOCK_FILE = "history.json.lock"
 
 
 
 
 
33
 
34
 
35
  def _read_data() -> Dict[str, Any]:
36
- """
37
- Вспомогательная функция для безопасного чтения данных из JSON-файла.
38
- Возвращает пустой словарь, если файл не существует, пуст или поврежден.
39
- """
40
- if not os.path.exists(DB_FILE) or os.path.getsize(DB_FILE) == 0:
41
  return {}
42
  try:
43
- with open(DB_FILE, "r", encoding="utf-8") as f:
44
- return json.load(f)
 
45
  except (json.JSONDecodeError, FileNotFoundError):
46
  return {}
 
47
 
48
 
49
  def save_emotion(user_id: int, emotion: str) -> None:
50
- """
51
- Сохраняет запись об эмоции пользовател�� в базу данных.
52
- Ограничивает максимальный размер истории для одного пользователя.
53
- """
54
  lock = FileLock(LOCK_FILE, timeout=10)
55
  with lock:
56
  data = _read_data()
@@ -65,37 +77,23 @@ def save_emotion(user_id: int, emotion: str) -> None:
65
  }
66
  data[user_id_str].append(record)
67
 
68
- # Ограничиваем историю, чтобы файл не разрастался бесконечно.
69
  MAX_HISTORY_PER_USER = 200
70
  if len(data[user_id_str]) > MAX_HISTORY_PER_USER:
71
  data[user_id_str] = data[user_id_str][-MAX_HISTORY_PER_USER:]
72
 
73
- # --- ИСПРАВЛЕНИЕ: Гарантируем, что директория для файла БД существует ---
74
- # `open()` в режиме 'w' не создает родительские директории,
75
- # что приводит к FileNotFoundError при первом запуске на "чистой" системе.
76
- db_directory = os.path.dirname(DB_FILE)
77
- # os.makedirs создаст директорию, только если ее нет (благодаря exist_ok=True).
78
- # Проверяем, что путь к директории не пустой (для локального запуска).
79
- if db_directory:
80
- os.makedirs(db_directory, exist_ok=True)
81
- # --- КОНЕЦ ИСПРАВЛЕНИЯ ---
82
-
83
- with open(DB_FILE, "w", encoding="utf-8") as f:
84
- json.dump(data, f, ensure_ascii=False, indent=4)
85
 
86
 
87
  def get_user_history(user_id: int, limit: Optional[int] = 10) -> List[Dict[str, str]]:
88
- """
89
- Возвращает историю эмоций пользователя из базы данных.
90
-
91
- Args:
92
- user_id: ID пользователя в Telegram.
93
- limit: Максимальное количество последних записей. Если None,
94
- возвращается вся история пользователя.
95
-
96
- Returns:
97
- Список словарей с записями об эмоциях.
98
- """
99
  lock = FileLock(LOCK_FILE, timeout=10)
100
  with lock:
101
  data = _read_data()
 
3
  """
4
  Модуль для работы с файловой базой данных (JSON) для хранения истории эмоций.
5
 
6
+ ФИНАЛЬНАЯ ВЕРСИЯ: Решает проблему `PermissionError` на платформе
7
+ Hugging Face Spaces, корректно обрабатывая права на примонтированную
8
+ директорию `/data`.
9
+
10
+ Принцип работы:
11
+ 1. При запуске код проверяет, существует ли директория `/data` и доступна ли
12
+ она для записи.
13
+ 2. Если да, то база данных будет размещена в ней (постоянное хранилище).
14
+ 3. Если нет (например, Persistent Storage не подключен), используется
15
+ временная директория /tmp, чтобы избежать падения приложения.
16
+ 4. В функции сохранения `save_emotion` более не делается попытка создать
17
+ саму директорию `/data`, что и было причиной ошибки.
18
  """
19
  import json
20
  import os
21
+ import logging
22
  from datetime import datetime
23
  from typing import Any, Dict, List, Optional
24
  from filelock import FileLock
25
 
26
+ logger = logging.getLogger(__name__)
27
+
28
+ # --- Динамическая конфигурация путей к файлам ---
29
+
30
+ DB_FILE = "history.json"
31
+ LOCK_FILE = "history.json.lock"
32
 
33
  # Проверяем, запущено ли приложение на Hugging Face Spaces.
34
  if os.environ.get('SPACE_ID'):
35
+ persistent_storage_path = "/data"
36
+
37
+ # Проверяем, существует ли путь и есть ли у нас права на запись в него.
38
+ if os.path.exists(persistent_storage_path) and os.access(persistent_storage_path, os.W_OK):
39
+ # Если да, используем постоянное хранилище.
40
+ DB_FILE = os.path.join(persistent_storage_path, "history.json")
41
+ LOCK_FILE = "/tmp/history.json.lock"
42
+ logger.info(f"Обнаружено доступное постоянное хранилище. База данных: {DB_FILE}")
43
+ else:
44
+ # Если нет, выводим предупреждение и используем временное хранилище.
45
+ logger.warning("Постоянное хранилище в /data не найдено или недоступно. "
46
+ "История будет теряться при перезапусках.")
47
+ DB_FILE = "/tmp/history.json"
48
+ LOCK_FILE = "/tmp/history.json.lock"
49
 
50
 
51
  def _read_data() -> Dict[str, Any]:
52
+ """Вспомогательная функция для безопасного чтения данных из JSON-файла."""
53
+ if not os.path.exists(DB_FILE):
 
 
 
54
  return {}
55
  try:
56
+ if os.path.getsize(DB_FILE) > 0:
57
+ with open(DB_FILE, "r", encoding="utf-8") as f:
58
+ return json.load(f)
59
  except (json.JSONDecodeError, FileNotFoundError):
60
  return {}
61
+ return {}
62
 
63
 
64
  def save_emotion(user_id: int, emotion: str) -> None:
65
+ """Сохраняет запись об эмоции пользователя в базу данных."""
 
 
 
66
  lock = FileLock(LOCK_FILE, timeout=10)
67
  with lock:
68
  data = _read_data()
 
77
  }
78
  data[user_id_str].append(record)
79
 
 
80
  MAX_HISTORY_PER_USER = 200
81
  if len(data[user_id_str]) > MAX_HISTORY_PER_USER:
82
  data[user_id_str] = data[user_id_str][-MAX_HISTORY_PER_USER:]
83
 
84
+ try:
85
+ # ИСПРАВЛЕНИЕ: Мы больше не пытаемся создать саму директорию /data.
86
+ # Наша логика при запуске уже подтвердила, что она существует
87
+ # и доступна. Мы можем сразу открывать файл для записи.
88
+ # Функция open() сама создаст файл history.json, если его нет.
89
+ with open(DB_FILE, "w", encoding="utf-8") as f:
90
+ json.dump(data, f, ensure_ascii=False, indent=4)
91
+ except Exception as e:
92
+ logger.error(f"КРИТИЧЕСКАЯ ОШИБКА: Не удалось сохранить данные в файл {DB_FILE}: {e}", exc_info=True)
 
 
 
93
 
94
 
95
  def get_user_history(user_id: int, limit: Optional[int] = 10) -> List[Dict[str, str]]:
96
+ """Возвращает историю эмоций пользователя из базы данных."""
 
 
 
 
 
 
 
 
 
 
97
  lock = FileLock(LOCK_FILE, timeout=10)
98
  with lock:
99
  data = _read_data()