from __future__ import annotations from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): """Runtime settings. SQLite is the default because this prototype should run immediately. The schema is deliberately plain enough to migrate to PostGIS later. """ database_url: str = "sqlite:///./data/workbench.sqlite" data_dir: Path = Path("./data") # 0 means import all stop_times. Use a positive value only for constrained # demos where full timetable routing is not needed. gtfs_stop_times_import_limit: int = 0 # "sidecar_stop_times" keeps the large timetable call table in a per-dataset # SQLite file and stores compact GTFS tables in the main app database. # Set to "main" for the old all-in-one SQLite layout. gtfs_timetable_storage: str = "sidecar_stop_times" gtfs_keep_activation_stage: bool = False # "sidecar_features" keeps extracted OSM transport features in a per-dataset # SQLite file. The main DB materializes only OSM rows that need stable # foreign keys for matches or route-layer output. osm_feature_storage: str = "sidecar_features" osm_sidecar_create_visual_only_stops: bool = False # Large OSM PBF extracts should be reduced to transport objects before the # Python extractor scans them. XML fixtures stay unfiltered by default. osm_pbf_prefilter_enabled: bool = True osm_pbf_prefilter_formats: str = "osm_pbf" osm_pbf_prefilter_script: Path = Path("scripts/osmium_transport_filter.sh") osm_diff_max_sequence_gap: int = 14 osm_diff_apply_batch_size: int = 7 osm_diff_state_timeout_seconds: float = 30.0 sqlite_timeout_seconds: float = 120.0 sqlite_busy_timeout_ms: int = 120000 database_write_lock_timeout_seconds: float = 1.0 database_write_lock_cli_timeout_seconds: float = 3600.0 queue_worker_autostart: bool = True queue_worker_count: int = 1 queue_worker_poll_interval_seconds: float = 2.0 queue_job_lease_seconds: int = 7200 route_matching_batch_size: int = 100 route_layer_osm_route_batch_size: int = 1000 route_layer_osm_stop_batch_size: int = 5000 # SQLite defaults to sidecar storage. PostgreSQL/PostGIS defaults to main # table storage so indexes, joins, and spatial operators can work over the # full imported datasets. postgres_use_sidecars: bool = False # Keep supervised workers alive across API server restarts. Stale workers are # detected by PID files at the next startup; stale job leases are requeued. queue_worker_stop_on_shutdown: bool = False model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8") @property def normalized_database_url(self) -> str: if self.database_url.startswith("postgresql://"): return "postgresql+psycopg://" + self.database_url.removeprefix("postgresql://") return self.database_url @property def is_sqlite_database(self) -> bool: return self.normalized_database_url.startswith("sqlite") @property def is_postgresql_database(self) -> bool: return self.normalized_database_url.startswith("postgresql") settings = Settings() settings.data_dir.mkdir(parents=True, exist_ok=True)