Metadata-Version: 2.1
Name: wexample-config
Version: 7.2.2
Summary: Defines typed config schemas as named, provider-extensible option trees that parse and validate raw dict configurations
Author-Email: weeger <contact@wexample.com>
License: MIT
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Project-URL: homepage, https://github.com/wexample/python-config
Requires-Python: >=3.10
Requires-Dist: attrs>=23.1.0
Requires-Dist: cattrs>=23.1.0
Requires-Dist: pydantic<3,>=2
Requires-Dist: wexample-helpers>=19.1.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-benchmark>=5.2.3; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Description-Content-Type: text/markdown

# config

Version: 7.2.2

`wexample-config` lets Python developers declare typed configuration schemas as trees of named option classes, then feed raw dicts into them for parsing and validation. Each option enforces a specific type — string, list, dict, union, or a custom `ConfigValue` subclass — and raises on a mismatch or an unexpected key; nested options recurse the same validation down the tree. One or more `AbstractOptionsProvider` classes can be registered on a single `AbstractConfigManager`, so different parts of a codebase can contribute their own option sets without coupling to each other.

## Table of Contents

- [Installation](#installation)
- [Quickstart](#quickstart)
- [Tests](#tests)
- [Architecture](#architecture)
- [Integration in the Suite](#integration-in-the-suite)
- [Dependencies](#dependencies)
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
- [License](#license)
- [About us](#about-us)
- [Known Limitations & Roadmap](#known-limitations--roadmap)
- [Status & Compatibility](#status--compatibility)
- [Useful Links](#useful-links)
- [Migration Notes](#migration-notes)

## Installation

```bash
pip install wexample-config
```

Requires Python >=3.10.

## Quickstart

Install the package:

```bash
pip install wexample-config
```

Three classes are enough to parse and validate a real config dict.

**1. Declare an option.** Subclass `AbstractConfigOption` and return the expected Python type from `get_raw_value_allowed_type`. The option key is derived from the class name automatically — `VersionConfigOption` becomes `"version"`.

**2. Register it in a provider.** Subclass `AbstractOptionsProvider` and list the option classes in `get_options`.

**3. Wire the provider into a manager.** Subclass `AbstractConfigManager` and return the provider from `get_options_providers`.

```python
from typing import Any

from wexample_config.classes.abstract_config_manager import AbstractConfigManager
from wexample_config.config_option.abstract_config_option import AbstractConfigOption
from wexample_config.options_provider.abstract_options_provider import AbstractOptionsProvider

class VersionConfigOption(AbstractConfigOption):
    @staticmethod
    def get_raw_value_allowed_type() -> Any:
        return str

class AppOptionsProvider(AbstractOptionsProvider):
    @classmethod
    def get_options(cls) -> list[type[AbstractConfigOption]]:
        return [VersionConfigOption]

class AppConfigManager(AbstractConfigManager):
    def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
        return [AppOptionsProvider]

manager = AppConfigManager()
manager.set_value({"version": "1.0.0"})

print(manager.get_option_value(VersionConfigOption).get_str())
# 1.0.0
```

`set_value` raises `InvalidOptionException` for any key not declared by a provider, and `NotAllowedVariableTypeException` when the value's type does not match `get_raw_value_allowed_type`. Both checks happen at parse time, not at read time.

`get_option_value` returns a `ConfigValue` wrapper. Call `.get_str()`, `.get_int()`, `.get_list()`, `.is_str()`, etc. to read the underlying value with or without a type assertion. You can also reach the option object directly via `get_option(VersionConfigOption)` and call `.get_value()` on it.

## Tests

This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.

### Installation

First, install the required testing dependencies:
```bash
.venv/bin/python -m pip install pytest pytest-cov
```

### Basic Usage

Run all tests with coverage:
```bash
.venv/bin/python -m pytest --cov --cov-report=html
```

### Common Commands
```bash
# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
```

### Viewing HTML Reports

After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.

### Coverage Threshold

To enforce a minimum coverage percentage:
```bash
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
```

This will cause the test suite to fail if coverage drops below 80%.

## Architecture

`wexample-config` turns raw dicts into validated, typed option trees. The library has four conceptual layers: the **manager** (entry point), **option providers** (schema registries), **config options** (tree nodes), and **config values** (value wrappers).

### Manager

src/wexample_config/classes/abstract_config_manager.py is the public entry point. It extends `AbstractNestedConfigOption` and is decorated with `@base_class` (attrs-backed). Consuming code subclasses it and overrides `get_options_providers` to declare which providers are active:

```python
class AppConfigManager(AbstractConfigManager):
    def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
        return [AppOptionsProvider]
```

Calling `manager.set_value({"key": value})` triggers the full parse-and-validate cycle described below.

### Option providers

src/wexample_config/options_provider/abstract_options_provider.py is a class-level registry. A subclass implements `get_options` and returns a list of option classes. `get_options_registry` caches the `name → class` mapping on the provider class itself, so the dict is built once and reused.

Multiple providers can be active simultaneously on one manager. `AbstractNestedConfigOption.get_options_providers` walks up the parent chain to the root before consulting `self.options_providers`, so providers declared at the manager level apply to the whole tree.

### Config options

src/wexample_config/config_option/abstract_config_option.py is the base for every schema node. It holds:

- `key` — derived automatically from the class name (`VersionConfigOption` → `"version"`) via `HasSnakeShortClassNameClassMixin`.
- `value` / `config_value` — the raw input and its `ConfigValue` wrapper.
- `parent` — set at construction time; `get_root()` memoises the traversal to the root option.

`set_value` validates the raw input against `get_raw_value_allowed_type()` before wrapping it in the appropriate `ConfigValue` subclass. `prepare_value` runs before wrapping to let subclasses normalise the raw value.

src/wexample_config/config_option/abstract_nested_config_option.py extends the base to own child options. Its `set_value` calls `_create_options`, which:

1. Builds the allowed-option registry from all active providers (cached by `(type(self), providers)` tuple).
2. Runs `option_class.resolve_config(config)` for every option class that overrides the base no-op — this lets an option inject extra keys into the raw dict before children are created.
3. Rejects any unknown key that is not in the registry (raises `InvalidOptionException`) unless `allow_undefined_keys=True`, in which case unknown keys are wrapped in a plain `ConfigOption`.
4. Instantiates each option class with the corresponding raw value, attaching `parent=self`.

src/wexample_config/config_option/abstract_list_config_option.py handles options whose raw value is `list[dict]`. Instead of creating a single nested option, it iterates over the list and appends one child instance per item to `self.children`.

src/wexample_config/config_option/config_option.py is the concrete fallback used by `_create_options` when `allow_undefined_keys=True` and a key has no declared class.

### Config values

src/wexample_config/config_value/config_value.py wraps any Python value and provides a uniform API:

- **Type checks**: `is_str()`, `is_int()`, `is_dict()`, `is_list()`, `is_callable()`, …
- **Typed getters**: `get_str()`, `get_int()`, … — raise `TypeError` on mismatch.
- **Safe getters**: `get_str_or_none()`, `get_int_or_default(n)`, …
- **Typed setters**: `set_str(v)`, `set_int(v)`, … — validate before assigning.
- **Conversions**: `to_str()`, `to_int()`, … — call the built-in constructor.

`get_allowed_types` (default `Any`) restricts which Python types the value may carry at construction time; `validate_value_type` enforces this via `wexample_helpers`.

src/wexample_config/config_value/nested_config_value.py is used for dict, list, and tuple values. On init it recursively wraps every nested container in another `NestedConfigValue` and every scalar in a plain `ConfigValue`. This enables `search("a.b.0.c")` (dot-separated path traversal) and `set_by_path` / `update_nested` for mutation.

src/wexample_config/config_value/callback_render_config_value.py holds a `Callable`. `_create_options` calls `render(option)` on it to resolve the callable to a concrete value before the child option is instantiated. `NameConfigOption.resolve_config` uses this to accept `name: lambda opt: ...` in raw configs.

src/wexample_config/config_value/custom_type_config_value.py shows the extension point: overriding `get_allowed_types` to return a concrete type (here `str`) restricts what raw values the wrapper may hold.

src/wexample_config/config_value/config_value_collection.py is a typed container over a list of `ConfigValue` objects. It adds bulk accessors (`get_str_collection()`, `to_int_collection()`, etc.) and `map(fn)`.

### Types and exceptions

src/wexample_config/const/types.py aliases `DictConfig = StringKeysDict` (from `wexample_helpers`). This type is used throughout the library for raw input dicts.

src/wexample_config/exception/invalid_option_exception.py is raised by `_create_options` when a raw key has no matching option class. It extends `UndefinedException` from `wexample_helpers`.

### A call through the stack

`manager.set_value({"name": "app", "version": "1.0"})` takes this path:

1. `AbstractConfigOption.set_value` validates `dict` against `get_raw_value_allowed_type` (`Union[dict, set[...]]`), then wraps the dict in a `ConfigValue`.
2. `AbstractNestedConfigOption.set_value` calls `_create_options` with the same dict.
3. `_create_options` resolves the provider list → builds the allowed-option registry → runs any `resolve_config` hooks → checks for unknown keys → instantiates `NameConfigOption(parent=manager, value="app")` and `VersionConfigOption(parent=manager, value="1.0")`.
4. Each option's `__attrs_post_init__` calls `set_value` on its own raw value, running type validation and wrapping in the option's `ConfigValue` subclass.
5. The finished options sit in `manager.options`, keyed by name. `manager.get_option("name").get_value().get_str()` returns `"app"`.

### Demo layer

`src/wexample_config/demo/` contains a self-contained example: `DemoConfigManager`, `DemoOptionsProvider`, and a set of demo option classes covering the standard patterns (scalar, union, list, nested, extensible, custom value type). The test suite in `tests/test_config_manager.py` exercises all of them and is the fastest way to see each pattern in context.

## Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

### Related Packages

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.

## Dependencies

- attrs: >=23.1.0
- cattrs: >=23.1.0
- pydantic: <3,>=2
- wexample-helpers: >=19.1.0

## Versioning & Compatibility Policy

Wexample packages follow **Semantic Versioning** (SemVer):

- **MAJOR**: Breaking changes
- **MINOR**: New features, backward compatible
- **PATCH**: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

Free to use in both personal and commercial projects.

## About us

[Wexample](https://wexample.com) stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

## Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the [project roadmap](https://github.com/wexample/python-config/issues) for upcoming features and improvements.

## Status & Compatibility

**Maturity**: Production-ready

**Python Support**: >=3.10

**OS Support**: Linux, macOS, Windows

**Status**: Actively maintained

## Useful Links

- **Homepage**: https://github.com/wexample/python-config
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
- **Issue Tracker**: https://github.com/wexample/python-config/issues
- **Discussions**: https://github.com/wexample/python-config/discussions
- **PyPI**: [pypi.org/project/wexample-config](https://pypi.org/project/wexample-config/)

## Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.
