Move to uv/pyproject and overhaul project structure

This commit is contained in:
Keannu Christian Bernasol 2025-09-01 02:10:28 +08:00
commit fbb76f8196
26 changed files with 1981 additions and 0 deletions

132
src/core/config/models.py Normal file
View file

@ -0,0 +1,132 @@
"""
Common model schemas
"""
import re
from typing import Literal
from datetime import timedelta
from pydantic import (
BaseModel,
StrictStr,
EmailStr,
Field,
field_validator,
model_validator,
)
from pydantic_extra_types.timezone_name import TimeZoneName
class Config(BaseModel):
"""
Pydantic Configuration model for Django
"""
SECRET_KEY: StrictStr = Field(
min_length=32, description="Secret key for the API", required=True
)
DEBUG: bool = Field(default=False, description="API debug mode")
TIMEZONE: TimeZoneName = "UTC"
CORS_ORIGINS: list[StrictStr] = Field(
description="Allowed CORS origins for API.", default_factory=list
)
ALLOWED_HOSTS: list[StrictStr] = Field(
description="Allowed hosts by the API.", default_factory=list
)
USE_TZ: bool = Field(
required=True,
default=True,
description="Whether the backend API defaults to using timezone-aware datetimes.",
)
DJANGO_LOG_LEVEL: Literal["INFO", "DEBUG"] = "INFO"
SERVE_MEDIA_LOCALLY: bool = Field(
default=False,
description="Whether to serve media files locally as oppossed to using a cloud storage solution.",
)
SMTP_HOST: StrictStr = Field(
required=True, description="SMTP server address")
SMTP_PORT: int = Field(
default=587, description="SMTP server port (default: 587)")
SMTP_USE_TLS: bool = Field(
default=True, description="Whether to use TLS for SMTP connections"
)
SMTP_AUTH_USERNAME: StrictStr = Field(
required=True, description="SMTP authentication username"
)
SMTP_AUTH_PASSWORD: StrictStr = Field(
required=True, description="SMTP authentication password"
)
SMTP_FROM_ADDRESS: EmailStr = Field(
required=True, description="SMTP from email address"
)
ACCESS_TOKEN_LIFETIME_MINUTES: timedelta = Field(
default=timedelta(minutes=240), description="Access token lifetime in minutes"
)
REFRESH_TOKEN_LIFETIME_DAYS: timedelta = Field(
default=timedelta(days=3), description="Refresh token lifetime in days"
)
@field_validator("CORS_ORIGINS", "ALLOWED_HOSTS", mode="before")
def parse_list(cls, v):
"""
Splits a comma-separated string into a list.
"""
if isinstance(v, str):
return v.split(",")
return v
@field_validator("ACCESS_TOKEN_LIFETIME_MINUTES", mode="before")
def parse_timedelta_minutes(cls, v):
"""
Parse integer values into timedelta objects.
"""
if isinstance(v, str):
return timedelta(minutes=int(v))
return v
@field_validator("REFRESH_TOKEN_LIFETIME_DAYS", mode="before")
def parse_timedelta_days(cls, v):
"""
Parse integer values into timedelta objects.
"""
if isinstance(v, str):
return timedelta(days=int(v))
return v
@model_validator(mode="after")
def derive_token_lifetimes(cls, v):
"""
Sets the appropriate log level based on the DEBUG setting.
"""
if v.DEBUG:
v.DJANGO_LOG_LEVEL = "DEBUG"
else:
v.DJANGO_LOG_LEVEL = "INFO"
return v
@model_validator(mode="after")
def derive_allowed_hosts(cls, v):
"""
Extracts additional hostnames from CORS_ORIGINS to append to ALLOWED_HOSTS.
"""
cors_origins = v.CORS_ORIGINS
allowed_hosts = set(v.ALLOWED_HOSTS or [])
for origin in cors_origins:
match = re.match(r"https?://([^/]+)", origin)
if match and match.group(1): # Ensure match.group(1) is not empty
allowed_hosts.add(match.group(1))
v.ALLOWED_HOSTS = list(allowed_hosts)
return v
@model_validator(mode="after")
def derive_log_level(cls, v):
"""
Sets the appropriate log level based on the DEBUG setting.
"""
if v.DEBUG:
v.DJANGO_LOG_LEVEL = "DEBUG"
else:
v.DJANGO_LOG_LEVEL = "INFO"
return v