feat: serialize official record imports
This commit is contained in:
@@ -3,11 +3,12 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import re
|
||||
import time as time_module
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import asdict, dataclass
|
||||
from datetime import date, datetime, time, timezone
|
||||
|
||||
import httpx
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import select, text
|
||||
from sqlalchemy.orm import Session
|
||||
from rf4_research.official_parser import RecordsContractError, parse_official_records
|
||||
|
||||
@@ -24,6 +25,10 @@ class ImportSourceError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class ImportAlreadyRunning(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class RawRecord:
|
||||
region: str
|
||||
@@ -105,7 +110,42 @@ def fetch_records(
|
||||
raise AssertionError("unreachable")
|
||||
|
||||
|
||||
def _lock_key(url: str, region: str, category: str) -> int:
|
||||
digest = hashlib.sha256(f"{url}|{region.upper()}|{category}".encode()).digest()
|
||||
return int.from_bytes(digest[:8], byteorder="big", signed=True)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _official_import_lock(session: Session, *, url: str, region: str, category: str):
|
||||
bind = session.get_bind()
|
||||
if bind.dialect.name != "postgresql":
|
||||
yield
|
||||
return
|
||||
connection = bind.connect()
|
||||
key = _lock_key(url, region, category)
|
||||
try:
|
||||
acquired = bool(connection.scalar(text("SELECT pg_try_advisory_lock(:key)"), {"key": key}))
|
||||
except Exception:
|
||||
connection.close()
|
||||
raise
|
||||
if not acquired:
|
||||
connection.close()
|
||||
raise ImportAlreadyRunning("official import is already running for this source and category")
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
try:
|
||||
connection.execute(text("SELECT pg_advisory_unlock(:key)"), {"key": key})
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def import_records(session: Session, *, url: str, region: str, category: str, html: str | None = None) -> OfficialRecordImport:
|
||||
with _official_import_lock(session, url=url, region=region, category=category):
|
||||
return _import_records_locked(session, url=url, region=region, category=category, html=html)
|
||||
|
||||
|
||||
def _import_records_locked(session: Session, *, url: str, region: str, category: str, html: str | None = None) -> OfficialRecordImport:
|
||||
run = OfficialRecordImport(started_at=datetime.now(timezone.utc), status=ImportStatus.running, source_url=url, rows_seen=0, rows_created=0, rows_updated=0)
|
||||
session.add(run)
|
||||
session.commit()
|
||||
|
||||
Reference in New Issue
Block a user