Skip to content

Commit 4a5e765

Browse files
committed
feat: Pydantic settings management
1 parent 62c582c commit 4a5e765

7 files changed

Lines changed: 154 additions & 9 deletions

File tree

src/patchwork/__init__.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
import os
2-
3-
CONFIG_FILE = os.path.join(os.path.dirname(__file__), "config.toml")

src/patchwork/app.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
import os
33
import time
44
from tkinter import filedialog, messagebox
5+
from typing import Literal
56

67
import ffmpeg
7-
import inquirer
8-
import toml
98
from pydavinci import davinci
109
from pydavinci.wrappers.marker import Marker
1110
from rich import print
1211
from rich.progress import Progress
13-
from rich.prompt import Confirm
1412

15-
from patchwork import CONFIG_FILE, helpers
16-
from patchwork.constants import STANDARD_RENDER_PRESETS
13+
from patchwork import helpers
14+
from patchwork.settings.manager import settings
1715

1816
resolve = davinci.Resolve()
1917

src/patchwork/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from rich import print
44
from rich.panel import Panel
55

6+
from patchwork.settings.manager import settings
67
typer_app = typer.Typer()
78

89

src/patchwork/settings/__init__.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
import shutil
3+
from pathlib import Path
4+
from rich import print
5+
6+
settings_dir = Path(__file__).parent
7+
8+
default_settings_file = str(
9+
Path(
10+
settings_dir,
11+
"default_settings.toml"
12+
).absolute()
13+
)
14+
15+
user_settings_file = str(Path(settings_dir, "user_settings.toml"))
16+
dotenv_settings_file = str(Path(settings_dir, ".env").absolute())
17+
18+
if not os.path.exists(user_settings_file):
19+
print("[magenta][Initialising 'user_settings.toml']")
20+
shutil.copy(default_settings_file, user_settings_file)

src/patchwork/config.toml renamed to src/patchwork/settings/default_settings.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ loglevel = "INFO" # See Python logging module for supported levels
33
render_directory = "Z:/@FinishedRenders"
44

55
[render]
6-
hide_generic_render_presets = true
6+
allow_generic_render_presets = false
77

88
[advanced]
99
advanced_stuff = false

src/patchwork/settings/manager.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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())
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[app]
2+
loglevel = "INFO" # See Python logging module for supported levels
3+
render_directory = "Z:/@FinishedRenders"
4+
5+
[render]
6+
allow_generic_render_presets = false
7+
8+
[advanced]
9+
advanced_stuff = false

0 commit comments

Comments
 (0)