"""Loads the scraped events JSON file and keeps it in memory. The file is treated as read-only. Call ``reload()`` to pick up a fresh scrape without restarting the server. """ import json import os from datetime import date from pathlib import Path from threading import Lock from typing import List, Optional from models import Event # Default: the fundi-scraped-output.json sitting in the repo root, one # directory above this file. Override with the DATA_FILE env var. _DEFAULT_DATA_FILE = Path(__file__).resolve().parent.parent / "fundi-scraped-output.json" class EventStore: def __init__(self, path: Optional[os.PathLike] = None): self.path = Path(path or os.environ.get("DATA_FILE") or _DEFAULT_DATA_FILE) self._lock = Lock() self._events: List[Event] = [] self.reload() def reload(self) -> int: """Re-read the JSON file from disk. Returns the number of events loaded.""" if not self.path.exists(): raise FileNotFoundError(f"Data file not found: {self.path}") with self.path.open(encoding="utf-8") as fh: raw = json.load(fh) events = [Event.model_validate(item) for item in raw] with self._lock: self._events = events return len(events) def all(self) -> List[Event]: with self._lock: return list(self._events) def get(self, index: int) -> Optional[Event]: with self._lock: if 0 <= index < len(self._events): return self._events[index] return None def parse_event_date(value: str) -> Optional[date]: """Parse the scraper's ``dd.mm.yy`` date strings. Returns None if unparseable.""" try: day, month, year = (int(part) for part in value.strip().split(".")) except (ValueError, AttributeError): return None if year < 100: year += 2000 try: return date(year, month, day) except ValueError: return None def iter_artist_names(event: Event): for group in event.event_artists: for artist in group: if artist.artist_name: yield artist.artist_name