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.
Files changed (51) hide show
  1. wexample_config-7.2.2/PKG-INFO +301 -0
  2. wexample_config-7.2.2/README.md +280 -0
  3. {wexample_config-7.2.0 → wexample_config-7.2.2}/pyproject.toml +3 -3
  4. wexample_config-7.2.0/tests/__pycache__/__init__.py → wexample_config-7.2.2/tests/.gitkeep +0 -0
  5. wexample_config-7.2.2/tests/__pycache__/__init__.py +0 -0
  6. wexample_config-7.2.0/PKG-INFO +0 -475
  7. wexample_config-7.2.0/README.md +0 -454
  8. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/__init__.py +0 -0
  9. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/__pycache__/__init__.py +0 -0
  10. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/__init__.py +0 -0
  11. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/__pycache__/__init__.py +0 -0
  12. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/classes/abstract_config_manager.py +0 -0
  13. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/__init__.py +0 -0
  14. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/__pycache__/__init__.py +0 -0
  15. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_config_option.py +0 -0
  16. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_list_config_option.py +0 -0
  17. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/abstract_nested_config_option.py +0 -0
  18. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/children_config_option.py +0 -0
  19. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/config_option.py +0 -0
  20. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_option/name_config_option.py +0 -0
  21. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/__init__.py +0 -0
  22. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/__pycache__/__init__.py +0 -0
  23. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/callback_render_config_value.py +0 -0
  24. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/config_value.py +0 -0
  25. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/config_value_collection.py +0 -0
  26. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/custom_type_config_value.py +0 -0
  27. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/config_value/nested_config_value.py +0 -0
  28. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/__init__.py +0 -0
  29. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/__pycache__/__init__.py +0 -0
  30. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/const/types.py +0 -0
  31. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/__init__.py +0 -0
  32. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/__pycache__/__init__.py +0 -0
  33. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/__init__.py +0 -0
  34. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_custom_value_config_option.py +0 -0
  35. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_dict_config_option.py +0 -0
  36. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_dict_extensible_option.py +0 -0
  37. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_extensible_config_option.py +0 -0
  38. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_list_config_option.py +0 -0
  39. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_nested_config_option.py +0 -0
  40. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/config_option/demo_union_config_option.py +0 -0
  41. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/demo_config_manager.py +0 -0
  42. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/option_provider/__init__.py +0 -0
  43. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/demo/option_provider/demo_options_provider.py +0 -0
  44. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/__init__.py +0 -0
  45. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/__pycache__/__init__.py +0 -0
  46. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/exception/invalid_option_exception.py +0 -0
  47. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/__init__.py +0 -0
  48. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/__pycache__/__init__.py +0 -0
  49. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/options_provider/abstract_options_provider.py +0 -0
  50. {wexample_config-7.2.0 → wexample_config-7.2.2}/src/wexample_config/py.typed +0 -0
  51. {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.0"
10
- description = "Helpers to manage dict types configurations."
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.0.0",
24
+ "wexample-helpers>=19.1.0",
25
25
  ]
26
26
 
27
27
  [project.readme]
File without changes