All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
CI / changes (pull_request) Successful in 9s
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
Две живые копии одного модуля с противоположной семантикой counters: app
`mark_done`/`mark_failed`/`mark_banned`/`update_heartbeat` ЗАМЕНЯЛИ
(`counters = CAST(:counters AS jsonb)`), kit — МЕРЖИЛИ
(`COALESCE(counters,'{}') || …`). Расхождение дважды за сутки дало ложные
выводы на ревью (#3388 «отдать только флаг, остальное домержится» — на
replace это стёрло бы измеренное; #3355). Разошлись и другие места: гейт
статуса, `honors_cancel` у mark_cancelled (был только в app), `mark_skipped`
(только в kit), `mark_backfill_finished`/`distinct_sources` (только в app).
Реализация теперь одна — `scraper_kit.orchestration.runs`; в неё перенесены
app-only функции. `app.services.scrape_runs` — алиас kit-модуля через
sys.modules, а не реэкспорт имён: реэкспорт разводит патч-цели
(`patch("app.services.scrape_runs.sentry_sdk")`, `patch.object(runs_mod,
"mark_done")` правили бы глобаль модуля-обёртки, а тело функции читает
глобаль kit'а) — тест остался бы зелёным, не подменив ничего. С алиасом оба
имени ведут в единственную реализацию, и ни один из ~40 вызывающих и ~30
патч-сайтов в тестах не правится.
Победила семантика мержа: у строки прогона несколько писателей (пульс,
финализатор, дрейн), каждый знает лишь свои ключи, и замена теряла чужие —
чекпоинт done_buckets (#930), метку interrupted (#3391), замер из пульса
(#3384). Обратной зависимости («вызывающий рассчитывает, что финализатор
УДАЛИТ ключ заменой») нет: строка создаётся пустой в create_run, резюм читает
counters ПРЕДЫДУЩЕГО прогона по его id.
Тесты по значению на обоих путях импорта (двойник сессии читает SQL: `||`
против CAST, WHERE-гейт из текста): пульс {a:5} + mark_failed {b:1} → {a,b};
пульс/финализатор по финализированной строке — no-op; mark_cancelled
отказывает источнику, который отмену не опрашивает. На main эти тесты
красные для app-пути.
Комментарии в app/services/scheduler.py и kit/pipeline.py, утверждавшие про
живого «перезаписывающего двойника», приведены в соответствие.
36 lines
3.1 KiB
Python
36 lines
3.1 KiB
Python
"""`app.services.scrape_runs` — АЛИАС `scraper_kit.orchestration.runs` (#3390).
|
||
|
||
Реализация одна, и она в kit. Здесь нет ни функций, ни констант: модуль подменяет
|
||
себя kit-модулем в `sys.modules`, поэтому `from app.services import scrape_runs as
|
||
runs_mod` и `from scraper_kit.orchestration import runs` дают ОДИН И ТОТ ЖЕ объект.
|
||
|
||
Зачем так, а не «тонкий модуль с реэкспортом имён»: реэкспорт разводит патч-цели.
|
||
`patch("app.services.scrape_runs.sentry_sdk")` или `patch.object(runs_mod, "mark_done")`
|
||
правит глобаль ЭТОГО модуля, а тело реэкспортированной функции читает глобаль СВОЕГО —
|
||
kit'а. Тест остался бы зелёным, не подменив ничего (или покраснел бы на пустом месте),
|
||
а прод-путь оказался бы вне проверки: ровно тот класс дефекта, из-за которого заведена
|
||
#3390. С алиасом патч по любому из двух имён попадает в единственную реализацию.
|
||
|
||
Что здесь было до #3390: полная вторая копия модуля (1159 строк), которая разошлась с
|
||
kit'ом минимум четырежды — counters ЗАМЕНЯЛА (`counters = CAST(:counters AS jsonb)`)
|
||
против мержа (`COALESCE(counters,'{}') || …`) у kit, свой гейт по статусу, `honors_cancel`
|
||
только здесь, `mark_skipped` только там. Ревью дважды за сутки делало из этого ложные
|
||
выводы (#3388, #3355), потому что «вызывающий пишет через runs» не отвечало на вопрос,
|
||
какая из двух семантик применится. Победила семантика kit'а — мерж: у одной строки
|
||
прогона несколько писателей (пульс, финализатор, дрейн), каждый знает лишь свои ключи,
|
||
и замена теряла чужие (чекпоинт `done_buckets` #930, метка `interrupted` #3391, замер из
|
||
пульса #3384). Зависимости «финализатор обязан УДАЛИТЬ ключ заменой» нет ни у одного
|
||
вызывающего: строка прогона создаётся пустой (`create_run`), а резюм читает counters
|
||
ПРЕДЫДУЩЕГО прогона по его id.
|
||
|
||
НЕ добавляй сюда код: всё, что написано ниже подмены, недостижимо — импортирующий
|
||
получает kit-модуль. Новые функции — в `scraper_kit/orchestration/runs.py`.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
|
||
from scraper_kit.orchestration import runs as _kit_runs
|
||
|
||
sys.modules[__name__] = _kit_runs
|