Админка осталась отдельной ветвью admin-panel; в master её нет (ни кода, ни страниц, ни переменных окружения, ни тома настроек). Что перенесено: - parsing: имена групп вида «ИВТ-ТОП-11БО» распознаются, порядок групп = порядок столбцов таблицы (похожие заголовки с пробелами группой не считаются); - main: GET /groups отдаёт группы в порядке столбцов; интервал обновления из SCHEDULE_INTERVAL_MINUTES (со старым SCHEDULE_CACHE_TTL как fallback); - cache: по интервалу сверяем modified — если файл не менялся, не скачиваем; при загрузке считаем sha256 и пишем в лог и /status, изменился ли файл; - source: запросы к Яндекс.Диску с no-cache и меткой времени; - frontend: ответы API с Cache-Control: no-store, общий .env из корня проекта (и backend, и frontend), страницы/прокси админки нет; - Dockerfile/compose/.env.example: без ADMIN_* и без тома настроек; в api-образ попадают все модули backend (проверяется тестом test_deploy); - Makefile: make help/venv/run/run-api/run-front/test + docker-цели — «make run» поднимает всё локально без Docker; - README: быстрый старт, полный список зависимостей, запуск локально и в Docker; - deploy/: пример nginx и инструкция «второй стенд рядом с основным». Тесты: 114 (удалены админские), все зелёные.
57 lines
2.1 KiB
Python
57 lines
2.1 KiB
Python
"""Загрузка файла расписания по публичной ссылке Яндекс.Диска."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import logging
|
||
import time
|
||
|
||
import requests
|
||
|
||
from .errors import ScheduleError
|
||
|
||
log = logging.getLogger(__name__)
|
||
|
||
CLOUD_API = "https://cloud-api.yandex.net/v1/disk/public/resources"
|
||
|
||
# Публичная ссылка может кэшироваться на стороне Диска: просим не кэшировать
|
||
# и добавляем в запрос метку времени, чтобы получать текущую версию файла.
|
||
_NO_CACHE = {"Cache-Control": "no-cache", "Pragma": "no-cache"}
|
||
|
||
|
||
def _bust() -> dict:
|
||
return {"_": int(time.time() * 1000)}
|
||
|
||
|
||
def download_schedule(public_url: str, timeout: float = 60.0) -> bytes:
|
||
"""Скачивает файл с диска; при любой ошибке бросает ScheduleError."""
|
||
try:
|
||
resp = requests.get(
|
||
f"{CLOUD_API}/download",
|
||
params={"public_key": public_url, **_bust()},
|
||
headers=_NO_CACHE,
|
||
timeout=timeout,
|
||
)
|
||
resp.raise_for_status()
|
||
href = resp.json()["href"]
|
||
file_resp = requests.get(href, headers=_NO_CACHE, timeout=timeout)
|
||
file_resp.raise_for_status()
|
||
return file_resp.content
|
||
except (requests.RequestException, KeyError, ValueError) as exc:
|
||
raise ScheduleError(f"Не удалось скачать таблицу: {exc}") from exc
|
||
|
||
|
||
def fetch_table_modified(public_url: str, timeout: float = 20.0) -> str | None:
|
||
"""Время последнего изменения файла на диске или None, если не удалось узнать."""
|
||
try:
|
||
resp = requests.get(
|
||
CLOUD_API,
|
||
params={"public_key": public_url, **_bust()},
|
||
headers=_NO_CACHE,
|
||
timeout=timeout,
|
||
)
|
||
resp.raise_for_status()
|
||
return resp.json().get("modified")
|
||
except (requests.RequestException, ValueError) as exc:
|
||
log.debug("Не удалось получить modified таблицы: %s", exc)
|
||
return None
|