diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,5 +1,4 @@ - from flask import Flask, render_template_string, request, redirect, url_for, session, send_from_directory import json import os @@ -10,145 +9,142 @@ from datetime import datetime from huggingface_hub import HfApi, hf_hub_download from huggingface_hub.utils import RepositoryNotFoundError from werkzeug.utils import secure_filename -import shutil # Import shutil for file operations app = Flask(__name__) -app.secret_key = 'your_unique_secret_key_soola_cosmetics_7890' # Новый уникальный секретный ключ +app.secret_key = 'your_unique_secret_key_soola_cosmetics_7890' # Уникальный секретный ключ (изменен) DATA_FILE = 'data_soola.json' USERS_FILE = 'users_soola.json' -# CONFIG_FILE убран, так как курс фиксирован на KGS -# Список файлов для синхронизации (убрали CONFIG_FILE) +# Список файлов для синхронизации (CONFIG_FILE удален) SYNC_FILES = [DATA_FILE, USERS_FILE] # Настройки Hugging Face -REPO_ID = "Kgshop/Soola" # Оставляем старый? Или создать новый репозиторий? Уточнить. +REPO_ID = "Kgshop/Soola" # Убедитесь, что этот репозиторий существует или создайте его HF_TOKEN_WRITE = os.getenv("HF_TOKEN") -HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Может быть тот же, что и WRITE +HF_TOKEN_READ = os.getenv("HF_TOKEN_READ") # Адрес магазина (только один) -STORE_ADDRESS = "Рынок Дордой, Джунхай, терминал, 38" +STORE_ADDRESS = "Рынок Дордой , Джунхай , терминал , 38" -# Поддерживаемые валюты - теперь только KGS +# Валюта (только KGS) CURRENCY_CODE = 'KGS' CURRENCY_NAME = 'Кыргызский сом (с)' # Настройка логирования -logging.basicConfig(level=logging.INFO) # Можно изменить на DEBUG для подробного лога +logging.basicConfig(level=logging.INFO) # Изменено на INFO для меньшей детализации в продакшене -# --- Функции работы с данными и Hugging Face (без изменений в логике, кроме списка файлов) --- +# --- Функции работы с данными и пользователями --- def load_data(): - """Загружает данные товаров и категорий.""" - try: - # Попробуем скачать актуальные файлы перед чтением - download_db_from_hf() - except RepositoryNotFoundError: - logging.warning(f"Репозиторий {REPO_ID} не найден на Hugging Face. Используется локальная версия, если есть.") - except Exception as e: - logging.warning(f"Ошибка при скачивании файлов с Hugging Face: {e}. Используется локальная версия, если есть.") - + """Загрузка данных о товарах и категориях.""" try: + # Попытка скачать актуальные файлы перед загрузкой + download_files_from_hf() with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) logging.info(f"Данные успешно загружены из {DATA_FILE}") - # Проверка базовой структуры + # Проверка структуры данных if not isinstance(data, dict): - logging.warning(f"{DATA_FILE} не является словарем. Сброс к формату по умолчанию.") + logging.warning(f"Файл {DATA_FILE} имеет неверный формат (не словарь). Создание структуры по умолчанию.") return {'products': [], 'categories': []} - if 'products' not in data: + if 'products' not in data or not isinstance(data['products'], list): + logging.warning(f"Ключ 'products' отсутствует или не является списком в {DATA_FILE}. Инициализация пустым списком.") data['products'] = [] - logging.warning(f"Ключ 'products' отсутствовал в {DATA_FILE}. Добавлен пустой список.") - if 'categories' not in data: + if 'categories' not in data or not isinstance(data['categories'], list): + logging.warning(f"Ключ 'categories' отсутствует или не является списком в {DATA_FILE}. Инициализация пустым списком.") data['categories'] = [] - logging.warning(f"Ключ 'categories' отсутствовал в {DATA_FILE}. Добавлен пустой список.") return data except FileNotFoundError: - logging.warning(f"Локальный файл {DATA_FILE} не найден. Создание структуры по умолчанию.") + logging.warning(f"Локальный файл {DATA_FILE} не найден. Возвращена пустая структура.") return {'products': [], 'categories': []} except json.JSONDecodeError: - logging.error(f"Ошибка: Невозможно декодировать JSON из {DATA_FILE}. Возврат структуры по умолчанию.") - # Попытка создать резервную копию поврежденного файла - try: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - shutil.copyfile(DATA_FILE, f"{DATA_FILE}.corrupted_{timestamp}") - logging.info(f"Создана резервная копия поврежденного файла: {DATA_FILE}.corrupted_{timestamp}") - except Exception as copy_e: - logging.error(f"Не удалось создать резервную копию поврежденного файла {DATA_FILE}: {copy_e}") + logging.error(f"Ошибка: Невозможно декодировать JSON файл {DATA_FILE}. Возвращена пустая структура.") return {'products': [], 'categories': []} + except RepositoryNotFoundError: + logging.error("Репозиторий Hugging Face не найден. Используется локальная база данных (если есть) или создается пустая.") + # Попробуем загрузить локальный файл еще раз, если он вдруг есть + try: + with open(DATA_FILE, 'r', encoding='utf-8') as file: + data = json.load(file) + logging.info(f"Загружен локальный файл {DATA_FILE} после ошибки с репозиторием.") + # Повторная проверка структуры + if not isinstance(data, dict): data = {'products': [], 'categories': []} + if 'products' not in data or not isinstance(data['products'], list): data['products'] = [] + if 'categories' not in data or not isinstance(data['categories'], list): data['categories'] = [] + return data + except (FileNotFoundError, json.JSONDecodeError): + logging.warning(f"Локальный файл {DATA_FILE} также не найден или поврежден. Создание пустой структуры.") + return {'products': [], 'categories': []} except Exception as e: - logging.error(f"Непредвиденная ошибка при загрузке данных из {DATA_FILE}: {e}") + logging.error(f"Непредвиденная ошибка при загрузке данных: {e}") return {'products': [], 'categories': []} def save_data(data): - """Сохраняет данные товаров и категорий.""" + """Сохранение данных о товарах и категориях.""" try: with open(DATA_FILE, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) logging.info(f"Данные успешно сохранены в {DATA_FILE}") - # Загружаем на HF после локального сохранения - upload_db_to_hf() + upload_files_to_hf([DATA_FILE]) # Загружаем только измененный файл except Exception as e: - logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}") - # Не пробрасываем исключение дальше, чтобы приложение могло продолжить работу, - # но логируем ошибку. Важно отслеживать такие ошибки. + logging.error(f"Ошибка при сохранении данных: {e}") + # Не прерываем работу приложения, но логируем ошибку + # raise # Можно раскомментировать, если сохранение критично def load_users(): - """Загружает данные пользователей.""" - try: - # Попробуем скачать актуальные файлы перед чтением - download_db_from_hf() # Убедимся, что работаем с последней версией - except RepositoryNotFoundError: - logging.warning(f"Репозиторий {REPO_ID} не найден на Hugging Face. Используется локальная версия users_soola.json, если есть.") - except Exception as e: - logging.warning(f"Ошибка при скачивании users_soola.json с Hugging Face: {e}. Используется локальная версия, если есть.") - + """Загрузка данных пользователей.""" try: + # Попытка скачать актуальный файл перед загрузкой + download_files_from_hf([USERS_FILE]) with open(USERS_FILE, 'r', encoding='utf-8') as file: users = json.load(file) logging.info(f"Данные пользователей успешно загружены из {USERS_FILE}") if not isinstance(users, dict): - logging.warning(f"{USERS_FILE} не является словарем. Возвращается пустой словарь.") - return {} + logging.warning(f"Файл {USERS_FILE} имеет неверный формат (не словарь). Возвращен пустой словарь.") + return {} return users except FileNotFoundError: - logging.warning(f"Локальный файл {USERS_FILE} не найден. Возвращается пустой словарь.") + logging.warning(f"Локальный файл {USERS_FILE} не найден. Возвращен пустой словарь.") return {} except json.JSONDecodeError: - logging.error(f"Ошибка: Невозможно декодировать JSON из {USERS_FILE}. Возвращается пустой словарь.") - # Попытка создать резервную копию поврежденного файла - try: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - shutil.copyfile(USERS_FILE, f"{USERS_FILE}.corrupted_{timestamp}") - logging.info(f"Создана резервная копия поврежденного файла: {USERS_FILE}.corrupted_{timestamp}") - except Exception as copy_e: - logging.error(f"Не удалось создать резервную копию поврежденного файла {USERS_FILE}: {copy_e}") + logging.error(f"Ошибка: Невозможно декодировать JSON файл {USERS_FILE}. Возвращен пустой словарь.") return {} + except RepositoryNotFoundError: + logging.error("Репозиторий Hugging Face не найден при загрузке пользователей. Используется локальный файл (если есть).") + try: + with open(USERS_FILE, 'r', encoding='utf-8') as file: + users = json.load(file) + logging.info(f"Загружен локальный файл {USERS_FILE} после ошибки с репозиторием.") + if not isinstance(users, dict): return {} + return users + except (FileNotFoundError, json.JSONDecodeError): + logging.warning(f"Локальный файл {USERS_FILE} также не найден или поврежден. Возвращен пустой словарь.") + return {} except Exception as e: - logging.error(f"Непредвиденная ошибка при загрузке данных из {USERS_FILE}: {e}") + logging.error(f"Непредвиденная ошибка при загрузке пользователей: {e}") return {} def save_users(users): - """Сохраняет данные пользователей.""" + """Сохранение данных пользователей.""" try: with open(USERS_FILE, 'w', encoding='utf-8') as file: json.dump(users, file, ensure_ascii=False, indent=4) logging.info(f"Данные пользователей успешно сохранены в {USERS_FILE}") - # Загружаем на HF после локального сохранения - upload_db_to_hf() + upload_files_to_hf([USERS_FILE]) # Загружаем только измененный файл except Exception as e: - logging.error(f"Ошибка при сохранении данных пользователей в {USERS_FILE}: {e}") + logging.error(f"Ошибка при сохранении пользователей: {e}") + # Не прерываем работу приложения, но логируем ошибку +# --- Функции синхронизации с Hugging Face --- -def upload_db_to_hf(): - """Загружает файлы данных на Hugging Face.""" +def upload_files_to_hf(filenames=SYNC_FILES): + """Загрузка указанных файлов на Hugging Face.""" if not HF_TOKEN_WRITE: - logging.warning("HF_TOKEN (токен для записи) не установлен. Загрузка на Hugging Face отключена.") + logging.warning("HF_TOKEN (write) не установлен. Загрузка на Hugging Face отключена.") return try: api = HfApi() - logging.info(f"Попытка загрузки файлов {SYNC_FILES} в репозиторий {REPO_ID}...") - for file_name in SYNC_FILES: + for file_name in filenames: if os.path.exists(file_name): api.upload_file( path_or_fileobj=file_name, @@ -156,56 +152,64 @@ def upload_db_to_hf(): repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN_WRITE, - commit_message=f"Автоматическое резервное копирование {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" + commit_message=f"Автоматическое обновление файла {file_name} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) logging.info(f"Файл {file_name} успешно загружен на Hugging Face.") else: - logging.warning(f"Файл {file_name} не найден локально, пропуск загрузки.") + logging.warning(f"Файл {file_name} не найден локально для загрузки.") except Exception as e: logging.error(f"Ошибка при загрузке файлов на Hugging Face: {e}") -def download_db_from_hf(): - """Скачивает файлы данных с Hugging Face.""" +def download_files_from_hf(filenames=SYNC_FILES): + """Скачивание указанных файлов с Hugging Face.""" if not HF_TOKEN_READ: - logging.warning("HF_TOKEN_READ (токен для чтения) не установлен. Скачивание с Hugging Face отключено.") - # Не вызываем raise, чтобы приложение могло работать с локальными файлами, если они есть + logging.warning("HF_TOKEN_READ не установлен. Скачивание с Hugging Face отключено.") + # Важно: Если нет токена на чтение, нельзя просто продолжать, + # т.к. мы можем перезаписать свежие локальные данные старыми. + # Поэтому просто выходим, полагаясь на локальные файлы. return + try: - api = HfApi() # Можно и без создания объекта api, hf_hub_download сам его использует - logging.info(f"Попытка скачивания файлов {SYNC_FILES} из репозитория {REPO_ID}...") - for file_name in SYNC_FILES: + api = HfApi() # Api для скачивания не нужна, но оставим для консистентности + logging.info(f"Попытка скачивания файлов: {filenames}") + for file_name in filenames: try: hf_hub_download( repo_id=REPO_ID, filename=file_name, repo_type="dataset", token=HF_TOKEN_READ, - local_dir=".", # Скачиваем в текущую директорию - local_dir_use_symlinks=False, # Важно для большинства сред развертывания + local_dir=".", + local_dir_use_symlinks=False, # Важно для избежания проблем с путями force_download=True # Принудительно скачиваем, чтобы получить последнюю версию ) logging.info(f"Файл {file_name} успешно скачан из Hugging Face.") - except RepositoryNotFoundError as repo_e: - logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face: {repo_e}") - raise # Пробрасываем ошибку, если репозиторий не найден - except Exception as file_e: - # Логируем ошибку для конкретного файла, но продолжаем скачивать остальные - logging.error(f"Ошибка при скачивании файла {file_name}: {file_e}") + except RepositoryNotFoundError as e: + logging.error(f"Репозиторий '{REPO_ID}' не найден при попытке скачать {file_name}: {e}") + raise # Передаем ошибку выше, чтобы load_data/load_users обработали ее + except Exception as e: # Ловим другие возможные ошибки скачивания файла + if "404 Client Error" in str(e) and "Entry not found" in str(e): + logging.warning(f"Файл {file_name} не найден в репозитории {REPO_ID}. Пропускаем скачивание.") + else: + logging.error(f"Ошибка при скачивании файла {file_name}: {e}") + # Не прерываем скачивание других файлов, но логируем ошибку except RepositoryNotFoundError: - # Эта ошибка уже обработана выше, но на всякий случай ловим здесь тоже - raise + # Эта ошибка уже обработана в load_data/load_users, просто логируем здесь для полноты + logging.error(f"Репозиторий '{REPO_ID}' не найден. Скачивание файлов невозможно.") + raise # Передаем ошибку выше except Exception as e: - # Общая ошибка при попытке скачивания - logging.error(f"Общая ошибка при скачивании файлов с Hugging Face: {e}") - # Не пробрасываем ошибку дальше, чтобы приложение могло попытаться запуститься с локальными данными + logging.error(f"Общая ошибка при скачивании файлов: {e}") + # Не прерываем работу, но логируем def periodic_backup(): - """Периодически загружает данные на Hugging Face.""" + """Периодическая загрузка всех файлов на HF.""" + logging.info("Запуск потока периодического резервного копирования.") while True: - logging.info("Запуск периодического резервного копирования...") - upload_db_to_hf() - logging.info("Периодическое резервное копирование завершено. Следующий запуск через 800 секунд.") - time.sleep(800) # 13 минут 20 секунд + time.sleep(1800) # Увеличено до 30 минут (1800 секунд) + logging.info("Выполнение периодического резервного копирования...") + upload_files_to_hf(SYNC_FILES) + logging.info("Периодическое резервное копирование завершено.") + # --- Маршруты Flask --- @@ -223,296 +227,280 @@ def catalog():
-{{ product.get('description', '') }}
- - {% if is_authenticated %} - +{{ product.get('description', 'Нет описания') }}
{# Добавлено .get с default #} +Товары не найдены.
+ {% endif %}