"""Service configuration. Every value is settable from the environment so the container needs no config file. `database_url` has no default on purpose: a service that silently falls back to a local database when its real one is misconfigured is worse than one that refuses to start. """ from __future__ import annotations from pathlib import Path from pydantic_settings import BaseSettings, SettingsConfigDict class Settings(BaseSettings): model_config = SettingsConfigDict(env_prefix="CANNED_PROMPTS_", extra="ignore") # Two ways in, and the file is the one to use in a cluster: an env var # holding a password is visible in `kubectl describe`, in crash dumps, and # to anything that can read /proc. The file form lets a mounted secret stay # a file. database_url: str = "" database_url_file: str = "" tenant: str = "default" service_name: str = "canned-prompts" # A single shared token, so the identity it proves is "the operator of this # service" and nothing finer. Unset means the service is read-only; see # auth.py for why that is the right default rather than an inconvenience. publish_token: str = "" publisher_name: str = "operator" publish_token_file: str = "" def _read_secret(self, path: str) -> str: try: return Path(path).read_text(encoding="utf-8").strip() except OSError as exc: raise RuntimeError(f"cannot read secret file {path}: {exc}") from exc @property def resolved_database_url(self) -> str: """The file wins when both are set: a mounted secret is the stronger statement of intent, and silently preferring the env var would make a rotated credential look like it had not taken effect.""" if self.database_url_file: return self._read_secret(self.database_url_file) return self.database_url @property def resolved_publish_token(self) -> str: if self.publish_token_file: return self._read_secret(self.publish_token_file) return self.publish_token @property def configured(self) -> bool: return bool(self.database_url or self.database_url_file) def get_settings() -> Settings: return Settings()