Coverage for src/ph/store_catalog.py: 100.0%
17 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 08:17 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 08:17 +0000
1"""Configured game-store registry and lookup helpers."""
3from dataclasses import dataclass
4from typing import Self
6from ph.cache_policy import DEFAULT_CATALOGUE_TTL_DAYS, catalogue_ttl_seconds
7from ph.config import Config
8from ph.minerva_store import MinervaStore
9from ph.store import GameStore
10from ph.vimm_store import VimmStore
13@dataclass(frozen=True, slots=True)
14class StoreCatalog:
15 """The stores available to this application run."""
17 stores: tuple[GameStore, ...]
19 @classmethod
20 def from_config(
21 cls,
22 config: Config,
23 ttl_seconds: int = catalogue_ttl_seconds(DEFAULT_CATALOGUE_TTL_DAYS),
24 ) -> Self:
25 """Create every enabled concrete store from application configuration."""
27 available: tuple[GameStore, ...] = (
28 VimmStore(
29 config.vimm_base_url,
30 config.timeout_seconds,
31 config.download_directory,
32 ttl_seconds,
33 ),
34 MinervaStore(
35 config.minerva_base_url,
36 config.minerva_torrent_base_url,
37 config.timeout_seconds,
38 config.download_directory,
39 ttl_seconds,
40 ),
41 )
42 enabled = set(config.enabled_stores)
43 return cls(tuple(store for store in available if store.store_id in enabled))
45 def find(self, store_id: str) -> GameStore | None:
46 """Resolve a store identifier case-insensitively."""
48 normalized = store_id.strip().casefold()
49 return next(
50 (store for store in self.stores if store.store_id.casefold() == normalized),
51 None,
52 )