@cache
class Settings(BaseSettings):
core: CoreConfig = Field(default_factory=CoreConfig)
editor: EditorConfig = Field(default_factory=EditorConfig)
theme: ThemeConfig = Field(default_factory=ThemeConfig)
language: LanguageConfig = Field(default_factory=LanguageConfig)
version: str = Field(default=app_version, exclude=True)
config_file: Path = Field(default=CONFIG_FILE, exclude=True)
cache_dir: Path = Field(
default=user_cache_path(app_name, app_author, ensure_exists=True),
exclude=True,
)
model_config = SettingsConfigDict(extra='allow', toml_file=CONFIG_FILE)
@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
env_settings: PydanticBaseSettingsSource,
dotenv_settings: PydanticBaseSettingsSource,
file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
return (TomlConfigSettingsSource(settings_cls),)
@cached_property
def options(self) -> tuple[str, ...]:
return tuple(
option
for key, value in self.model_dump().items()
for option in get_options(value, key)
)
def __getitem__(self, name):
value = self
for attr in name.lower().split('.'):
value = getattr(value, attr)
return value
async def write_config_file(self, config: dict):
@singledispatch
def add(value, doc: tomlkit.TOMLDocument, key: str, comments: str):
doc.add(key, value)
for comment in comments.splitlines():
doc.add(tomlkit.comment(comment))
if comments:
doc.add(tomlkit.nl())
@add.register
def _(
value: BaseModel, doc: tomlkit.TOMLDocument, key: str, comment: str
):
table = tomlkit.table()
if comment:
table.add(tomlkit.comment(comment))
for opt, field in value.model_fields.items():
if value := getattr(value, opt, None):
add(value, table, opt, field.description or '')
doc.add(key, table)
config = lower_keys(config)
doc = tomlkit.document()
for key, field in self.model_fields.items():
if not (value := config.get(key)):
continue
add(value, doc, key, field.description or '')
await AsyncPath(self.config_file).write_text(tomlkit.dumps(doc))
async def save(self, **options):
"""Save config file."""
for opt, val in options.items():
opts = opt.lower().split('.')
key = opts.pop()
obj = self
for attr in opts:
obj = getattr(obj, attr)
setattr(obj, key, val)
config_file = AsyncPath(self.config_file)
configs = {}
if await config_file.exists():
configs = tomlkit.parse(await config_file.read_text())
configs |= lower_keys(self.model_dump())
await self.write_config_file(configs)