feat(tradein): global scraper delay setting (applies across all scrapers) #485
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#485
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-global-scraper-delay"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add global request delay setting — single source of control over
request_delay_secдля ALL scrapers (avito, cian, n1, yandex, domrf, rosreestr). Companion frontend UI в sibling PRfeat/tradein-cian-admin-page.Files
data/sql/053_scraper_settings.sql(NEW, 19 lines) —scraper_settingstabledata/sql/054_scraper_settings_global.sql(NEW, 25 lines) — seed'global'row + per-source defaultsapp/services/scraper_settings.py(+130 lines) —_GLOBAL_KEY, refactored cache,get_scraper_delay() = max(per_source, global)app/services/scrapers/avito.py(+5 lines) — load delay from DB в__init__app/services/scrapers/cian.py(+3 lines) — sameapp/services/scrapers/n1.py(+13 lines) — same + 3 pre-existing lint fixesapp/api/v1/admin.py(+69 lines) —GET /scraper-settings,PUT /scraper-settings/{source}tests/test_scraper_settings.py(+196 lines, 10 tests)Schema
scraper_settingstable (already exists from earlier — migration 053 idempotent):Migration 054 seeds:
'global'→0.0(no floor by default)'avito'→7.0,'cian'→5.0,'n1'→5.0,'domrf'→5.0,'rosreestr'→5.0ON CONFLICT DO NOTHING— preserves user-edited values on re-run.get_scraper_delay() behavior
global = 0→ per-source value used (current behavior)global = 10→ all scrapers wait ≥ 10s even if per-source = 5API endpoints
/api/v1/admin/scraper-settings{settings: [{source, request_delay_sec, description, updated_at}, ...]}/api/v1/admin/scraper-settings/{source}{source, request_delay_sec (0..60), description?}PUT calls
invalidate_cache(source)для immediate effect (next scraper instance reads fresh value).Validators
request_delay_sec:ge=0.0, le=60.0— global=0 validsource: 1..64 charsTests (10 pass)
Scrapers updated
avito.py, cian.py, n1.py теперь mirror yandex pattern:
Yandex scrapers already use this pattern (unchanged).
Push verify
LOCAL == REMOTE =
bfe56e44Companion PR
feat/tradein-cian-admin-page— Cian admin UI с slider/input для global delay (calls these endpoints).Test plan
- SQL migrations 053 (scraper_settings table) + 054 (seed global + per-source rows) - scraper_settings.py: get_scraper_delay() returns max(per_source, global); in-memory cache TTL=60s; invalidate_cache() for immediate effect on PUT - avito/cian/n1 scrapers load delay from DB in __init__ (mirrors yandex pattern) - Admin API: GET /scraper-settings (list all), PUT /scraper-settings/{source} with cache invalidation on update; CAST(:d AS numeric) per psycopg v3 rules - 10 unit tests: global>per_source, per_source>global, global=0, DB error fallback, cache invalidation, API endpoint smokeDeep review — BLOCK (rebase required)
PR #484 was merged 27 seconds after #485 was created. #485 is now stale —
mergeable: falseconfirms the conflict. Genuinely useful additions (global delay floor, avito/cian/n1 wiring) but cannot merge as-is.Hard conflicts with main (post-#484)
data/sql/053_scraper_settings.sql— incompatible schemas:source text PRIMARY KEY,numeric(5,2), noid/created_at,Field(ge=1.0)id SERIAL PRIMARY KEY,source text UNIQUE,numeric(6,2) DEFAULT 5.0,created_at,Field(ge=0.0)CREATE TABLE IF NOT EXISTSsilently skips → schema mismatch on prod.app/services/scraper_settings.py— file already exists in main (101 lines, yandex-only). This PR'sstatus: "added"cannot apply over existing file.app/api/v1/admin.py—ScraperSetting,ScraperSettingUpdate,list_scraper_settings,update_scraper_setting,invalidate_cacheimport — all already in main from #484. Duplicate route registration + duplicate import.Required rebase plan
git fetch forgejo && git rebase forgejo/maindata/sql/053_scraper_settings.sqlfrom PR (#484's version stays canonical)054_scraper_settings_global.sqlas ALTER, not CREATE:scraper_settings.py(don't replace):_GLOBAL_KEY = "global"_DEFAULT_DELAY_BY_SOURCEwith avito/cian/n1/domrf/rosreestrmax(per_source, global)logic toget_scraper_delay()key == _GLOBAL_KEY → 0.0fallback_KEY_ALIASESadmin.py(don't add duplicates):ScraperSettingUpdate.Field(ge=1.0, le=60.0)→ge=0.0(required forglobal=0)descriptionto UPDATE path (currently dropped on UPSERT)invalidate_cacheimportavito.py,cian.py,n1.py+ tests) — these are the real value-addMinor (post-rebase)
id bigserialbut SQL usesSERIAL(int4) — moot since column dropping anywaytest_invalidate_cache_single_sourcedoesn't cover "PUT global → all per-source max() recomputes"; consider adding regression testcode/modules/tradein/Live_Scraper_Config.mdafter mergeWhat's preserved from this PR's value
max(per_source, global)floor semanticsField(ge=0.0)to allowglobal=0Not merging. Please rebase and re-request review.
Merge commit
814530d1pushed — resolves conflicts с PR #484 (которая уже добавила scraper_settings infrastructure).Resolution
data/sql/053_scraper_settings.sql--theirs(kept main'ssource TEXT PRIMARY KEYschema from PR #484)app/services/scraper_settings.py_open_session()+ Yandex alias map + наш_GLOBAL_KEY,_get_setting_cached(),max(per_source, global)semanticstests/test_scraper_settings.pyapp/api/v1/admin.pyScraperSetting/ScraperSettingUpdatemodels + endpoint block; kept ours (ScraperSettingPayloadсge=0.0— required дляglobal=0)Key design decisions
_open_session()use main's deferred-import pattern (avoidsDATABASE_URLvalidation at import time — tests run без env)_DEFAULT_DELAY_BY_SOURCEmerged: main's Yandex sub-keys + наши avito/cian/n1/domrf/rosreestr_KEY_ALIASESuse main's full Yandex alias map (ours был empty)ScraperSettingPayload.ge=0.0overrides main'sge=1.0— global=0 разрешён ("no floor")Verified
max(per_source, global)— только per-source lookup. Наше enhancement additive.814530d1Ready for re-review.
Merged via deep-code-reviewer — verdict APPROVE.
Rebase plan executed correctly:
ON CONFLICT DO NOTHING)scraper_settings.pyMODIFIED (not added) —_GLOBAL_KEY+max(per_source, global)layered on top of #484admin.pyMODIFIED — oldScraperSetting*models + endpoints removed, newScraperSettingPayload+ge=0.0validator added (no duplication)Verified:
numeric(5,2)fits 0..60 range)_KEY_ALIASESfrom #484 preserved → yandex umbrella still works under newmax()logicmock_open.call_countcorrectly bumped for double DB hits (per-source + global)CAST(:d AS numeric)✅; migration wrapped inBEGIN; COMMIT;--no-verify/--force/--amendin branch historyCloses follow-up gap from #486 (Cian admin slider min=0 now accepted by API).
Minor non-blocking follow-ups:
invalidate_cache()only clears receiving process (others wait TTL 60s) — single-replica deploy currently, acceptablecode/modules/tradein/Live_Scraper_Config.mdrecommended (documentmax(per_source, global)+ 60s TTL + per-process cache caveat)Post-merge verify steps:
'global'=0seeded (existing yandex row unchanged)GET /admin/scraper-settingsreturns 6 rows (yandex, avito, cian, n1, domrf, rosreestr, global = 7 if all seeded)PUT /admin/scraper-settings/globalbody{source: "global", request_delay_sec: 0}→ 200 (was rejected before)PUT global=10→ next avito scrape waits ≥10s