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
Третья часть #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
65 lines
3.2 KiB
TOML
65 lines
3.2 KiB
TOML
[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"]
|