This commit is contained in:
David Arranz 2026-01-22 13:14:30 +01:00
parent d57355a9c9
commit 41ebca932a
3 changed files with 112 additions and 110 deletions

View File

@ -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

View File

@ -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,
)

View File

@ -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()