|
| 1 | +import os |
| 2 | +from typing import Optional, List |
| 3 | + |
| 4 | +from dataclasses import dataclass, field |
| 5 | +from functools import lru_cache |
| 6 | + |
| 7 | + |
| 8 | +@dataclass |
| 9 | +class DatabaseSettings: |
| 10 | + DSN: Optional[str] = None |
| 11 | + MIN_SIZE: int = 4 |
| 12 | + MAX_SIZE: int = 16 |
| 13 | + MAX_QUERIES: int = 50000 |
| 14 | + MAX_INACTIVE_CONNECTION_LIFETIME: float = 300.0 |
| 15 | + |
| 16 | + def __post_init__(self): |
| 17 | + self.DSN = self.DSN or os.getenv("DATABASE_DSN") |
| 18 | + self.MIN_SIZE = int(os.getenv("DATABASE_MIN_SIZE", self.MIN_SIZE)) |
| 19 | + self.MAX_SIZE = int(os.getenv("DATABASE_MAX_SIZE", self.MAX_SIZE)) |
| 20 | + self.MAX_QUERIES = int(os.getenv("DATABASE_MAX_QUERIES", self.MAX_QUERIES)) |
| 21 | + self.MAX_INACTIVE_CONNECTION_LIFETIME = float( |
| 22 | + os.getenv( |
| 23 | + "DATABASE_MAX_INACTIVE_CONNECTION_LIFETIME", |
| 24 | + self.MAX_INACTIVE_CONNECTION_LIFETIME, |
| 25 | + ) |
| 26 | + ) |
| 27 | + |
| 28 | + |
| 29 | +@dataclass |
| 30 | +class AppSettings: |
| 31 | + SECRET_KEY: Optional[str] = None |
| 32 | + ALLOWED_CORS_ORIGINS: List[str] = field(default_factory=list) |
| 33 | + CSRF_COOKIE_NAME: str = "XSRF-TOKEN" |
| 34 | + CSRF_COOKIE_SECURE: bool = False |
| 35 | + JWT_ALGORITHM: str = "HS256" |
| 36 | + SESSION_SALT: Optional[str] = None |
| 37 | + PBKDF2_ITERATIONS: int = 600_000 |
| 38 | + PBKDF2_ALGORITHM: str = "sha256" |
| 39 | + MAX_FINGERPRINT_VALUE: int = 100_000_000 |
| 40 | + BCRYPT_GENSALT: int = 12 |
| 41 | + |
| 42 | + def __post_init__(self): |
| 43 | + self.SECRET_KEY = self.SECRET_KEY or os.getenv("SECRET_KEY") |
| 44 | + self.SESSION_SALT = self.SESSION_SALT or os.getenv("SESSION_SALT") |
| 45 | + self.PBKDF2_ITERATIONS = int(os.getenv("PBKDF2_ITERATIONS", self.PBKDF2_ITERATIONS)) |
| 46 | + self.PBKDF2_ALGORITHM = os.getenv("PBKDF2_ALGORITHM", self.PBKDF2_ALGORITHM) |
| 47 | + self.MAX_FINGERPRINT_VALUE = int(os.getenv("MAX_FINGERPRINT_VALUE", self.MAX_FINGERPRINT_VALUE)) |
| 48 | + self.BCRYPT_GENSALT = int(os.getenv("BCRYPT_GENSALT", self.BCRYPT_GENSALT)) |
| 49 | + |
| 50 | + if not self.ALLOWED_CORS_ORIGINS: |
| 51 | + cors_origins = os.getenv("ALLOWED_CORS_ORIGINS") |
| 52 | + if cors_origins: |
| 53 | + self.ALLOWED_CORS_ORIGINS = [ |
| 54 | + origin.strip() for origin in cors_origins.split(",") |
| 55 | + ] |
| 56 | + else: |
| 57 | + self.ALLOWED_CORS_ORIGINS = ["*"] |
| 58 | + |
| 59 | + self.CSRF_COOKIE_NAME = os.getenv("CSRF_COOKIE_NAME", self.CSRF_COOKIE_NAME) |
| 60 | + self.CSRF_COOKIE_SECURE = os.getenv("CSRF_COOKIE_SECURE", "").lower() in ( |
| 61 | + "true", |
| 62 | + "1", |
| 63 | + "yes", |
| 64 | + ) |
| 65 | + self.JWT_ALGORITHM = os.getenv("JWT_ALGORITHM", self.JWT_ALGORITHM) |
| 66 | + |
| 67 | + |
| 68 | +@dataclass |
| 69 | +class Settings: |
| 70 | + app: AppSettings = field(default_factory=AppSettings) |
| 71 | + db: DatabaseSettings = field(default_factory=DatabaseSettings) |
| 72 | + |
| 73 | + @classmethod |
| 74 | + def from_env(cls, dotenv_filename: str = ".env") -> "Settings": |
| 75 | + from pathlib import Path |
| 76 | + from dotenv import load_dotenv |
| 77 | + |
| 78 | + env_file = Path(f"{os.curdir}/{dotenv_filename}") |
| 79 | + if env_file.is_file(): |
| 80 | + load_dotenv(env_file, override=True) |
| 81 | + return Settings() |
| 82 | + |
| 83 | + |
| 84 | +@lru_cache(maxsize=1, typed=True) |
| 85 | +def get_settings() -> Settings: |
| 86 | + return Settings.from_env() |
0 commit comments