Source code for app.config.settings

"""Application settings.

Secrets are consumed from env vars.
"""

from __future__ import annotations

import binascii
import os
from dataclasses import dataclass, field
from functools import lru_cache
from pathlib import Path
from typing import TYPE_CHECKING, Final

from litestar.contrib.jinja import JinjaTemplateEngine
from litestar.utils.module_loader import module_to_os_path

if TYPE_CHECKING:
    from litestar.data_extractors import RequestExtractorField, ResponseExtractorField

DEFAULT_MODULE_NAME = "app"
BASE_DIR: Final[Path] = module_to_os_path(DEFAULT_MODULE_NAME)

TRUE_VALUES = {"True", "true", "1", "yes", "Y", "T"}


[docs] @dataclass class ViteSettings: """Server configurations.""" DEV_MODE: bool = field( default_factory=lambda: os.getenv("VITE_DEV_MODE", "False") in TRUE_VALUES, ) """Start ``vite`` development server.""" USE_SERVER_LIFESPAN: bool = field( default_factory=lambda: os.getenv("VITE_USE_SERVER_LIFESPAN", "True") in TRUE_VALUES, ) """Auto start and stop ``vite`` processes when running in development mode..""" HOST: str = field(default_factory=lambda: os.getenv("VITE_HOST", "0.0.0.0")) # noqa: S104 """The host the ``vite`` process will listen on. Defaults to ``0.0.0.0``""" PORT: int = field(default_factory=lambda: int(os.getenv("VITE_PORT", "5173"))) """The port to start vite on. Default to ``5173``""" HOT_RELOAD: bool = field( default_factory=lambda: os.getenv("VITE_HOT_RELOAD", "True") in TRUE_VALUES, ) """Start ``vite`` with HMR enabled.""" ENABLE_REACT_HELPERS: bool = field( default_factory=lambda: os.getenv("VITE_ENABLE_REACT_HELPERS", "True") in TRUE_VALUES, ) """Enable React support in HMR.""" BUNDLE_DIR: Path = field(default_factory=lambda: Path(f"{BASE_DIR}/applets/core/public")) """Bundle directory""" RESOURCE_DIR: Path = field(default_factory=lambda: Path("resources")) """Resource directory""" TEMPLATE_DIR: Path = field(default_factory=lambda: Path(f"{BASE_DIR}/applets/core/templates")) """Template directory.""" ASSET_URL: str = field(default_factory=lambda: os.getenv("ASSET_URL", "/static/")) """Base URL for assets""" @property def set_static_files(self) -> bool: """Serve static assets. Returns: bool: True if the asset URL is a relative path. """ return self.ASSET_URL.startswith("/")
[docs] @dataclass class ServerSettings: """Server configurations.""" APP_LOC: str = "app.asgi:app" """Path to app executable, or factory.""" APP_LOC_IS_FACTORY: bool = False """Indicate if APP_LOC points to an executable or factory.""" HOST: str = field(default_factory=lambda: os.getenv("LITESTAR_HOST", "0.0.0.0")) # noqa: S104 """Server network host.""" PORT: int = field(default_factory=lambda: int(os.getenv("LITESTAR_PORT", "8000"))) """Server port.""" KEEPALIVE: int = field(default_factory=lambda: int(os.getenv("LITESTAR_KEEPALIVE", "65"))) """Seconds to hold connections open (65 is > AWS lb idle timeout).""" RELOAD: bool = field( default_factory=lambda: os.getenv("LITESTAR_RELOAD", "False") in TRUE_VALUES, ) """Turn on hot reloading.""" RELOAD_DIRS: list[str] = field(default_factory=lambda: [f"{BASE_DIR}"]) """Directories to watch for reloading.""" HTTP_WORKERS: int | None = field( default_factory=lambda: int(os.getenv("WEB_CONCURRENCY")) if os.getenv("WEB_CONCURRENCY") is not None else None, # type: ignore[arg-type] ) """Number of HTTP Worker processes to be spawned by Uvicorn."""
[docs] @dataclass class LogSettings: """Logger configuration.""" # https://stackoverflow.com/a/1845097/6560549 EXCLUDE_PATHS: str = r"\A(?!x)x" """Regex to exclude paths from logging.""" HTTP_EVENT: str = "HTTP" """Log event name for logs from Litestar handlers.""" INCLUDE_COMPRESSED_BODY: bool = False """Include ``body`` of compressed responses in log output.""" LEVEL: int = field(default_factory=lambda: int(os.getenv("LOG_LEVEL", "10"))) """Stdlib log levels. Only emit logs at this level, or higher. """ OBFUSCATE_COOKIES: set[str] = field(default_factory=lambda: {"session"}) """Request cookie keys to obfuscate.""" OBFUSCATE_HEADERS: set[str] = field(default_factory=lambda: {"Authorization", "X-API-KEY"}) """Request header keys to obfuscate.""" REQUEST_FIELDS: list[RequestExtractorField] = field( default_factory=lambda: [ "path", "method", "headers", "cookies", "query", "path_params", "body", ], ) """Attributes of the `~litestar.connection.request.Request`_ to be logged.""" RESPONSE_FIELDS: list[ResponseExtractorField] = field( default_factory=lambda: [ "status_code", "cookies", "headers", "body", ], ) """Attributes of the `~litestar.response.Response`_ to be logged.""" GRANIAN_ACCESS_LEVEL: int = 30 """Level to log uvicorn access logs.""" GRANIAN_ERROR_LEVEL: int = 20 """Level to log uvicorn error logs."""
[docs] @dataclass class AppSettings: """Application configuration.""" URL: str = field(default_factory=lambda: os.getenv("APP_URL", "http://localhost:8000")) """The frontend base URL""" DEBUG: bool = field(default_factory=lambda: os.getenv("LITESTAR_DEBUG", "False") in TRUE_VALUES) """Run ``Litestar`` with ``debug=True``.""" SECRET_KEY: str = field( default_factory=lambda: os.getenv("SECRET_KEY", binascii.hexlify(os.urandom(32)).decode(encoding="utf-8")), ) """Application secret key.""" NAME: str = field(default_factory=lambda: "python-source-builder") """Application name."""
[docs] @dataclass class TemplateSettings: """Configures Templating for the project.""" ENGINE: type[JinjaTemplateEngine] = JinjaTemplateEngine """Template engine to use. (Jinja2 or Mako)"""
[docs] @dataclass class Settings: """Application settings.""" app: AppSettings = field(default_factory=AppSettings) template: TemplateSettings = field(default_factory=TemplateSettings) vite: ViteSettings = field(default_factory=ViteSettings) server: ServerSettings = field(default_factory=ServerSettings) log: LogSettings = field(default_factory=LogSettings)
[docs] @classmethod def from_env(cls, dotenv_filename: str = ".env") -> Settings: """Load settings from environment variables. Args: dotenv_filename (str): The name of the dotenv file to load. Assumes ``curdir`` but can pass the rest of the path. Returns: Settings: Application settings """ # noinspection PyProtectedMember from litestar.cli._utils import console env_file = Path(f"{os.curdir}/{dotenv_filename}") if env_file.is_file(): from dotenv import load_dotenv console.print(f"[yellow]Loading environment configuration from {dotenv_filename}[/]") load_dotenv(env_file, override=True) return Settings()
[docs] @lru_cache(maxsize=1, typed=True) def get_settings() -> Settings: """Helper function to get settings from elsewhere. Returns: Settings: Application settings """ return Settings.from_env()