1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
PyGitWeb settings file: ~/.pygitweb/settings.json (or PYGITWEB_SETTINGS_CONFIG).
On first run, loads from environment and writes the file. When the file exists,
loads from JSON then applies explicit environment/.env overrides (with a warning).
Changes via the settings UI or setattr persist back to the file.
"""
from __future__ import annotations
import json
import sys
from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pygitweb.config import Settings
DEFAULT_SETTINGS_CONFIG_PATH = Path.home() / ".pygitweb" / "settings.json"
_persist_enabled = False
_batch_update = False
_active_path: Path | None = None
_setattr_patched = False
def settings_config_path() -> Path:
explicit = __import__("os").environ.get("PYGITWEB_SETTINGS_CONFIG", "").strip()
if explicit:
return Path(explicit).expanduser()
return DEFAULT_SETTINGS_CONFIG_PATH
def write_settings_file(settings: Settings, path: Path | None = None) -> None:
target = path or _active_path or settings_config_path()
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(
json.dumps(settings.model_dump(mode="json"), indent=2) + "\n",
encoding="utf-8",
)
def _warn_env_overrides(path: Path, fields: list[str]) -> None:
names = ", ".join(fields)
print(
f"WARNING: PyGitWeb: environment overrides settings file {path}\n Overridden field(s): {names}\n",
file=sys.stderr,
)
def _apply_env_overrides(base: Settings) -> list[str]:
from pygitweb.config import Settings
env_settings = Settings()
overrides: list[str] = []
for name in env_settings.model_fields_set:
env_val = getattr(env_settings, name)
if getattr(base, name) == env_val:
continue
setattr(base, name, env_val)
overrides.append(name)
return overrides
def _patch_settings_setattr() -> None:
global _setattr_patched
if _setattr_patched:
return
from pygitweb.config import Settings
original = Settings.__setattr__
def _setattr(self: Settings, name: str, value: object) -> None:
original(self, name, value)
if not _persist_enabled or _batch_update:
return
if name not in Settings.model_fields:
return
write_settings_file(self)
Settings.__setattr__ = _setattr # type: ignore[method-assign]
_setattr_patched = True
def init_settings() -> Settings:
from pygitweb.config import Settings, _finalize_auth_settings
global _persist_enabled, _active_path
_persist_enabled = False
path = settings_config_path()
_active_path = path
if path.is_file():
settings = Settings.model_validate_json(path.read_text(encoding="utf-8"))
overrides = _apply_env_overrides(settings)
if overrides:
_warn_env_overrides(path, overrides)
else:
settings = Settings()
_finalize_auth_settings(settings)
write_settings_file(settings, path)
_patch_settings_setattr()
_persist_enabled = True
return settings
@contextmanager
def settings_batch_update(settings: Settings):
"""Apply multiple setting changes and persist once."""
global _batch_update
_batch_update = True
try:
yield
finally:
_batch_update = False
if _persist_enabled:
write_settings_file(settings)