From 41ebca932a61e3b36590c37429ec936b8544e74b Mon Sep 17 00:00:00 2001 From: david Date: Thu, 22 Jan 2026 13:14:30 +0100 Subject: [PATCH] . --- .../application/settings/app_settings.py | 126 +++--------------- .../settings/app_settings_loader.py | 92 +++++++++++++ .../application/settings/container.py | 4 +- 3 files changed, 112 insertions(+), 110 deletions(-) create mode 100644 src/signing_service/application/settings/app_settings_loader.py diff --git a/src/signing_service/application/settings/app_settings.py b/src/signing_service/application/settings/app_settings.py index 6b2ff89..06aa5e0 100644 --- a/src/signing_service/application/settings/app_settings.py +++ b/src/signing_service/application/settings/app_settings.py @@ -1,114 +1,22 @@ -import os +from dataclasses import dataclass +@dataclass(frozen=True) class AppSettings: - def __init__(self) -> None: - # -------------------- - # Application - # -------------------- - self.app_env = self._get_env( - "APP_ENV", - default="local", - normalize=self._normalize_lower, - ) + # -------------------- + # Application + # -------------------- + app_env: str + log_level: str - self.log_level = self._get_env( - "LOG_LEVEL", - default="INFO", - normalize=self._normalize_upper, - ) + # -------------------- + # Secret manager + # -------------------- + secret_provider: str + gcp_project_id: str | None - # -------------------- - # Secret manager - # -------------------- - self.secret_provider = self._get_env( - "SECRET_PROVIDER", - default="fake", - normalize=self._normalize_lower, - ) - - self.gcp_project_id = self._get_env( - "GCP_PROJECT_ID", - default=None, - required=self.secret_provider == "google", - ) - - # -------------------- - # PDF signing - # -------------------- - self.pdf_cert_secret_name = self._get_env( - "PDF_CERT_SECRET_NAME", - required=True, - ) - - self.pdf_cert_password_secret_name = self._get_env( - "PDF_CERT_PASSWORD_SECRET_NAME", - required=True, - ) - - # -------------------- - # Cross-field validation - # -------------------- - self._validate() - - # ====================================================== - # Helpers - # ====================================================== - - def _get_env( - self, - name: str, - *, - default: str | None = None, - required: bool = False, - normalize=None, - ) -> str | None: - """ - Read and validate an environment variable. - """ - raw = os.environ.get(name, default) - - if raw is None: - if required: - raise ValueError(f"Environment variable {name} is required") - return None - - if not isinstance(raw, str): - raise ValueError(f"Environment variable {name} must be a string") - - value = raw.strip() - - if required and value == "": - raise ValueError(f"Environment variable {name} cannot be empty") - - if normalize: - value = normalize(value) - - return value - - @staticmethod - def _normalize_lower(value: str) -> str: - return value.strip().lower() - - @staticmethod - def _normalize_upper(value: str) -> str: - return value.strip().upper() - - # ====================================================== - # Validations - # ====================================================== - - def _validate(self) -> None: - allowed_providers = {"fake", "infisical", "google"} - - if self.secret_provider not in allowed_providers: - raise ValueError( - f"Invalid SECRET_PROVIDER: {self.secret_provider}. " - f"Allowed values: {allowed_providers}" - ) - - if self.app_env not in {"local", "prod"}: - raise ValueError( - f"Invalid APP_ENV: {self.app_env}. " - "Allowed values: local | prod" - ) + # -------------------- + # PDF signing + # -------------------- + pdf_cert_secret_name: str + pdf_cert_password_secret_name: str diff --git a/src/signing_service/application/settings/app_settings_loader.py b/src/signing_service/application/settings/app_settings_loader.py new file mode 100644 index 0000000..71d59f5 --- /dev/null +++ b/src/signing_service/application/settings/app_settings_loader.py @@ -0,0 +1,92 @@ +import os + +from signing_service.application.settings.app_settings import AppSettings + + +def load_app_settings() -> AppSettings: + def get_env( + name: str, + *, + default: str | None = None, + required: bool = False, + normalize=None, + ) -> str | None: + raw = os.environ.get(name, default) + + if raw is None: + if required: + raise ValueError(f"Environment variable {name} is required") + return None + + if not isinstance(raw, str): + raise ValueError(f"Environment variable {name} must be a string") + + value = raw.strip() + + if required and value == "": + raise ValueError(f"Environment variable {name} cannot be empty") + + if normalize: + value = normalize(value) + + return value + + # -------------------- + # Application + # -------------------- + app_env = get_env( + "APP_ENV", + default="local", + normalize=str.lower, + ) + + log_level = get_env( + "LOG_LEVEL", + default="INFO", + normalize=str.upper, + ) + + # -------------------- + # Secret manager + # -------------------- + secret_provider = get_env( + "SECRET_PROVIDER", + default="fake", + normalize=str.lower, + ) + + if secret_provider not in {"fake", "google", "infisical"}: + raise ValueError( + f"Invalid SECRET_PROVIDER: {secret_provider}. " + "Allowed values: fake | google | infisical" + ) + + gcp_project_id = get_env( + "GCP_PROJECT_ID", + required=secret_provider == "google", + ) + + # -------------------- + # PDF signing + # -------------------- + pdf_cert_secret_name = get_env( + "PDF_CERT_SECRET_NAME", + required=True, + ) + + pdf_cert_password_secret_name = get_env( + "PDF_CERT_PASSWORD_SECRET_NAME", + required=True, + ) + + # -------------------- + # Build immutable settings object + # -------------------- + return AppSettings( + app_env=app_env, + log_level=log_level, + secret_provider=secret_provider, + gcp_project_id=gcp_project_id, + pdf_cert_secret_name=pdf_cert_secret_name, + pdf_cert_password_secret_name=pdf_cert_password_secret_name, + ) diff --git a/src/signing_service/application/settings/container.py b/src/signing_service/application/settings/container.py index fc32608..58246a2 100644 --- a/src/signing_service/application/settings/container.py +++ b/src/signing_service/application/settings/container.py @@ -1,7 +1,9 @@ from functools import lru_cache + from signing_service.application.settings.app_settings import AppSettings +from signing_service.application.settings.app_settings_loader import load_app_settings @lru_cache def get_settings() -> AppSettings: - return AppSettings() + return load_app_settings()