From d57355a9c93d3ba021c2b2545826f8d27766af0d Mon Sep 17 00:00:00 2001 From: david Date: Thu, 22 Jan 2026 13:07:15 +0100 Subject: [PATCH] . --- pyproject.toml | 1 - .../application/settings/app_settings.py | 142 +++++++++++++----- src/signing_service/main.py | 2 +- 3 files changed, 102 insertions(+), 43 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 323be28..99f5cfc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,6 @@ dependencies = [ "python-dotenv>=1.0", "fastapi>=0.110", "uvicorn[standard]>=0.27", - "pydantic-settings>=2.2", "pyhanko>=0.25", "cryptography>=42", diff --git a/src/signing_service/application/settings/app_settings.py b/src/signing_service/application/settings/app_settings.py index f0873a8..6b2ff89 100644 --- a/src/signing_service/application/settings/app_settings.py +++ b/src/signing_service/application/settings/app_settings.py @@ -1,54 +1,114 @@ -from pydantic_settings import BaseSettings -from pydantic import Field, model_validator +import os -class AppSettings(BaseSettings): - # -------------------- - # Application - # -------------------- - app_env: str = Field(default="local") - log_level: str = Field(default="INFO") +class AppSettings: + def __init__(self) -> None: + # -------------------- + # Application + # -------------------- + self.app_env = self._get_env( + "APP_ENV", + default="local", + normalize=self._normalize_lower, + ) - # -------------------- - # Secret manager - # -------------------- - secret_provider: str = Field(default="fake") - gcp_project_id: str | None = None + self.log_level = self._get_env( + "LOG_LEVEL", + default="INFO", + normalize=self._normalize_upper, + ) - # -------------------- - # PDF signing - # -------------------- - pdf_cert_secret_name: str - pdf_cert_password_secret_name: str + # -------------------- + # Secret manager + # -------------------- + self.secret_provider = self._get_env( + "SECRET_PROVIDER", + default="fake", + normalize=self._normalize_lower, + ) - @model_validator(mode="after") - def validate_required_settings(self) -> "AppSettings": + 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: """ - Fail fast if required configuration is missing. + Read and validate an environment variable. """ + raw = os.environ.get(name, default) - if not self.pdf_cert_secret_name: - raise ValueError("PDF_CERT_SECRET_NAME is required") + if raw is None: + if required: + raise ValueError(f"Environment variable {name} is required") + return None - if not self.pdf_cert_password_secret_name: - raise ValueError("PDF_CERT_PASSWORD_SECRET_NAME is required") + if not isinstance(raw, str): + raise ValueError(f"Environment variable {name} must be a string") - # ---- Secret provider validation ---- - if self.secret_provider == "google": - if not self.gcp_project_id: - raise ValueError( - "GCP_PROJECT_ID is required when SECRET_PROVIDER=google" - ) + value = raw.strip() - # ---- PDF signing validation ---- - if self.app_env == "prod": - if not self.pdf_pfx_password: - raise ValueError( - "PDF_PFX_PASSWORD is required in production" - ) + if required and value == "": + raise ValueError(f"Environment variable {name} cannot be empty") - return self + if normalize: + value = normalize(value) - class Config: - env_file = ".env" - case_sensitive = False + 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" + ) diff --git a/src/signing_service/main.py b/src/signing_service/main.py index 5daa6aa..cde7e56 100644 --- a/src/signing_service/main.py +++ b/src/signing_service/main.py @@ -10,7 +10,7 @@ load_dotenv() # 👇 FAIL FAST: load settings at startup get_settings() -app = FastAPI(title="Signing Service") +app = FastAPI(title="FactuGES Document Signing Service") app.include_router(sign_router) @app.get("/health")