fix(#3197): no_proxy_stop=1 как у соседей, INFO на финале хендлера, докстринг фабрики
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
This commit is contained in:
parent
cb5714fff0
commit
939a8a1646
5 changed files with 39 additions and 18 deletions
|
|
@ -367,11 +367,15 @@ async def _job_yandex_newbuilding_sweep(
|
|||
# исход — назвать прогон неуспешным, а не дотянуть succeeded до ненуля.
|
||||
processed = int(counters.get("processed") or 0)
|
||||
succeeded = int(counters.get("succeeded") or 0)
|
||||
if counters.get("no_proxy_stop"):
|
||||
if result.no_proxy_stop:
|
||||
# #3197: прогон оборван на пустом пуле — к площадке не ходили вовсе.
|
||||
# Это отказ нашей инфраструктуры, а не «ЖК не разрешились»: называть
|
||||
# такой прогон успешным нельзя, и причина должна быть отличима.
|
||||
logger.error(
|
||||
#
|
||||
# INFO, как у соседей (scheduler.py:175, avito_detail_backfill.py:1050,
|
||||
# domclick:626): причина уже записана в mark_failed + counters.no_proxy_stop=1,
|
||||
# а ERROR ставил её в один разряд с падением задачи (logger.exception ниже).
|
||||
logger.info(
|
||||
"yandex_newbuilding_sweep run_id=%d: пул прокси пуст — прогон оборван "
|
||||
"(обработано %d)",
|
||||
run_id,
|
||||
|
|
|
|||
|
|
@ -85,7 +85,15 @@ class YandexNewbuildingSweepResult:
|
|||
duration_sec: float = field(default=0.0)
|
||||
|
||||
def to_dict(self) -> dict[str, int | float]:
|
||||
return {f.name: getattr(self, f.name) for f in fields(self)}
|
||||
"""Счётчики прогона; `no_proxy_stop` — 1/0, а не JSON true/false.
|
||||
|
||||
Формат один с соседями (`scheduler.py:168` cian, `avito_detail_backfill.py:1043`,
|
||||
`domclick_detail_backfill.py:610`): SQL-монитор ищет
|
||||
`counters->>'no_proxy_stop' = '1'` и мимо `true` промахнётся молча.
|
||||
"""
|
||||
out: dict[str, int | float] = {f.name: getattr(self, f.name) for f in fields(self)}
|
||||
out["no_proxy_stop"] = int(self.no_proxy_stop)
|
||||
return out
|
||||
|
||||
|
||||
# ── SQL ────────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -27,18 +27,21 @@ import pytest
|
|||
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
|
||||
|
||||
from app.services.product_handlers import _job_yandex_newbuilding_sweep
|
||||
from app.tasks.yandex_newbuilding_sweep import YandexNewbuildingSweepResult
|
||||
|
||||
|
||||
def _ctx() -> SimpleNamespace:
|
||||
return SimpleNamespace(runs=MagicMock())
|
||||
|
||||
|
||||
def _result(processed: int, succeeded: int) -> MagicMock:
|
||||
r = MagicMock()
|
||||
r.to_dict = MagicMock(
|
||||
return_value={"processed": processed, "succeeded": succeeded, "total": 425}
|
||||
)
|
||||
return r
|
||||
def _result(processed: int, succeeded: int) -> YandexNewbuildingSweepResult:
|
||||
"""Настоящий результат задачи, а не MagicMock.
|
||||
|
||||
У MagicMock любой не заданный атрибут (`no_proxy_stop` из #3197) — truthy, то есть
|
||||
добавление хендлеру новой ветки по полю результата уводило бы сюда ВСЕ три случая,
|
||||
и тест краснел бы по причине, не имеющей отношения к #2860.
|
||||
"""
|
||||
return YandexNewbuildingSweepResult(processed=processed, succeeded=succeeded, total=425)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -172,4 +172,8 @@ async def test_scheduler_marks_run_failed_on_empty_pool() -> None:
|
|||
_db, run_id, reason, counters = ctx.runs.mark_failed.call_args.args
|
||||
assert run_id == 3197
|
||||
assert "пул" in reason
|
||||
assert counters["no_proxy_stop"] is True
|
||||
# Ровно 1, а не JSON true: SQL-монитор соседей ищет counters->>'no_proxy_stop' = '1'
|
||||
# (scheduler.py:168 cian, avito_detail_backfill.py:1043, domclick_detail_backfill.py:610).
|
||||
# bool — подкласс int, поэтому `== 1` один сам по себе зелёный и на True: тип проверяем
|
||||
# отдельно, иначе красное на откате не наступит.
|
||||
assert counters["no_proxy_stop"] == 1 and not isinstance(counters["no_proxy_stop"], bool)
|
||||
|
|
|
|||
|
|
@ -206,16 +206,18 @@ def build_browser_fetcher(
|
|||
`ScraperConfig` невозможно на уровне сигнатуры (не полагаемся на
|
||||
code-review чтобы поймать пропущенный параметр).
|
||||
|
||||
`proxy_provider=None` (дефолт) — валидно: часть providers (avito legacy
|
||||
curl-only путь, domclick, cian/yandex newbuilding) пока не подключены к
|
||||
browser-proxy-пулу (#2160 P4 wiring gap, отдельный issue) — `use_pool` в
|
||||
этом случае эффективно игнорируется `BrowserFetcher` (`proxy_provider is
|
||||
None` → env-fallback, см. `browser_fetcher.py::_pool_proxy`).
|
||||
`proxy_provider=None` (дефолт) — валидно, но у providers больше не встречается:
|
||||
после #3382 (cian newbuilding) и #3389 (yandex newbuilding) провайдер передают
|
||||
ВСЕ call-site'ы providers (avito/cian/domclick/yandex, serp и newbuilding).
|
||||
Без пула остались только прямые конструкции `BrowserFetcher(...)` мимо этой
|
||||
фабрики — служебные ручки `app/api/v1/admin.py` (511 cian-login, 702
|
||||
domclick-detail-debug); прямые конструкции в `orchestration/pipeline.py` и в
|
||||
backfill-задачах пул получают. `use_pool` при `proxy_provider is None` эффективно
|
||||
игнорируется `BrowserFetcher` (env-fallback, см. `browser_fetcher.py::_pool_proxy`).
|
||||
|
||||
`fetch_timeout_s=None` (дефолт) → используется дефолт `BrowserFetcher`
|
||||
(120s). Yandex-провайдер передаёt здесь 30s явно (см. вызовы в
|
||||
`yandex/serp.py`/`yandex/newbuilding.py`) — единственный provider с
|
||||
отличным от дефолта таймаутом.
|
||||
(120s). Явный таймаут передаёт ровно один call-site — `yandex/serp.py` (30s);
|
||||
`yandex/newbuilding.py` идёт на дефолтных 120s.
|
||||
|
||||
`environment=getattr(config, "environment", "dev")` (#2616 шаг 1) — прокидывается в
|
||||
`BrowserFetcher._pool_proxy`: пул пуст/сломан + прод → отказ вместо мёртвого
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue