Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions scripts/local_with_uvicorn/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
APP_NAME="My Project"
APP_DESCRIPTION="My Project Description"
APP_VERSION="0.1"
APP_BACKEND_HOST="http://localhost:8000"
APP_FRONTEND_HOST="http://localhost:3000"
CONTACT_NAME="Me"
CONTACT_EMAIL="[email protected]"
LICENSE_NAME="MIT"
Expand Down
40 changes: 39 additions & 1 deletion src/app/core/config.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import os
import warnings
from enum import Enum
from typing import Self

from pydantic import SecretStr, computed_field
from pydantic import SecretStr, computed_field, field_validator, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class AppSettings(BaseSettings):
APP_NAME: str = "FastAPI app"
APP_DESCRIPTION: str | None = None
APP_VERSION: str | None = None
APP_BACKEND_HOST: str = "http://localhost:8000"
APP_FRONTEND_HOST: str | None = None
LICENSE_NAME: str | None = None
CONTACT_NAME: str | None = None
CONTACT_EMAIL: str | None = None

@field_validator("APP_BACKEND_HOST", "APP_FRONTEND_HOST", mode="after")
@classmethod
def validate_hosts(cls, host: str) -> str:
if host is not None and not (host.startswith("http://") or host.startswith("https://")):
raise ValueError(
f"HOSTS must define their protocol and start with http:// or https://. Received the host '{host}'."
)
return host


class CryptSettings(BaseSettings):
SECRET_KEY: SecretStr = SecretStr("secret-key")
Expand Down Expand Up @@ -172,5 +185,30 @@ class Settings(
extra="ignore",
)

@model_validator(mode="after")
def validate_environment_settings(self) -> Self:
"The validation should not modify any of the settings. It should provide"
"feedback to the user if any misconfiguration is detected."
if self.ENVIRONMENT == EnvironmentOption.LOCAL:
pass
elif self.ENVIRONMENT == EnvironmentOption.STAGING:
if "*" in self.CORS_ORIGINS:
warnings.warn(
"For security, in a staging environment CORS_ORIGINS should not include '*'. "
"It's recommended to specify explicit origins (e.g., ['https://staging.example.com'])."
)
elif self.ENVIRONMENT == EnvironmentOption.PRODUCTION:
if "*" in self.CORS_ORIGINS:
raise ValueError(
"For security, in a production environment CORS_ORIGINS cannot include '*'. "
"You must specify explicit allowed origins (e.g., ['https://example.com', 'https://www.example.com'])."
)
if self.APP_FRONTEND_HOST and not self.APP_FRONTEND_HOST.startswith("https://"):
raise ValueError(
"In production, APP_FRONTEND_HOST must start with the https:// protocol. "
f"Received the host '{self.APP_FRONTEND_HOST}'."
)
return self


settings = Settings()