Coverage for src/ph/config.py: 100.0%
43 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"""Application configuration loaded from environment variables."""
3import os
4from collections.abc import Mapping
5from dataclasses import dataclass
6from pathlib import Path
7from typing import Self
9from ph.targets import DARKOS, LinuxTarget, resolve_target
10from ph.updater import DEFAULT_UPDATE_API_URL
12DEFAULT_VIMM_BASE_URL = "https://vimm.net"
13DEFAULT_MINERVA_BASE_URL = "https://minerva-archive.org"
14DEFAULT_MINERVA_TORRENT_BASE_URL = "https://cdn.minerva-archive.org/torrents"
17@dataclass(frozen=True, slots=True)
18class Config:
19 """Runtime settings shared by every supported Linux target."""
21 vimm_base_url: str
22 download_directory: Path
23 roms_directories: tuple[Path, ...]
24 timeout_seconds: float = 30.0
25 minerva_base_url: str = DEFAULT_MINERVA_BASE_URL
26 minerva_torrent_base_url: str = DEFAULT_MINERVA_TORRENT_BASE_URL
27 enabled_stores: tuple[str, ...] = ("vimm", "minerva")
28 install_directory: Path | None = None
29 update_api_url: str = DEFAULT_UPDATE_API_URL
30 log_file: Path | None = None
31 log_level: str = "INFO"
32 target: LinuxTarget = DARKOS
34 @classmethod
35 def from_environment(cls, environment: Mapping[str, str] | None = None) -> Self:
36 """Build settings with DarkOS as the currently tested default target."""
38 values = os.environ if environment is None else environment
39 vimm_base_url = values.get("PH_VIMM_BASE_URL", DEFAULT_VIMM_BASE_URL).rstrip("/")
40 directory = Path(values.get("PH_DOWNLOAD_DIR", ".")).expanduser()
41 roms_values = values.get("PH_ROMS_DIRS")
42 if roms_values is not None:
43 roms_directories = tuple(
44 dict.fromkeys(
45 Path(value).expanduser()
46 for value in roms_values.split(os.pathsep)
47 if value.strip()
48 )
49 )
50 else:
51 roms_value = values.get("PH_ROMS_DIR")
52 roms_directories = (Path(roms_value).expanduser(),) if roms_value else ()
53 timeout = float(values.get("PH_TIMEOUT", "30"))
54 minerva_base_url = values.get("PH_MINERVA_BASE_URL", DEFAULT_MINERVA_BASE_URL).rstrip("/")
55 minerva_torrent_base_url = values.get(
56 "PH_MINERVA_TORRENT_BASE_URL", DEFAULT_MINERVA_TORRENT_BASE_URL
57 ).rstrip("/")
58 enabled_stores = tuple(
59 dict.fromkeys(
60 store.strip().casefold()
61 for store in values.get("PH_STORES", "vimm,minerva").split(",")
62 if store.strip()
63 )
64 )
65 install_value = values.get("PH_INSTALL_DIR")
66 install_directory = Path(install_value).expanduser() if install_value else None
67 update_api_url = values.get("PH_UPDATE_API_URL", DEFAULT_UPDATE_API_URL)
68 log_value = values.get("PH_LOG_FILE")
69 log_file = Path(log_value).expanduser() if log_value else None
70 log_level = values.get("PH_LOG_LEVEL", "INFO")
71 target = resolve_target(values.get("PH_TARGET_OS", DARKOS.target_id))
72 return cls(
73 vimm_base_url=vimm_base_url,
74 download_directory=directory,
75 roms_directories=roms_directories,
76 timeout_seconds=timeout,
77 minerva_base_url=minerva_base_url,
78 minerva_torrent_base_url=minerva_torrent_base_url,
79 enabled_stores=enabled_stores,
80 install_directory=install_directory,
81 update_api_url=update_api_url,
82 log_file=log_file,
83 log_level=log_level,
84 target=target,
85 )