A simple, type-safe Python library for managing application configuration from Environment variables and YAML.
ApPySetty uses Python dataclasses as the single definition of your application configuration. It can load values from configuration files and environment variables and generate documentation from the same definition.
Install:
uv add appysettyDefine your configuration and read from Environment:
from dataclasses import dataclass
from appysetty import EnvSource, read_configuration
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
config = read_configuration(
Config,
EnvSource(),
)Or load values from YAML:
host: localhost
port: 8080
debug: falseconfig = read_configuration(
Config,
YamlSource(path="yaml_file.yaml"),
)Or both:
config = read_configuration(
Config,
[YamlSource(path="yaml_file.yaml"), EnvSource()],
)Note
Sources are applied in order. Later sources override values from earlier sources.
And also document your configuration with an example .yaml and a markdown document:
write_configuration_documentation(Config, output_dir=Path("./docs"))ApPySetty uses AppConfigSource as the interface to define loaders. These sources are loaded and applied in the order they are provided.
cfg = read_configuration(
Config, [YamlSource(...), TomlSource(...), EnvSource(...), DictSource(...)]
)In the example above, YAML values are applied first, then environment variables, and finally dictionary values. Later sources override values from earlier sources.
The available sources are:
cfg = read_configuration(Config, EnvSource(prefix="MY_PREFIX"))For every key within the config, the key is converted to UPPER_SNAKE_CASE, the optional prefix is applied and the resulting key is used to read a value from the environment.
Note
The prefix itself will not be converted to UPPER_SNAKE_CASE
cfg = read_configuration(Config, DictSource(input={"key": "val"}))Values are read from the provided dictionary using the configuration field names as keys. Unknown dictionary keys are rejected.
cfg = read_configuration(Config, YamlSource(path="", required=True))If path is specified, that file is used. Otherwise, the first existing file from the following list is used:
config.yml
config.yaml
config/config.yml
config/config.yaml
If required is False, a missing file will simply be ignored. If required is True an AppConfigError is raised. By default required is set to True.
Note
Only flat mappings are allowed and the YAML key must match the config key exactly
cfg = read_configuration(Config, TomlSource(path="", required=True))If path is specified, that file is used. Otherwise, the first existing file from the following list is used:
config.toml
config/config.toml
If required is False, a missing file will simply be ignored. If required is True an AppConfigError is raised. By default required is set to True.
Note
Only flat mappings are allowed and the TOML key must match the config key exactly
All sources are based on the AppConfigSource. To extend the list of sources, you could supply your own implementation:
@dataclass(frozen=True)
class MyOwnSource(AppConfigSource):
"""Example for your source, based on the DictSource"""
input: dict[str, str]
def load(self, config_type_hints):
values: dict[str, object] = {}
for name, value in self.input.items():
...
return valuesCaution
Using your own source might allow for more types then anticipated by the tool. So be careful.
The simplest form of a config class looks like this:
@dataclass
class Config:
host: str = "localhost"
port: int = 8080
debug: bool = False
timeout: float = 5.0Note
As of now, only str, int, float and bool are supported
You can also extend your dataclass with additional information for better documentation and for masking secrets:
@dataclass
class ConfigWithMetadata:
host: Annotated[
str,
AppConfigEntry(description="The application host"),
] = "localhost"
password: Annotated[
str,
AppConfigEntry(description="The database password", is_secret=True),
] = "secret"
debug: bool = FalseBoth variants can be mixed. If no description is provided, the name of the field will be the description.
One feature of this tool is automating the documentation items for configuration options:
config.example.yamlcontaining an example YAML file with default values and descriptive comments (if descriptions were defined)DefaultConfiguration.mdcontaining a table of all options with ENV variant, a docker composeenvironmentblock for docker compose and a docker run example command with all -e set.
To create the documentation, use:
# Create both documents
write_configuration_documentation(
ConfigWithMetadata, env_prefix="MY_APP_PREFIX", output_dir=Path()
)
# Only create YAML example
write_config_yaml_example(ConfigWithMetadata, output_dir=Path())
# Only create markdown document
write_config_markdown(ConfigWithMetadata, env_prefix="MY_APP_PREFIX", output_dir=Path())Note
env_prefix defaults to "" if not set and output_dir defaults to ./docs/config if not set.
Caution
Make sure to always match the env_prefix to the actual prefix used for the EnvSource if applied
Clone the repository and install the development dependencies:
git clone https://github.com/SmartFactory-KL/appysetty.git
cd appysetty
uv syncRun the tests:
uv run pytestRun tests with coverage:
uv run pytest --cov=appysetty --cov-report=term-missingRun the examples:
uv run python -m examples.write_documentation
uv run python -m examples.read_documentationRun Ruff:
uv run ruff check
uv run ruff format .ApPySetty is licensed under the MIT License.