diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,5 +1,5 @@ -from flask import Flask, render_template_string, request, redirect, url_for, session, send_from_directory +from flask import Flask, render_template_string, request, redirect, url_for, session, send_file import json import os import logging @@ -11,211 +11,185 @@ from huggingface_hub.utils import RepositoryNotFoundError from werkzeug.utils import secure_filename app = Flask(__name__) -app.secret_key = 'your_unique_secret_key_soola_cosmetics_7890' # Уникальный секретный ключ (изменен) -DATA_FILE = 'data_soola.json' -USERS_FILE = 'users_soola.json' +app.secret_key = 'your_unique_secret_key_12345_cosmetics' # Уникальный секретный ключ +DATA_FILE = 'data_soola_cosmetics.json' +USERS_FILE = 'users_soola_cosmetics.json' -# Список файлов для синхронизации (CONFIG_FILE удален) +# Список файлов для синхронизации (config.json убран) 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") -# Адрес магазина (только один) +# Адрес магазина (теперь один) STORE_ADDRESS = "Рынок Дордой , Джунхай , терминал , 38" -# Валюта (только KGS) +# Единственная валюта CURRENCY_CODE = 'KGS' CURRENCY_NAME = 'Кыргызский сом (с)' # Настройка логирования -logging.basicConfig(level=logging.INFO) # Изменено на INFO для меньшей детализации в продакшене +logging.basicConfig(level=logging.DEBUG) -# --- Функции работы с данными и пользователями --- +# --- Функции для работы с данными и пользователями --- def load_data(): """Загрузка данных о товарах и категориях.""" try: # Попытка скачать актуальные файлы перед загрузкой - download_files_from_hf() + download_db_from_hf() with open(DATA_FILE, 'r', encoding='utf-8') as file: data = json.load(file) - logging.info(f"Данные успешно загружены из {DATA_FILE}") + logging.info("Данные успешно загружены из JSON") # Проверка структуры данных - if not isinstance(data, dict): - logging.warning(f"Файл {DATA_FILE} имеет неверный формат (не словарь). Создание структуры по умолчанию.") - return {'products': [], 'categories': []} - if 'products' not in data or not isinstance(data['products'], list): - logging.warning(f"Ключ 'products' отсутствует или не является списком в {DATA_FILE}. Инициализация пустым списком.") - data['products'] = [] - if 'categories' not in data or not isinstance(data['categories'], list): - logging.warning(f"Ключ 'categories' отсутствует или не является списком в {DATA_FILE}. Инициализация пустым списком.") - data['categories'] = [] + if not isinstance(data, dict) or 'products' not in data or 'categories' not in data: + logging.warning("Структура данных некорректна, инициализация пустыми списками.") + # Если data это список (старый формат), пытаемся сохранить его как категории + return {'products': [], 'categories': [] if not isinstance(data, list) else data} return data except FileNotFoundError: - logging.warning(f"Локальный файл {DATA_FILE} не найден. Возвращена пустая структура.") - return {'products': [], 'categories': []} + logging.warning(f"Локальный файл {DATA_FILE} не найден. Попытка скачивания с HF.") + # Повторная попытка скачать, если первая была до создания файла + try: + download_db_from_hf() + # Если скачивание успешно, пробуем загрузить снова + with open(DATA_FILE, 'r', encoding='utf-8') as file: + data = json.load(file) + logging.info("Данные успешно загружены из JSON после скачивания.") + if not isinstance(data, dict) or 'products' not in data or 'categories' not in data: + return {'products': [], 'categories': [] if not isinstance(data, list) else data} + return data + except (RepositoryNotFoundError, FileNotFoundError, Exception) as e: + logging.error(f"Не удалось скачать или найти файл {DATA_FILE} после ошибки. Создание локальной базы данных. Ошибка: {e}") + return {'products': [], 'categories': []} # Инициализация пустой структурой except json.JSONDecodeError: - logging.error(f"Ошибка: Невозможно декодировать JSON файл {DATA_FILE}. Возвращена пустая структура.") + 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': []} + logging.error("Репозиторий Hugging Face не найден. Создание локальной базы данных.") + return {'products': [], 'categories': []} except Exception as e: - logging.error(f"Непредвиденная ошибка при загрузке данных: {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}") - upload_files_to_hf([DATA_FILE]) # Загружаем только измененный файл + upload_db_to_hf() # Синхронизация после сохранения except Exception as e: - logging.error(f"Ошибка при сохранении данных: {e}") - # Не прерываем работу приложения, но логируем ошибку - # raise # Можно раскомментировать, если сохранение критично + logging.error(f"Ошибка при сохранении данных в {DATA_FILE}: {e}") + # Не пробрасываем исключение дальше, чтобы приложение не падало, + # но логируем ошибку. В идеале, нужна стратегия обработки таких ошибок. + def load_users(): """Загрузка данных пользователей.""" try: - # Попытка скачать актуальный файл перед загрузкой - download_files_from_hf([USERS_FILE]) + # Попытка скачать актуальный файл пользователей + # download_db_from_hf() # Не будем скачивать каждый раз при загрузке юзеров, только при старте и по запросу 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 {} - return users + return users if isinstance(users, dict) else {} except FileNotFoundError: - logging.warning(f"Локальный файл {USERS_FILE} не найден. Возвращен пустой словарь.") + logging.warning(f"Локальный файл пользователей {USERS_FILE} не найден. Возвращаем пустой словарь.") return {} except json.JSONDecodeError: - logging.error(f"Ошибка: Невозможно декодировать JSON файл {USERS_FILE}. Возвращен пустой словарь.") + 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"Непредвиденная ошибка при загрузке пользователей: {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}") - upload_files_to_hf([USERS_FILE]) # Загружаем только измененный файл + upload_db_to_hf() # Синхронизация после сохранения except Exception as e: - logging.error(f"Ошибка при сохранении пользователей: {e}") - # Не прерываем работу приложения, но логируем ошибку + logging.error(f"Ошибка при сохранении пользователей в {USERS_FILE}: {e}") + # --- Функции синхронизации с Hugging Face --- -def upload_files_to_hf(filenames=SYNC_FILES): - """Загрузка указанных файлов на Hugging Face.""" +def upload_db_to_hf(): + """Загрузка файлов данных на Hugging Face.""" if not HF_TOKEN_WRITE: - logging.warning("HF_TOKEN (write) не установлен. Загрузка на Hugging Face отключена.") + logging.warning("Переменная окружения HF_TOKEN (WRITE) не установлена. Загрузка на Hugging Face отключена.") return try: api = HfApi() - for file_name in filenames: + for file_name in SYNC_FILES: if os.path.exists(file_name): + logging.info(f"Начинается загрузка файла {file_name} на Hugging Face...") api.upload_file( path_or_fileobj=file_name, path_in_repo=file_name, 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.") + logging.info(f"Резервная копия {file_name} успешно загружена на Hugging Face.") else: - logging.warning(f"Файл {file_name} не найден локально для загрузки.") + logging.warning(f"Файл {file_name} не найден для загрузки на Hugging Face.") except Exception as e: - logging.error(f"Ошибка при загрузке файлов на Hugging Face: {e}") + logging.error(f"Ошибка при загрузке резервной копии на Hugging Face: {e}") -def download_files_from_hf(filenames=SYNC_FILES): - """Скачивание указанных файлов с Hugging Face.""" +def download_db_from_hf(): + """Скачивание файлов данных с Hugging Face.""" if not HF_TOKEN_READ: - logging.warning("HF_TOKEN_READ не установлен. Скачивание с Hugging Face отключено.") - # Важно: Если нет токена на чтение, нельзя просто продолжать, - # т.к. мы можем перезаписать свежие локальные данные старыми. - # Поэтому просто выходим, полагаясь на локальные файлы. - return + logging.warning("Переменная окружения HF_TOKEN_READ не установлена. Скачивание с Hugging Face может быть недоступно для приватных репозиториев.") + # Можно продолжать без токена для публичных репо + # return # Раскомментировать, если чтение без токена не нужно + + logging.info("Попытка скачивания файлов с Hugging Face...") + api = HfApi() # Создаем объект API здесь + for file_name in SYNC_FILES: + try: + hf_hub_download( + repo_id=REPO_ID, + filename=file_name, + repo_type="dataset", + token=HF_TOKEN_READ, # Может быть None, если HF_TOKEN_READ не установлен + local_dir=".", + local_dir_use_symlinks=False, + force_download=True, # Принудительно скачиваем для обновления + resume_download=False # Не возобновляем, скачиваем заново + ) + logging.info(f"Файл {file_name} успешно скачан/обновлен из Hugging Face.") + except RepositoryNotFoundError: + logging.error(f"Репозиторий {REPO_ID} не найден на Hugging Face.") + # Не прерываем цикл, пытаемся скачать другие файлы + continue # Переходим к следующему файлу + except Exception as e: + # Логируем ошибку для конкретного файла, но не падаем + logging.error(f"Ошибка при скачивании файла {file_name} с Hugging Face: {e}. Пропускаем.") + # Если файл не скачался, будем использовать локальную версию (если есть) - try: - 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, # Важно для избежания проблем с путями - force_download=True # Принудительно скачиваем, чтобы получить последнюю версию - ) - logging.info(f"Файл {file_name} успешно скачан из Hugging Face.") - 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: - # Эта ошибка уже обработана в load_data/load_users, просто логируем здесь для полноты - logging.error(f"Репозиторий '{REPO_ID}' не найден. Скачивание файлов невозможно.") - raise # Передаем ошибку выше - except Exception as e: - logging.error(f"Общая ошибка при скачи��ании файлов: {e}") - # Не прерываем работу, но логируем def periodic_backup(): - """Периодическая загрузка всех файлов на HF.""" - logging.info("Запуск потока периодического резервного копирования.") + """Периодическая загрузка данных на Hugging Face.""" while True: - time.sleep(1800) # Увеличено до 30 минут (1800 секунд) - logging.info("Выполнение периодического резервного копирования...") - upload_files_to_hf(SYNC_FILES) + logging.info("Запуск периодического резервного копирования...") + upload_db_to_hf() logging.info("Периодическое резервное копирование завершено.") - + time.sleep(1800) # Увеличил интервал до 30 минут (1800 секунд) # --- Маршруты Flask --- @app.route('/') def catalog(): - """Главная страница каталога.""" + """Главная страница каталога товаров.""" data = load_data() products = data.get('products', []) categories = data.get('categories', []) @@ -227,137 +201,237 @@ def catalog():
-{{ product.get('description', 'Нет описания') }}
{# Добавлено .get с default #} -Товары не найдены.
+ +{{ product['description'] }}
+ + +Товары пока не добавлены.
{% endif %}