gendesign/tradein-mvp/backend/pyproject.toml
bot-backend 124cfb3d5d
All checks were successful
CI / backend-tests (pull_request) Successful in 17m30s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m22s
CI Trade-In / backend-tests (pull_request) Successful in 4m51s
feat(observability): /metrics в обоих бэкендах — счётчики, задержка, дашборд
Третья часть #3078 и единственная, трогающая прод-код.

До неё числовых рядов у приложений не было вовсе: только логи и исключения в
GlitchTip. Класс отказов «отвечает, но медленно» и «отдаёт 401 потоком» в такой
картине невидим — исключения нет, строка в логе выглядит обычной, а продукт
при этом не работает.

Метка route — ШАБЛОН маршрута, а не путь запроса. Это несущее решение, а не
деталь: кадастровый номер или идентификатор заявки в метке даёт новый временной
ряд на каждую сущность, а ряд у Prometheus стоит памяти постоянно, а не в момент
запроса. Самый известный способ уронить мониторинг тем самым мониторингом.
Незаматченные пути (404, сканеры) сведены в одну метку, иначе тот же взрыв
устроит любой бот, перебирающий адреса. Оба свойства сторожатся тестами, а не
комментарием: тест бьёт тремя разными идентификаторами и требует ОДИН ряд.

Слой регистрируется последним и потому оказывается самым внешним. Изнутри
RBAC-гварда не видно ни отказов авторизации, ни времени, которое он тратит на
резолв сессии в БД auth, — а именно этот путь уже давал инцидент с блокирующим
I/O в middleware (#1202). Упавший исключением запрос считается как 500 в
finally: без этого он просто отсутствовал бы в счётчике, то есть ровно тогда,
когда метрики нужнее всего.

Путь публичен ВНУТРИ и закрыт СНАРУЖИ — это два разных периметра. Скрейп идёт
из docker-сети, где заголовка X-Authenticated-User нет ни у кого, поэтому
/metrics внесён в _PUBLIC_PATHS обоих бэкендов; иначе агент получал бы 401 и
метрик не было бы вовсе. Наружу путь не открывается ни через gendsgn.ru, ни
через meraocenka.ru, и вдобавок закрыт явным respond 404 в обоих site-блоках —
чтобы закрытость осталась решением, а не следствием текущего порядка директив.

Ограничитель частоты и аудит «Меры» не трогались: оба смотрят только на пути
под /api/, скрейп под них не попадает. Проверено тестом, а не чтением.

Прод-поведение не меняется ничем, кроме нового публичного пути: ни один
существующий обработчик, гвард или маршрут не тронут.

Refs #3078
2026-08-26 11:30:18 +03:00

65 lines
3.2 KiB
TOML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[project]
name = "tradein-mvp-backend"
version = "0.1.0"
description = "Trade-In Estimator MVP — standalone fork of gendesign trade-in feature"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.115.0",
"uvicorn[standard]>=0.30.0",
"sqlalchemy>=2.0.30",
"geoalchemy2>=0.15.0", # PostGIS support для SQLAlchemy
"pydantic>=2.7.0",
"pydantic-settings>=2.3.0",
"psycopg[binary]>=3.2.0",
"Pillow>=10.3.0", # photo sanitization re-encode (#6 audit fix)
"weasyprint>=62.0",
"jinja2>=3.1.0",
"httpx[socks]>=0.27.0", # для geocoder + scrapers; [socks] тянет socksio для socks5://
"tenacity>=9.0.0", # retry с exp backoff
"selectolax>=0.3.0", # быстрый HTML парсинг для scrapers
"lxml>=5.0.0", # стриминговый iterparse для многогигабайтных ГАР XML (#143)
"segno>=1.6.0", # QR-код для PDF shareable URL
"matplotlib>=3.9.0", # price-range chart (SVG) для PDF отчёта Trade-In
"curl-cffi>=0.15.0", # impersonate=<profile> для Cian/Avito (TLS fingerprint).
# Пол 0.15.0, а не 0.7.0: DEFAULT_IMPERSONATE=chrome146, а этот
# профиль появился позже 0.7.0 — установка по нижней границе
# роняла бы КАЖДЫЙ запрос скраппера на невалидном impersonate.
"python-multipart>=0.0.9", # FastAPI UploadFile — загрузка фото квартиры (#394)
"sentry-sdk>=2.0.0", # мониторинг ошибок → GlitchTip (#396)
"redis>=5.0.0", # async hot cache для /api/v1/search (Phase 3.2)
"pyyaml>=6.0.0", # RBAC roles.yaml loader (app/core/auth.py)
"bcrypt>=4.2.0", # password hashing для DB-auth (#2550)
"playwright>=1.45", # Playwright client для connect к tradein-browser (#905)
"prometheus-client>=0.21.0", # /metrics — экспозиция и process-коллекторы (#3078)
"scraper-kit", # internal workspace-package (#2137) — общие утилиты скрапперов;
# резолвится из workspace (см. [tool.uv.sources]), не с PyPI.
# Docker-context = tradein-mvp root, uv sync ставит editable.
]
[tool.uv.sources]
scraper-kit = { workspace = true }
[dependency-groups]
dev = [
"pytest>=8.0.0",
"pytest-asyncio>=0.24.0",
"pytest-timeout>=2.3.0",
"ruff==0.15.20",
]
[tool.pytest.ini_options]
asyncio_mode = "auto"
# Страховка от зависших тестов в CI (per-test wall-clock limit, секунды).
# Покрывает и pre-merge гейт (ci-tradein.yml), и post-merge test-job (deploy).
timeout = 120
[tool.setuptools.packages.find]
include = ["app*"]
[tool.ruff]
target-version = "py312"
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP", "N", "RUF"]
ignore = ["RUF001", "RUF002", "RUF003"]