.
This commit is contained in:
parent
76103ac0f2
commit
d57355a9c9
@ -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",
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user