Skip to content

Commit

Permalink
Migrate settings models from boilercv
Browse files Browse the repository at this point in the history
  • Loading branch information
blakeNaccarato committed Aug 3, 2024
1 parent 3c0c69d commit e0dd28f
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 4 deletions.
8 changes: 5 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Ignore test plots
tests/plots

# Ignore schema, which has absolute paths that will vary by system
params_schema.json
# Ignore schemas, which have absolute paths that will vary by system
*schema.json
# Ignore local dev environment settings
*.env
settings*.yaml

# * -------------------------------------------------------------------------------- * #
# * Template gitignore
Expand Down
11 changes: 10 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
// * ----------------------------------------------------------------------------- * #
//! Schema
"yaml.schemas": {
"params_schema.json": "params.yaml"
"params_schema.json": "params.yaml",
// ? `boilercore`
"src/boilercore/settings_schema.json": "src/boilercore/settings.yaml",
"src/boilercore/settings_plugin_schema.json": "src/boilercore/settings_plugin.yaml",
// ? `boilercore_docs`
"docs/boilercore_docs/settings_schema.json": "docs/boilercore_docs/settings.yaml",
"docs/boilercore_docs/settings_plugin_schema.json": "docs/boilercore_docs/settings_plugin.yaml",
// ? `boilercore_tests`
"tests/boilercore_tests/settings_schema.json": "tests/boilercore_tests/settings.yaml",
"tests/boilercore_tests/settings_plugin_schema.json": "tests/boilercore_tests/settings_plugin.yaml"
},
// * ----------------------------------------------------------------------------- * #
//! Terminal
Expand Down
60 changes: 60 additions & 0 deletions docs/boilercore_docs/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Settings."""

from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
)

import boilercore_docs
from boilercore.settings_models import (
customise_sources,
get_settings_paths,
sync_settings_schema,
)

paths = get_settings_paths(boilercore_docs)


class PluginModelConfig(BaseSettings):
"""Pydantic plugin model configuration."""

model_config = SettingsConfigDict(use_attribute_docstrings=True)

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
*_args: PydanticBaseSettingsSource,
**_kwds: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Source settings from init and TOML."""
return customise_sources(settings_cls, init_settings, paths.plugin_settings)


class Settings(BaseSettings):
"""Package settings."""

model_config = SettingsConfigDict(use_attribute_docstrings=True)

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
*_args: PydanticBaseSettingsSource,
**_kwds: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Source settings from init and TOML."""
return customise_sources(settings_cls, init_settings, paths.plugin_settings)


for path, model in zip(
paths.all_dev_settings if paths.in_dev else paths.all_cwd_settings,
(PluginModelConfig, Settings),
strict=True,
):
sync_settings_schema(path, model)

default = Settings()
1 change: 1 addition & 0 deletions docs/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies = [
"matplotlib>=3.7.2",
"numpy>=1.24.4",
"pandas[hdf5,performance]>=2.2.2",
"pydantic-settings>=2.4.0",
"seaborn>=0.13.2",
"sympy>=1.12",
]
Expand Down
58 changes: 58 additions & 0 deletions src/boilercore/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Settings."""

from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
SettingsConfigDict,
)

import boilercore
from boilercore.settings_models import (
customise_sources,
get_settings_paths,
sync_settings_schema,
)

paths = get_settings_paths(boilercore)


class PluginModelConfig(BaseSettings):
"""Pydantic plugin model configuration."""

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
*_args: PydanticBaseSettingsSource,
**_kwds: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Source settings from init and TOML."""
return customise_sources(settings_cls, init_settings, paths.plugin_settings)


class Settings(BaseSettings):
"""Package settings."""

model_config = SettingsConfigDict(use_attribute_docstrings=True)

@classmethod
def settings_customise_sources(
cls,
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
*_args: PydanticBaseSettingsSource,
**_kwds: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
"""Source settings from init and TOML."""
return customise_sources(settings_cls, init_settings, paths.settings)


for path, model in zip(
paths.all_dev_settings if paths.in_dev else paths.all_cwd_settings,
(PluginModelConfig, Settings),
strict=True,
):
sync_settings_schema(path, model)

default = Settings()
107 changes: 107 additions & 0 deletions src/boilercore/settings_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Settings models."""

from collections.abc import Iterable
from json import dumps
from pathlib import Path
from site import getsitepackages
from types import ModuleType

from pydantic import BaseModel
from pydantic_settings import (
BaseSettings,
PydanticBaseSettingsSource,
YamlConfigSettingsSource,
)

from boilercore.paths import get_module_name, get_package_dir


class Paths(BaseModel):
"""Settings model paths."""

cwd_plugin_settings: Path
dev_plugin_settings: Path
plugin_settings: list[Path]

cwd_settings: Path
dev_settings: Path
settings: list[Path]

all_cwd_settings: list[Path]
all_dev_settings: list[Path]

in_dev: bool


def get_settings_paths(module: ModuleType) -> Paths:
"""Get settings model paths."""
package_dir = get_package_dir(module)
package_name = get_module_name(module)
return Paths(
cwd_plugin_settings=(
cwd_plugin_settings := Path.cwd() / f"{package_name}_plugin.yaml"
),
dev_plugin_settings=(
dev_plugin_settings := package_dir / "settings_plugin.yaml"
),
plugin_settings=[cwd_plugin_settings, dev_plugin_settings],
cwd_settings=(cwd_settings := Path.cwd() / Path(f"{package_name}.yaml")),
dev_settings=(dev_settings := package_dir / "settings.yaml"),
settings=[cwd_settings, dev_settings],
all_cwd_settings=[cwd_plugin_settings, cwd_settings],
all_dev_settings=[dev_plugin_settings, dev_settings],
in_dev=not (
getsitepackages() and package_dir.is_relative_to(Path(getsitepackages()[0]))
),
)


def get_yaml_sources(
settings_cls: type[BaseSettings], paths: Iterable[Path], encoding: str = "utf-8"
) -> tuple[YamlConfigSettingsSource, ...]:
"""Source settings from init and TOML."""
sources: list[YamlConfigSettingsSource] = []
for yaml_file in paths:
source = YamlConfigSettingsSource(
settings_cls, yaml_file, yaml_file_encoding=encoding
)
if source.init_kwargs.get("$schema"):
del source.init_kwargs["$schema"]
sources.append(source)
return tuple(sources)


def customise_sources(
settings_cls: type[BaseSettings],
init_settings: PydanticBaseSettingsSource,
yaml_files: Iterable[Path],
encoding: str = "utf-8",
):
"""Source settings from init and YAML."""
return (init_settings, *get_yaml_sources(settings_cls, yaml_files, encoding))


def get_plugin_settings(
package_name: str, config: BaseSettings
) -> dict[str, BaseSettings]:
"""Get Pydantic plugin model configuration.
```Python
model_config = SettingsConfigDict(
plugin_settings={"boilercv_docs": PluginModelConfig()}
)
```
"""
return {package_name: config}


def sync_settings_schema(
path: Path, model: type[BaseModel], encoding: str = "utf-8"
) -> None:
"""Create settings file and update its schema."""
if not path.exists():
path.touch()
schema = path.with_name(f"{path.stem}_schema.json")
schema.write_text(
encoding=encoding, data=f"{dumps(model.model_json_schema(), indent=2)}\n"
)

0 comments on commit e0dd28f

Please sign in to comment.