wexample-config 7.2.0__tar.gz → 7.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wexample_config-7.2.2/PKG-INFO +301 -0
- wexample_config-7.2.2/README.md +280 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/pyproject.toml +3 -3
- wexample_config-7.2.0/tests/__pycache__/__init__.py → wexample_config-7.2.2/tests/.gitkeep +0 -0
- wexample_config-7.2.2/tests/__pycache__/__init__.py +0 -0
- wexample_config-7.2.0/PKG-INFO +0 -475
- wexample_config-7.2.0/README.md +0 -454
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/abstract_config_manager.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_list_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_nested_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/children_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/name_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/callback_render_config_value.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/config_value.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/config_value_collection.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/custom_type_config_value.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/nested_config_value.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/types.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_custom_value_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_dict_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_dict_extensible_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_extensible_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_list_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_nested_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_union_config_option.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/demo_config_manager.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/option_provider/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/option_provider/demo_options_provider.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/invalid_option_exception.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/__pycache__/__init__.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/abstract_options_provider.py +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/py.typed +0 -0
- {wexample_config-7.2.0 → wexample_config-7.2.2}/tests/test_config_manager.py +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: wexample-config
|
|
3
|
+
Version: 7.2.2
|
|
4
|
+
Summary: Defines typed config schemas as named, provider-extensible option trees that parse and validate raw dict configurations
|
|
5
|
+
Author-Email: weeger <contact@wexample.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Project-URL: homepage, https://github.com/wexample/python-config
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Requires-Dist: attrs>=23.1.0
|
|
13
|
+
Requires-Dist: cattrs>=23.1.0
|
|
14
|
+
Requires-Dist: pydantic<3,>=2
|
|
15
|
+
Requires-Dist: wexample-helpers>=19.1.0
|
|
16
|
+
Provides-Extra: dev
|
|
17
|
+
Requires-Dist: pytest; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-benchmark>=5.2.3; extra == "dev"
|
|
19
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# config
|
|
23
|
+
|
|
24
|
+
Version: 7.2.2
|
|
25
|
+
|
|
26
|
+
`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.
|
|
27
|
+
|
|
28
|
+
## Table of Contents
|
|
29
|
+
|
|
30
|
+
- [Installation](#installation)
|
|
31
|
+
- [Quickstart](#quickstart)
|
|
32
|
+
- [Tests](#tests)
|
|
33
|
+
- [Architecture](#architecture)
|
|
34
|
+
- [Integration in the Suite](#integration-in-the-suite)
|
|
35
|
+
- [Dependencies](#dependencies)
|
|
36
|
+
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
|
|
37
|
+
- [License](#license)
|
|
38
|
+
- [About us](#about-us)
|
|
39
|
+
- [Known Limitations & Roadmap](#known-limitations--roadmap)
|
|
40
|
+
- [Status & Compatibility](#status--compatibility)
|
|
41
|
+
- [Useful Links](#useful-links)
|
|
42
|
+
- [Migration Notes](#migration-notes)
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install wexample-config
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Requires Python >=3.10.
|
|
51
|
+
|
|
52
|
+
## Quickstart
|
|
53
|
+
|
|
54
|
+
Install the package:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install wexample-config
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Three classes are enough to parse and validate a real config dict.
|
|
61
|
+
|
|
62
|
+
**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"`.
|
|
63
|
+
|
|
64
|
+
**2. Register it in a provider.** Subclass `AbstractOptionsProvider` and list the option classes in `get_options`.
|
|
65
|
+
|
|
66
|
+
**3. Wire the provider into a manager.** Subclass `AbstractConfigManager` and return the provider from `get_options_providers`.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from typing import Any
|
|
70
|
+
|
|
71
|
+
from wexample_config.classes.abstract_config_manager import AbstractConfigManager
|
|
72
|
+
from wexample_config.config_option.abstract_config_option import AbstractConfigOption
|
|
73
|
+
from wexample_config.options_provider.abstract_options_provider import AbstractOptionsProvider
|
|
74
|
+
|
|
75
|
+
class VersionConfigOption(AbstractConfigOption):
|
|
76
|
+
@staticmethod
|
|
77
|
+
def get_raw_value_allowed_type() -> Any:
|
|
78
|
+
return str
|
|
79
|
+
|
|
80
|
+
class AppOptionsProvider(AbstractOptionsProvider):
|
|
81
|
+
@classmethod
|
|
82
|
+
def get_options(cls) -> list[type[AbstractConfigOption]]:
|
|
83
|
+
return [VersionConfigOption]
|
|
84
|
+
|
|
85
|
+
class AppConfigManager(AbstractConfigManager):
|
|
86
|
+
def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
|
|
87
|
+
return [AppOptionsProvider]
|
|
88
|
+
|
|
89
|
+
manager = AppConfigManager()
|
|
90
|
+
manager.set_value({"version": "1.0.0"})
|
|
91
|
+
|
|
92
|
+
print(manager.get_option_value(VersionConfigOption).get_str())
|
|
93
|
+
# 1.0.0
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`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.
|
|
97
|
+
|
|
98
|
+
`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.
|
|
99
|
+
|
|
100
|
+
## Tests
|
|
101
|
+
|
|
102
|
+
This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
|
|
103
|
+
|
|
104
|
+
### Installation
|
|
105
|
+
|
|
106
|
+
First, install the required testing dependencies:
|
|
107
|
+
```bash
|
|
108
|
+
.venv/bin/python -m pip install pytest pytest-cov
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Basic Usage
|
|
112
|
+
|
|
113
|
+
Run all tests with coverage:
|
|
114
|
+
```bash
|
|
115
|
+
.venv/bin/python -m pytest --cov --cov-report=html
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Common Commands
|
|
119
|
+
```bash
|
|
120
|
+
# Run tests with coverage for a specific module
|
|
121
|
+
.venv/bin/python -m pytest --cov=your_module
|
|
122
|
+
|
|
123
|
+
# Show which lines are not covered
|
|
124
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
|
|
125
|
+
|
|
126
|
+
# Generate an HTML coverage report
|
|
127
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=html
|
|
128
|
+
|
|
129
|
+
# Combine terminal and HTML reports
|
|
130
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
|
|
131
|
+
|
|
132
|
+
# Run specific test file with coverage
|
|
133
|
+
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Viewing HTML Reports
|
|
137
|
+
|
|
138
|
+
After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
|
|
139
|
+
|
|
140
|
+
### Coverage Threshold
|
|
141
|
+
|
|
142
|
+
To enforce a minimum coverage percentage:
|
|
143
|
+
```bash
|
|
144
|
+
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This will cause the test suite to fail if coverage drops below 80%.
|
|
148
|
+
|
|
149
|
+
## Architecture
|
|
150
|
+
|
|
151
|
+
`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).
|
|
152
|
+
|
|
153
|
+
### Manager
|
|
154
|
+
|
|
155
|
+
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:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
class AppConfigManager(AbstractConfigManager):
|
|
159
|
+
def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
|
|
160
|
+
return [AppOptionsProvider]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Calling `manager.set_value({"key": value})` triggers the full parse-and-validate cycle described below.
|
|
164
|
+
|
|
165
|
+
### Option providers
|
|
166
|
+
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
### Config options
|
|
172
|
+
|
|
173
|
+
src/wexample_config/config_option/abstract_config_option.py is the base for every schema node. It holds:
|
|
174
|
+
|
|
175
|
+
- `key` — derived automatically from the class name (`VersionConfigOption` → `"version"`) via `HasSnakeShortClassNameClassMixin`.
|
|
176
|
+
- `value` / `config_value` — the raw input and its `ConfigValue` wrapper.
|
|
177
|
+
- `parent` — set at construction time; `get_root()` memoises the traversal to the root option.
|
|
178
|
+
|
|
179
|
+
`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.
|
|
180
|
+
|
|
181
|
+
src/wexample_config/config_option/abstract_nested_config_option.py extends the base to own child options. Its `set_value` calls `_create_options`, which:
|
|
182
|
+
|
|
183
|
+
1. Builds the allowed-option registry from all active providers (cached by `(type(self), providers)` tuple).
|
|
184
|
+
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.
|
|
185
|
+
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`.
|
|
186
|
+
4. Instantiates each option class with the corresponding raw value, attaching `parent=self`.
|
|
187
|
+
|
|
188
|
+
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`.
|
|
189
|
+
|
|
190
|
+
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.
|
|
191
|
+
|
|
192
|
+
### Config values
|
|
193
|
+
|
|
194
|
+
src/wexample_config/config_value/config_value.py wraps any Python value and provides a uniform API:
|
|
195
|
+
|
|
196
|
+
- **Type checks**: `is_str()`, `is_int()`, `is_dict()`, `is_list()`, `is_callable()`, …
|
|
197
|
+
- **Typed getters**: `get_str()`, `get_int()`, … — raise `TypeError` on mismatch.
|
|
198
|
+
- **Safe getters**: `get_str_or_none()`, `get_int_or_default(n)`, …
|
|
199
|
+
- **Typed setters**: `set_str(v)`, `set_int(v)`, … — validate before assigning.
|
|
200
|
+
- **Conversions**: `to_str()`, `to_int()`, … — call the built-in constructor.
|
|
201
|
+
|
|
202
|
+
`get_allowed_types` (default `Any`) restricts which Python types the value may carry at construction time; `validate_value_type` enforces this via `wexample_helpers`.
|
|
203
|
+
|
|
204
|
+
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.
|
|
205
|
+
|
|
206
|
+
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.
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
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)`.
|
|
211
|
+
|
|
212
|
+
### Types and exceptions
|
|
213
|
+
|
|
214
|
+
src/wexample_config/const/types.py aliases `DictConfig = StringKeysDict` (from `wexample_helpers`). This type is used throughout the library for raw input dicts.
|
|
215
|
+
|
|
216
|
+
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`.
|
|
217
|
+
|
|
218
|
+
### A call through the stack
|
|
219
|
+
|
|
220
|
+
`manager.set_value({"name": "app", "version": "1.0"})` takes this path:
|
|
221
|
+
|
|
222
|
+
1. `AbstractConfigOption.set_value` validates `dict` against `get_raw_value_allowed_type` (`Union[dict, set[...]]`), then wraps the dict in a `ConfigValue`.
|
|
223
|
+
2. `AbstractNestedConfigOption.set_value` calls `_create_options` with the same dict.
|
|
224
|
+
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")`.
|
|
225
|
+
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.
|
|
226
|
+
5. The finished options sit in `manager.options`, keyed by name. `manager.get_option("name").get_value().get_str()` returns `"app"`.
|
|
227
|
+
|
|
228
|
+
### Demo layer
|
|
229
|
+
|
|
230
|
+
`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.
|
|
231
|
+
|
|
232
|
+
## Integration in the Suite
|
|
233
|
+
|
|
234
|
+
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.
|
|
235
|
+
|
|
236
|
+
### Related Packages
|
|
237
|
+
|
|
238
|
+
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.
|
|
239
|
+
|
|
240
|
+
Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
|
|
241
|
+
|
|
242
|
+
## Dependencies
|
|
243
|
+
|
|
244
|
+
- attrs: >=23.1.0
|
|
245
|
+
- cattrs: >=23.1.0
|
|
246
|
+
- pydantic: <3,>=2
|
|
247
|
+
- wexample-helpers: >=19.1.0
|
|
248
|
+
|
|
249
|
+
## Versioning & Compatibility Policy
|
|
250
|
+
|
|
251
|
+
Wexample packages follow **Semantic Versioning** (SemVer):
|
|
252
|
+
|
|
253
|
+
- **MAJOR**: Breaking changes
|
|
254
|
+
- **MINOR**: New features, backward compatible
|
|
255
|
+
- **PATCH**: Bug fixes, backward compatible
|
|
256
|
+
|
|
257
|
+
We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
262
|
+
|
|
263
|
+
Free to use in both personal and commercial projects.
|
|
264
|
+
|
|
265
|
+
## About us
|
|
266
|
+
|
|
267
|
+
[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.
|
|
268
|
+
|
|
269
|
+
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.
|
|
270
|
+
|
|
271
|
+
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.
|
|
272
|
+
|
|
273
|
+
## Known Limitations & Roadmap
|
|
274
|
+
|
|
275
|
+
Current limitations and planned features are tracked in the GitHub issues.
|
|
276
|
+
|
|
277
|
+
See the [project roadmap](https://github.com/wexample/python-config/issues) for upcoming features and improvements.
|
|
278
|
+
|
|
279
|
+
## Status & Compatibility
|
|
280
|
+
|
|
281
|
+
**Maturity**: Production-ready
|
|
282
|
+
|
|
283
|
+
**Python Support**: >=3.10
|
|
284
|
+
|
|
285
|
+
**OS Support**: Linux, macOS, Windows
|
|
286
|
+
|
|
287
|
+
**Status**: Actively maintained
|
|
288
|
+
|
|
289
|
+
## Useful Links
|
|
290
|
+
|
|
291
|
+
- **Homepage**: https://github.com/wexample/python-config
|
|
292
|
+
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
|
|
293
|
+
- **Issue Tracker**: https://github.com/wexample/python-config/issues
|
|
294
|
+
- **Discussions**: https://github.com/wexample/python-config/discussions
|
|
295
|
+
- **PyPI**: [pypi.org/project/wexample-config](https://pypi.org/project/wexample-config/)
|
|
296
|
+
|
|
297
|
+
## Migration Notes
|
|
298
|
+
|
|
299
|
+
When upgrading between major versions, refer to the migration guides in the documentation.
|
|
300
|
+
|
|
301
|
+
Breaking changes are clearly documented with upgrade paths and examples.
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# config
|
|
2
|
+
|
|
3
|
+
Version: 7.2.2
|
|
4
|
+
|
|
5
|
+
`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.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [Quickstart](#quickstart)
|
|
11
|
+
- [Tests](#tests)
|
|
12
|
+
- [Architecture](#architecture)
|
|
13
|
+
- [Integration in the Suite](#integration-in-the-suite)
|
|
14
|
+
- [Dependencies](#dependencies)
|
|
15
|
+
- [Versioning & Compatibility Policy](#versioning--compatibility-policy)
|
|
16
|
+
- [License](#license)
|
|
17
|
+
- [About us](#about-us)
|
|
18
|
+
- [Known Limitations & Roadmap](#known-limitations--roadmap)
|
|
19
|
+
- [Status & Compatibility](#status--compatibility)
|
|
20
|
+
- [Useful Links](#useful-links)
|
|
21
|
+
- [Migration Notes](#migration-notes)
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install wexample-config
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Requires Python >=3.10.
|
|
30
|
+
|
|
31
|
+
## Quickstart
|
|
32
|
+
|
|
33
|
+
Install the package:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install wexample-config
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Three classes are enough to parse and validate a real config dict.
|
|
40
|
+
|
|
41
|
+
**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"`.
|
|
42
|
+
|
|
43
|
+
**2. Register it in a provider.** Subclass `AbstractOptionsProvider` and list the option classes in `get_options`.
|
|
44
|
+
|
|
45
|
+
**3. Wire the provider into a manager.** Subclass `AbstractConfigManager` and return the provider from `get_options_providers`.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from typing import Any
|
|
49
|
+
|
|
50
|
+
from wexample_config.classes.abstract_config_manager import AbstractConfigManager
|
|
51
|
+
from wexample_config.config_option.abstract_config_option import AbstractConfigOption
|
|
52
|
+
from wexample_config.options_provider.abstract_options_provider import AbstractOptionsProvider
|
|
53
|
+
|
|
54
|
+
class VersionConfigOption(AbstractConfigOption):
|
|
55
|
+
@staticmethod
|
|
56
|
+
def get_raw_value_allowed_type() -> Any:
|
|
57
|
+
return str
|
|
58
|
+
|
|
59
|
+
class AppOptionsProvider(AbstractOptionsProvider):
|
|
60
|
+
@classmethod
|
|
61
|
+
def get_options(cls) -> list[type[AbstractConfigOption]]:
|
|
62
|
+
return [VersionConfigOption]
|
|
63
|
+
|
|
64
|
+
class AppConfigManager(AbstractConfigManager):
|
|
65
|
+
def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
|
|
66
|
+
return [AppOptionsProvider]
|
|
67
|
+
|
|
68
|
+
manager = AppConfigManager()
|
|
69
|
+
manager.set_value({"version": "1.0.0"})
|
|
70
|
+
|
|
71
|
+
print(manager.get_option_value(VersionConfigOption).get_str())
|
|
72
|
+
# 1.0.0
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`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.
|
|
76
|
+
|
|
77
|
+
`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.
|
|
78
|
+
|
|
79
|
+
## Tests
|
|
80
|
+
|
|
81
|
+
This project uses `pytest` for testing and `pytest-cov` for code coverage analysis.
|
|
82
|
+
|
|
83
|
+
### Installation
|
|
84
|
+
|
|
85
|
+
First, install the required testing dependencies:
|
|
86
|
+
```bash
|
|
87
|
+
.venv/bin/python -m pip install pytest pytest-cov
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Basic Usage
|
|
91
|
+
|
|
92
|
+
Run all tests with coverage:
|
|
93
|
+
```bash
|
|
94
|
+
.venv/bin/python -m pytest --cov --cov-report=html
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Common Commands
|
|
98
|
+
```bash
|
|
99
|
+
# Run tests with coverage for a specific module
|
|
100
|
+
.venv/bin/python -m pytest --cov=your_module
|
|
101
|
+
|
|
102
|
+
# Show which lines are not covered
|
|
103
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing
|
|
104
|
+
|
|
105
|
+
# Generate an HTML coverage report
|
|
106
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=html
|
|
107
|
+
|
|
108
|
+
# Combine terminal and HTML reports
|
|
109
|
+
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html
|
|
110
|
+
|
|
111
|
+
# Run specific test file with coverage
|
|
112
|
+
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Viewing HTML Reports
|
|
116
|
+
|
|
117
|
+
After generating an HTML report, open `htmlcov/index.html` in your browser to view detailed line-by-line coverage information.
|
|
118
|
+
|
|
119
|
+
### Coverage Threshold
|
|
120
|
+
|
|
121
|
+
To enforce a minimum coverage percentage:
|
|
122
|
+
```bash
|
|
123
|
+
.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This will cause the test suite to fail if coverage drops below 80%.
|
|
127
|
+
|
|
128
|
+
## Architecture
|
|
129
|
+
|
|
130
|
+
`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).
|
|
131
|
+
|
|
132
|
+
### Manager
|
|
133
|
+
|
|
134
|
+
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:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
class AppConfigManager(AbstractConfigManager):
|
|
138
|
+
def get_options_providers(self) -> list[type[AbstractOptionsProvider]]:
|
|
139
|
+
return [AppOptionsProvider]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Calling `manager.set_value({"key": value})` triggers the full parse-and-validate cycle described below.
|
|
143
|
+
|
|
144
|
+
### Option providers
|
|
145
|
+
|
|
146
|
+
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.
|
|
147
|
+
|
|
148
|
+
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.
|
|
149
|
+
|
|
150
|
+
### Config options
|
|
151
|
+
|
|
152
|
+
src/wexample_config/config_option/abstract_config_option.py is the base for every schema node. It holds:
|
|
153
|
+
|
|
154
|
+
- `key` — derived automatically from the class name (`VersionConfigOption` → `"version"`) via `HasSnakeShortClassNameClassMixin`.
|
|
155
|
+
- `value` / `config_value` — the raw input and its `ConfigValue` wrapper.
|
|
156
|
+
- `parent` — set at construction time; `get_root()` memoises the traversal to the root option.
|
|
157
|
+
|
|
158
|
+
`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.
|
|
159
|
+
|
|
160
|
+
src/wexample_config/config_option/abstract_nested_config_option.py extends the base to own child options. Its `set_value` calls `_create_options`, which:
|
|
161
|
+
|
|
162
|
+
1. Builds the allowed-option registry from all active providers (cached by `(type(self), providers)` tuple).
|
|
163
|
+
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.
|
|
164
|
+
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`.
|
|
165
|
+
4. Instantiates each option class with the corresponding raw value, attaching `parent=self`.
|
|
166
|
+
|
|
167
|
+
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`.
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
### Config values
|
|
172
|
+
|
|
173
|
+
src/wexample_config/config_value/config_value.py wraps any Python value and provides a uniform API:
|
|
174
|
+
|
|
175
|
+
- **Type checks**: `is_str()`, `is_int()`, `is_dict()`, `is_list()`, `is_callable()`, …
|
|
176
|
+
- **Typed getters**: `get_str()`, `get_int()`, … — raise `TypeError` on mismatch.
|
|
177
|
+
- **Safe getters**: `get_str_or_none()`, `get_int_or_default(n)`, …
|
|
178
|
+
- **Typed setters**: `set_str(v)`, `set_int(v)`, … — validate before assigning.
|
|
179
|
+
- **Conversions**: `to_str()`, `to_int()`, … — call the built-in constructor.
|
|
180
|
+
|
|
181
|
+
`get_allowed_types` (default `Any`) restricts which Python types the value may carry at construction time; `validate_value_type` enforces this via `wexample_helpers`.
|
|
182
|
+
|
|
183
|
+
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.
|
|
184
|
+
|
|
185
|
+
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.
|
|
186
|
+
|
|
187
|
+
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.
|
|
188
|
+
|
|
189
|
+
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)`.
|
|
190
|
+
|
|
191
|
+
### Types and exceptions
|
|
192
|
+
|
|
193
|
+
src/wexample_config/const/types.py aliases `DictConfig = StringKeysDict` (from `wexample_helpers`). This type is used throughout the library for raw input dicts.
|
|
194
|
+
|
|
195
|
+
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`.
|
|
196
|
+
|
|
197
|
+
### A call through the stack
|
|
198
|
+
|
|
199
|
+
`manager.set_value({"name": "app", "version": "1.0"})` takes this path:
|
|
200
|
+
|
|
201
|
+
1. `AbstractConfigOption.set_value` validates `dict` against `get_raw_value_allowed_type` (`Union[dict, set[...]]`), then wraps the dict in a `ConfigValue`.
|
|
202
|
+
2. `AbstractNestedConfigOption.set_value` calls `_create_options` with the same dict.
|
|
203
|
+
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")`.
|
|
204
|
+
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.
|
|
205
|
+
5. The finished options sit in `manager.options`, keyed by name. `manager.get_option("name").get_value().get_str()` returns `"app"`.
|
|
206
|
+
|
|
207
|
+
### Demo layer
|
|
208
|
+
|
|
209
|
+
`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.
|
|
210
|
+
|
|
211
|
+
## Integration in the Suite
|
|
212
|
+
|
|
213
|
+
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.
|
|
214
|
+
|
|
215
|
+
### Related Packages
|
|
216
|
+
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
Visit the [Wexample Suite documentation](https://docs.wexample.com) for the complete package ecosystem.
|
|
220
|
+
|
|
221
|
+
## Dependencies
|
|
222
|
+
|
|
223
|
+
- attrs: >=23.1.0
|
|
224
|
+
- cattrs: >=23.1.0
|
|
225
|
+
- pydantic: <3,>=2
|
|
226
|
+
- wexample-helpers: >=19.1.0
|
|
227
|
+
|
|
228
|
+
## Versioning & Compatibility Policy
|
|
229
|
+
|
|
230
|
+
Wexample packages follow **Semantic Versioning** (SemVer):
|
|
231
|
+
|
|
232
|
+
- **MAJOR**: Breaking changes
|
|
233
|
+
- **MINOR**: New features, backward compatible
|
|
234
|
+
- **PATCH**: Bug fixes, backward compatible
|
|
235
|
+
|
|
236
|
+
We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.
|
|
237
|
+
|
|
238
|
+
## License
|
|
239
|
+
|
|
240
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
241
|
+
|
|
242
|
+
Free to use in both personal and commercial projects.
|
|
243
|
+
|
|
244
|
+
## About us
|
|
245
|
+
|
|
246
|
+
[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.
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
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.
|
|
251
|
+
|
|
252
|
+
## Known Limitations & Roadmap
|
|
253
|
+
|
|
254
|
+
Current limitations and planned features are tracked in the GitHub issues.
|
|
255
|
+
|
|
256
|
+
See the [project roadmap](https://github.com/wexample/python-config/issues) for upcoming features and improvements.
|
|
257
|
+
|
|
258
|
+
## Status & Compatibility
|
|
259
|
+
|
|
260
|
+
**Maturity**: Production-ready
|
|
261
|
+
|
|
262
|
+
**Python Support**: >=3.10
|
|
263
|
+
|
|
264
|
+
**OS Support**: Linux, macOS, Windows
|
|
265
|
+
|
|
266
|
+
**Status**: Actively maintained
|
|
267
|
+
|
|
268
|
+
## Useful Links
|
|
269
|
+
|
|
270
|
+
- **Homepage**: https://github.com/wexample/python-config
|
|
271
|
+
- **Documentation**: [docs.wexample.com](https://docs.wexample.com)
|
|
272
|
+
- **Issue Tracker**: https://github.com/wexample/python-config/issues
|
|
273
|
+
- **Discussions**: https://github.com/wexample/python-config/discussions
|
|
274
|
+
- **PyPI**: [pypi.org/project/wexample-config](https://pypi.org/project/wexample-config/)
|
|
275
|
+
|
|
276
|
+
## Migration Notes
|
|
277
|
+
|
|
278
|
+
When upgrading between major versions, refer to the migration guides in the documentation.
|
|
279
|
+
|
|
280
|
+
Breaking changes are clearly documented with upgrade paths and examples.
|
|
@@ -6,8 +6,8 @@ build-backend = "pdm.backend"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "wexample-config"
|
|
9
|
-
version = "7.2.
|
|
10
|
-
description = "
|
|
9
|
+
version = "7.2.2"
|
|
10
|
+
description = "Defines typed config schemas as named, provider-extensible option trees that parse and validate raw dict configurations"
|
|
11
11
|
authors = [
|
|
12
12
|
{ name = "weeger", email = "contact@wexample.com" },
|
|
13
13
|
]
|
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
|
21
21
|
"attrs>=23.1.0",
|
|
22
22
|
"cattrs>=23.1.0",
|
|
23
23
|
"pydantic>=2,<3",
|
|
24
|
-
"wexample-helpers>=19.
|
|
24
|
+
"wexample-helpers>=19.1.0",
|
|
25
25
|
]
|
|
26
26
|
|
|
27
27
|
[project.readme]
|
|
File without changes
|
|
File without changes
|