|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import logging |
| 4 | +import os |
| 5 | +import pathlib |
| 6 | + |
| 7 | +import rich.traceback |
| 8 | +import rtoml |
| 9 | +from pydantic import (BaseModel, BaseSettings, Field, |
| 10 | + ValidationError, validator) |
| 11 | +from pydantic.env_settings import SettingsSourceCallable |
| 12 | +from rich import print |
| 13 | +from rich.panel import Panel |
| 14 | + |
| 15 | +from patchwork.settings import dotenv_settings_file, user_settings_file |
| 16 | + |
| 17 | +logger = logging.getLogger("proxima") |
| 18 | +rich.traceback.install(show_locals=False) |
| 19 | + |
| 20 | + |
| 21 | +def load_toml_user(_): |
| 22 | + user_toml = pathlib.Path(user_settings_file) |
| 23 | + return rtoml.load(user_toml.read_text()) |
| 24 | + |
| 25 | + |
| 26 | +class App(BaseModel): |
| 27 | + loglevel: str = Field( |
| 28 | + ..., |
| 29 | + description="General application loglevel", |
| 30 | + ) |
| 31 | + render_directory: str = Field( |
| 32 | + ..., |
| 33 | + description="Root directory for rendered files and sidecars", |
| 34 | + ) |
| 35 | + |
| 36 | + @validator("loglevel") |
| 37 | + def must_be_valid_loglevel(cls, v): |
| 38 | + valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] |
| 39 | + if v not in valid_levels: |
| 40 | + raise ValueError( |
| 41 | + f"'{v}' is not a valid loglevel. " |
| 42 | + f"Choose from [cyan]{', '.join(valid_levels)}[/]" |
| 43 | + ) |
| 44 | + return v |
| 45 | + |
| 46 | + @validator("render_directory") |
| 47 | + def dir_is_writable(cls, v): |
| 48 | + if not os.path.exists(v): |
| 49 | + raise FileNotFoundError( |
| 50 | + f"Render directory '{v}' does not exist! " |
| 51 | + "Please create the directory or pick another one." |
| 52 | + ) |
| 53 | + |
| 54 | + if not os.access(v, os.W_OK): |
| 55 | + raise PermissionError( |
| 56 | + f"Render directory '{v}' is unwritable! " |
| 57 | + "Please change permissions or pick another directory." |
| 58 | + ) |
| 59 | + return v |
| 60 | + |
| 61 | + |
| 62 | +class Render(BaseModel): |
| 63 | + allow_generic_render_presets: bool = Field( |
| 64 | + ..., description="Allow rendering with a non-custom render preset" |
| 65 | + ) |
| 66 | + |
| 67 | + |
| 68 | +class Advanced(BaseModel): |
| 69 | + advanced_stuff: bool = Field( |
| 70 | + ..., description="Ffmpeg's internal loglevel visible in worker output" |
| 71 | + ) |
| 72 | + |
| 73 | + |
| 74 | +class Settings(BaseSettings): |
| 75 | + app: App |
| 76 | + render: Render |
| 77 | + advanced: Advanced |
| 78 | + |
| 79 | + class Config: |
| 80 | + env_file = dotenv_settings_file |
| 81 | + env_file_encoding = "utf-8" |
| 82 | + env_prefix = "PATCHWORK" |
| 83 | + env_nested_delimiter = "__" |
| 84 | + |
| 85 | + @classmethod |
| 86 | + def customise_sources( |
| 87 | + cls, |
| 88 | + init_settings, |
| 89 | + env_settings, |
| 90 | + file_secret_settings, |
| 91 | + ) -> tuple[SettingsSourceCallable, ...]: |
| 92 | + return ( |
| 93 | + env_settings, |
| 94 | + load_toml_user, |
| 95 | + file_secret_settings, |
| 96 | + init_settings, |
| 97 | + ) |
| 98 | + |
| 99 | + |
| 100 | +settings = None |
| 101 | + |
| 102 | + |
| 103 | +try: |
| 104 | + settings = Settings() # type: ignore |
| 105 | +except ValidationError as e: |
| 106 | + print( |
| 107 | + Panel( |
| 108 | + title="[red]Uh, oh! Invalid user settings", |
| 109 | + title_align="left", |
| 110 | + highlight=True, |
| 111 | + expand=False, |
| 112 | + renderable=f"\n{str(e)}\n\nRun 'Proxima config --help' " |
| 113 | + "to see how to fix broken settings.", |
| 114 | + ) |
| 115 | + ) |
| 116 | + exit() |
| 117 | + |
| 118 | +if __name__ == "__main__": |
| 119 | + if settings: |
| 120 | + print(settings.dict()) |
0 commit comments