appsettings2 2.0.9__tar.gz → 2.0.14__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 (25) hide show
  1. {appsettings2-2.0.9/src/appsettings2.egg-info → appsettings2-2.0.14}/PKG-INFO +3 -24
  2. {appsettings2-2.0.9 → appsettings2-2.0.14}/pyproject.toml +4 -4
  3. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/ConfigurationBuilder.py +3 -2
  4. appsettings2-2.0.14/src/appsettings2/SKILL.md +475 -0
  5. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/__init__.py +2 -2
  6. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/helpers.py +13 -2
  7. appsettings2-2.0.14/src/appsettings2/providers/EnvironmentConfigurationProvider.py +43 -0
  8. {appsettings2-2.0.9 → appsettings2-2.0.14/src/appsettings2.egg-info}/PKG-INFO +3 -24
  9. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/SOURCES.txt +1 -0
  10. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/requires.txt +1 -1
  11. appsettings2-2.0.9/src/appsettings2/providers/EnvironmentConfigurationProvider.py +0 -14
  12. {appsettings2-2.0.9 → appsettings2-2.0.14}/LICENSE +0 -0
  13. {appsettings2-2.0.9 → appsettings2-2.0.14}/README.md +0 -0
  14. {appsettings2-2.0.9 → appsettings2-2.0.14}/setup.cfg +0 -0
  15. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/Configuration.py +0 -0
  16. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/ConfigurationException.py +0 -0
  17. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/CommandLineConfigurationProvider.py +0 -0
  18. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/ConfigurationProvider.py +0 -0
  19. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/JsonConfigurationProvider.py +0 -0
  20. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/TomlConfigurationProvider.py +0 -0
  21. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/YamlConfigurationProvider.py +0 -0
  22. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/__init__.py +0 -0
  23. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/py.typed +0 -0
  24. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/dependency_links.txt +0 -0
  25. {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/top_level.txt +0 -0
@@ -1,30 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appsettings2
3
- Version: 2.0.9
3
+ Version: 2.0.14
4
4
  Summary: Unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
- License: MIT License
7
-
8
- © 2025 Shaun Wilson
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
6
+ License-Expression: MIT
28
7
  Project-URL: Documentation, https://appsettings2.readthedocs.io/
29
8
  Project-URL: Homepage, https://github.com/wilson0x4d/appsettings2
30
9
  Project-URL: Repository, https://github.com/wilson0x4d/appsettings2.git
@@ -42,7 +21,7 @@ Requires-Dist: flake8; extra == "dev"
42
21
  Requires-Dist: flake8-pyproject; extra == "dev"
43
22
  Requires-Dist: mypy; extra == "dev"
44
23
  Requires-Dist: pep8-naming; extra == "dev"
45
- Requires-Dist: punit>=1.4.9; extra == "dev"
24
+ Requires-Dist: punit>=1.10.8; extra == "dev"
46
25
  Requires-Dist: twine; extra == "dev"
47
26
  Requires-Dist: types-PyYAML; extra == "dev"
48
27
  Dynamic: license-file
@@ -1,13 +1,13 @@
1
1
  [project]
2
2
  name = "appsettings2"
3
- version = "2.0.9"
3
+ version = "2.0.14"
4
4
  description = "Unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data."
5
5
  keywords = ["appsettings", "configuration"]
6
6
  authors = [
7
7
  { name="Shaun Wilson", email="mrshaunwilson@msn.com" }
8
8
  ]
9
9
  readme = "README.md"
10
- license = {file = "LICENSE"}
10
+ license = "MIT"
11
11
  classifiers = [
12
12
  "Intended Audience :: Developers",
13
13
  "Operating System :: OS Independent",
@@ -25,7 +25,7 @@ dev = [
25
25
  "flake8-pyproject",
26
26
  "mypy",
27
27
  "pep8-naming",
28
- "punit (>=1.4.9)",
28
+ "punit (>=1.10.8)",
29
29
  "twine",
30
30
  "types-PyYAML"
31
31
  ]
@@ -48,7 +48,7 @@ exclude = [
48
48
  ]
49
49
 
50
50
  [tool.setuptools.package-data]
51
- "appsettings2" = ["py.typed"]
51
+ "appsettings2" = ["py.typed", "SKILL.md"]
52
52
 
53
53
  [tool.flake8]
54
54
  max-line-length = 120
@@ -65,13 +65,14 @@ class ConfigurationBuilder:
65
65
  addCommandLine = add_command_line
66
66
  """⚠️ DEPRECATED: use ``add_command_line(...)`` instead."""
67
67
 
68
- def add_environment(self) -> 'ConfigurationBuilder':
68
+ def add_environment(self, required_prefix: str | None = None) -> 'ConfigurationBuilder':
69
69
  """
70
70
  Add a :py:class:`~appsettings2.providers.EnvironmentConfigurationProvider`.
71
71
 
72
+ :param required_prefix: Optional prefix used to filter which environment variables are included. Only variables whose names start with this prefix (followed by ``_`` or ``__``) will be loaded. When ``None``, all environment variables are included. Defaults to None.
72
73
  :return: Returns :py:class:`~appsettings2.ConfigurationBuilder` for method chaining.
73
74
  """
74
- return self.add_provider(EnvironmentConfigurationProvider())
75
+ return self.add_provider(EnvironmentConfigurationProvider(required_prefix=required_prefix))
75
76
 
76
77
  addEnvironment = add_environment
77
78
  """⚠️ DEPRECATED: use ``add_environment(...)`` instead."""
@@ -0,0 +1,475 @@
1
+ ---
2
+ name: appsettings2
3
+ description: Unifies configuration sources into a Configuration object that can be bound to complex Python types or accessed directly. Covers ConfigurationBuilder, providers, object binding, helpers, and ConfigurationException. Use as a reference document for appsettings2 API and concepts.
4
+ user-invocable: true
5
+ disable-model-invocation: false
6
+ type: reference
7
+ ---
8
+
9
+ # appsettings2 — AI-First Feature Reference
10
+
11
+ **Unifies configuration sources into a `Configuration` object that can be bound to complex Python types or accessed directly.** Requires Python 3.11+.
12
+
13
+ ```python
14
+ from appsettings2 import ConfigurationBuilder
15
+ from appsettings2.providers import JsonConfigurationProvider, EnvironmentConfigurationProvider
16
+
17
+ config = ConfigurationBuilder()\
18
+ .add_provider(JsonConfigurationProvider('appsettings.json'))\
19
+ .add_provider(EnvironmentConfigurationProvider())\
20
+ .build()
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Table of Contents
26
+
27
+ 1. [ConfigurationBuilder](#1-configurationbuilder)
28
+ 2. [Configuration Access Patterns](#2-configuration-access-patterns)
29
+ 3. [Object Binding](#3-object-binding)
30
+ 4. [Built-in Providers](#4-built-in-providers)
31
+ 5. [helpers.get_configuration](#5-helpersget_configuration)
32
+ 6. [CustomAttributes](#6-custom-attributes)
33
+ 7. [Dictionary Conversion](#7-dictionary-conversion)
34
+ 8. [ConfigurationException](#8-configurationalertionerror)
35
+
36
+ ---
37
+
38
+ ## 1. ConfigurationBuilder
39
+
40
+ Fluent builder that assembles one or more `ConfigurationProvider` objects into a single `Configuration`.
41
+
42
+ ### Constructor
43
+
44
+ ```python
45
+ ConfigurationBuilder(normalize: bool = False, scrubkeys: bool = False) -> ConfigurationBuilder
46
+ ```
47
+
48
+ | Parameter | Effect |
49
+ |---|---|
50
+ | `normalize=True` | Normalises all attribute names to UPPER-CASE on the resulting `Configuration` |
51
+ | `scrubkeys=True` | Scrubs attribute names to be lexically valid Python identifiers (replaces non-identifier chars with `_`) |
52
+
53
+ ### Chaining helpers
54
+
55
+ All return `ConfigurationBuilder` for fluent chaining.
56
+
57
+ ```python
58
+ builder = ConfigurationBuilder()
59
+
60
+ builder.add_provider(provider) # custom provider (most generic)
61
+ builder.add_json(filepath='s.json') # from file or string
62
+ builder.add_toml(filepath='s.toml') # from file or string
63
+ builder.add_yaml(filepath='s.yaml') # from file or string
64
+ builder.add_command_line(argv=['a=1']) # from sys.argv or custom list
65
+ builder.add_environment() # from os.environ
66
+ builder.add_environment(required_prefix='APP_') # only APP_* vars, prefix stripped
67
+ ```
68
+
69
+ ### `required` parameter on file-based providers
70
+
71
+ ```python
72
+ .add_json('appsettings.Production.json', required=False) # silently missing
73
+ .add_toml('secrets.toml', required=True) # raises ConfigurationException
74
+ ```
75
+
76
+ ### `required_prefix` on environment provider
77
+
78
+ When `required_prefix='PREFIX_'`, only environment variables starting with `PREFIX_` are loaded, and that prefix is **stripped** from the resulting config key.
79
+
80
+ ```bash
81
+ export MYAPP_DB_HOST=postgres://... # loaded as "DB_HOST"
82
+ export OTHER_KEY=value # ignored
83
+ ```
84
+
85
+ ### `build()`
86
+
87
+ ```python
88
+ config = ConfigurationBuilder()\
89
+ .add_json('base.json')\
90
+ .add_json('appsettings.Development.json', required=False)\
91
+ .add_environment()\
92
+ .build()
93
+ ```
94
+
95
+ **Precedence rule:** providers added later take precedence. Later providers override values from earlier providers for matching keys only.
96
+
97
+ ---
98
+
99
+ ## 2. Configuration Access Patterns
100
+
101
+ The `Configuration` object exposes **four** equivalent ways to read hierarchical config data, using `__` or `:` as path delimiters.
102
+
103
+ ```python
104
+ # Example hierarchy produces key "ConnectionStrings__SampleDb" or "ConnectionStrings:SampleDb"
105
+
106
+ config.ConnectionStrings.SampleDb # dynamic attribute (case-sensitive)
107
+ config.get('ConnectionStrings__SampleDb') # get() API (case-insensitive)
108
+ config.get('ConnectionStrings:SampleDb') # colon-delimiter (case-insensitive)
109
+ config.get('ConnectionStrings').get('SampleDb') # chained get() (case-insensitive)
110
+ config['ConnectionStrings__SampleDb'] # indexer (case-insensitive)
111
+ config['ConnectionStrings']['SampleDb'] # chained indexer (case-insensitive)
112
+ ```
113
+
114
+ ### Key delimiters
115
+
116
+ `__` and `:` are **equivalent** and both case-insensitive. Attribute access is **case-sensitive**.
117
+
118
+ ### Setting values
119
+
120
+ ```python
121
+ config.set('Section__Key', 'value') # hierarchical key
122
+ config['Section__Key'] = 'value' # same via setter
123
+ ```
124
+
125
+ ### Other members
126
+
127
+ | Member | Signature | Description |
128
+ |---|---|---|
129
+ | `get(key, default=None)` | `Any` | Get value with fallback |
130
+ | `set(key, value)` | `None` | Set a (possibly nested) key |
131
+ | `has_key(key)` | `bool` | Check existence (case-insensitive) |
132
+ | `keys()` | `list[str]` | All top-level keys |
133
+ | `items()` | `list[tuple[str, Any]]` | Key-value pairs |
134
+ | `values()` | `list[Any]` | All values |
135
+ | `pop(key)` | `Any` | Retrieve and delete |
136
+ | `clear()` | `None` | Remove all keys |
137
+ | `__delitem__(key)` | `None` | Delete by key |
138
+ | `__iter__()` | `Iterator[str]` | Iterate keys |
139
+ | `__len__()` | `int` | Number of keys |
140
+
141
+ ---
142
+
143
+ ## 3. Object Binding
144
+
145
+ Map configuration data onto strongly-typed Python classes with type hints. The binding is **case-insensitive** and performs **primitive coercion**.
146
+
147
+ ### Basic binding
148
+
149
+ ```python
150
+ from appsettings2 import ConfigurationBuilder
151
+ from appsettings2.providers import JsonConfigurationProvider
152
+
153
+ config = ConfigurationBuilder()\
154
+ .add_json(json='''{
155
+ "ConnectionStrings": {"SampleDb": "conn_str_here"},
156
+ "EnableSwagger": true,
157
+ "MaxBatchSize": 100
158
+ }''')\
159
+ .build()
160
+
161
+ class ConnStrs:
162
+ SampleDB: str
163
+
164
+ class AppSettings:
165
+ ConnectionStrings: ConnStrs
166
+ EnableSwagger: bool
167
+ MaxBatchSize: int
168
+
169
+ settings = AppSettings()
170
+ config.bind(settings)
171
+
172
+ print(settings.ConnectionStrings.SampleDB) # "conn_str_here"
173
+ ```
174
+
175
+ ### Partial binding (key-subset)
176
+
177
+ Bind only a slice of the configuration using the `key` parameter:
178
+
179
+ ```python
180
+ conn_strs = config.bind(ConnStrs(), 'ConnectionStrings')
181
+ print(conn_strs.SampleDB)
182
+ ```
183
+
184
+ Returns the target object (for method chaining).
185
+
186
+ ### Collection binding
187
+
188
+ ```python
189
+ from typing import List
190
+
191
+ class Node:
192
+ host: str
193
+ port: int
194
+
195
+ class ClusterSettings:
196
+ servers: dict[str, Node]
197
+ nodes: list[Node]
198
+ ports: set[int]
199
+
200
+ config.bind(ClusterSettings())
201
+ ```
202
+
203
+ - `dict[str, SomeClass]` — values are **rehydrated** into instances of `SomeClass`
204
+ - `list[SomeClass]` — each element is **rehydrated** into an instance of `SomeClass`
205
+ - `set[SomeClass]` — each element is **rehydrated** into an instance of `SomeClass`
206
+ - `dict[str, str]` — remains a plain dict (no rehydration)
207
+ - `list[int]` / `set[str]` — elements coerced to the element type
208
+
209
+ ### Nullable / Optional / Union fields
210
+
211
+ ```python
212
+ from typing import Optional
213
+
214
+ class Settings:
215
+ maybe_servers: Optional[dict[str, InnerNode]] # None if absent in config
216
+ ```
217
+
218
+ ### Primitive type coercion
219
+
220
+ Binding converts config values to match type hints:
221
+
222
+ | Target type | Coercion |
223
+ |---|---|
224
+ | `int` | `int(value)` |
225
+ | `float` | `float(value)` |
226
+ | `str` | `str(value)` |
227
+ | `bool` | `bool(value)` |
228
+
229
+ ### Scrubbed / normalized key matching
230
+
231
+ When both the `ConfigurationBuilder` and the `Configuration` use `scrubkeys=True` or `normalize=True`, binding matches scrubbed/normalised names:
232
+
233
+ ```python
234
+ config = ConfigurationBuilder(normalize=True, scrubkeys=True).build()
235
+ config.set('X-foo.1', 'hello') # stored as 'X_FOO_1'
236
+
237
+ class Target:
238
+ x_foo_1: str
239
+
240
+ config.bind(Target()) # Target.x_foo_1 == 'hello'
241
+ ```
242
+
243
+ ---
244
+
245
+ ## 4. Built-in Providers
246
+
247
+ All providers subclass the abstract `ConfigurationProvider`:
248
+
249
+ ```python
250
+ class ConfigurationProvider(ABC):
251
+ @abstractmethod
252
+ def populate_configuration(self, configuration: Configuration) -> None:
253
+ ...
254
+ ```
255
+
256
+ ### 4.1 JsonConfigurationProvider
257
+
258
+ ```python
259
+ from appsettings2.providers import JsonConfigurationProvider
260
+
261
+ # From file path (required by default)
262
+ JsonConfigurationProvider(filepath='appsettings.json')
263
+
264
+ # From JSON string
265
+ JsonConfigurationProvider(json='{"key": "value"}')
266
+
267
+ # From file descriptor (int)
268
+ JsonConfigurationProvider(fd=io.open('appsettings.json').fd)
269
+
270
+ # Optional / environment-override pattern
271
+ JsonConfigurationProvider(filepath='appsettings.Production.json', required=False)
272
+ ```
273
+
274
+ Hierarchical JSON flattens to `__`-delimited keys. Nested dicts produce nested `Configuration` objects.
275
+
276
+ ### 4.2 YamlConfigurationProvider
277
+
278
+ ```python
279
+ from appsettings2.providers import YamlConfigurationProvider
280
+
281
+ YamlConfigurationProvider(filepath='appsettings.yaml')
282
+ YamlConfigurationProvider(yaml='key: value')
283
+ ```
284
+
285
+ Same interface and behavior as JSON provider, using `yaml.safe_load`. **Requires `pyyaml`.**
286
+
287
+ ### 4.3 TomlConfigurationProvider
288
+
289
+ ```python
290
+ from appsettings2.providers import TomlConfigurationProvider
291
+
292
+ TomlConfigurationProvider(filepath='appsettings.toml')
293
+ TomlConfigurationProvider(toml='key = "value"')
294
+ ```
295
+
296
+ Same interface as JSON provider, using `tomllib`. **No extra dependency — stdlib in Python 3.11+.**
297
+
298
+ ### 4.4 EnvironmentConfigurationProvider
299
+
300
+ ```python
301
+ from appsettings2.providers import EnvironmentConfigurationProvider
302
+
303
+ # All env vars become config keys
304
+ EnvironmentConfigurationProvider()
305
+
306
+ # Only vars with prefix (prefix stripped in output)
307
+ EnvironmentConfigurationProvider(required_prefix='APP_')
308
+ ```
309
+
310
+ ### 4.5 CommandLineConfigurationProvider
311
+
312
+ ```python
313
+ from appsettings2.providers import CommandLineConfigurationProvider
314
+
315
+ # Uses sys.argv
316
+ CommandLineConfigurationProvider()
317
+
318
+ # Custom argv
319
+ CommandLineConfigurationProvider(argv=['--key=value', 'flag'])
320
+ ```
321
+
322
+ Parses `--key=value` pairs and `--flag` switches (set to `True`). Keys may be `key__nested` for hierarchy.
323
+
324
+ ### 4.6 Custom Provider
325
+
326
+ ```python
327
+ from appsettings2 import Configuration
328
+ from appsettings2.providers import ConfigurationProvider
329
+
330
+ class RedisConfigurationProvider(ConfigurationProvider):
331
+ def populate_configuration(self, configuration: Configuration) -> None:
332
+ import redis
333
+ client = redis.Redis()
334
+ configuration.set('Redis__Host', client.config_get('host')['host'])
335
+ ```
336
+
337
+ Most providers are under 20 lines of logic.
338
+
339
+ ---
340
+
341
+ ## 5. helpers.get_configuration
342
+
343
+ Convenience function for the common case of loading from well-known file variations, CLI args, and environment variables in a single call.
344
+
345
+ ```python
346
+ import appsettings2
347
+
348
+ config = appsettings2.get_configuration()
349
+ ```
350
+
351
+ ### Parameters
352
+
353
+ | Parameter | Default | Description |
354
+ |---|---|---|
355
+ | `basename` | `'appsettings'` | Base filename for file-based providers |
356
+ | `json` | `True` | Load JSON variations |
357
+ | `toml` | `True` | Load TOML variations |
358
+ | `yaml` | `True` | Load YAML variations |
359
+ | `cli` | `True` | Load from command-line args |
360
+ | `environment` | `True` | Load from environment variables |
361
+ | `variations` | `['', 'prod', 'production', 'stage', 'staging', 'qa', 'dev', 'development', 'local']` | Filename suffixes (e.g. `appsettings.Development.json`) |
362
+ | `env_prefix` | `None` | If set, only load env vars with this prefix (stripped from key) |
363
+
364
+ ### Examples
365
+
366
+ ```python
367
+ # Default: loads appsettings.json, appsettings.dev.json, etc.
368
+ config = appsettings2.get_configuration()
369
+
370
+ # Custom base name and skip TOML
371
+ config = appsettings2.get_configuration('myapp', toml=False)
372
+
373
+ # Custom variation list (dev only, nothing else)
374
+ config = appsettings2.get_configuration('appsettings', variations=['dev', 'staging'])
375
+
376
+ # Only load env vars with prefix, skip all file-based providers
377
+ config = appsettings2.get_configuration(
378
+ json=False, toml=False, yaml=False, cli=False,
379
+ environment=True, env_prefix='MYAPP_'
380
+ )
381
+
382
+ # pathlib.Path support
383
+ from pathlib import Path
384
+ config = appsettings2.get_configuration(Path('configs') / 'settings')
385
+ ```
386
+
387
+ Each variation file is loaded with `required=False` so missing files are silently skipped.
388
+
389
+ ---
390
+
391
+ ## 6. CustomAttributes
392
+
393
+ ### `normalize`
394
+
395
+ When `normalize=True` on the builder, all keys are stored in **UPPER-CASE** on the resulting `Configuration` object. Attribute access becomes case-insensitive too.
396
+
397
+ ```python
398
+ config = ConfigurationBuilder(normalize=True).build()
399
+ config.set('Section__Key', 'value')
400
+ print(config.SECTION__KEY) # 'value'
401
+ ```
402
+
403
+ ### `scrubkeys`
404
+
405
+ When `scrubkeys=True`, keys are rewritten to be valid Python identifiers — non-identifier characters are replaced with `_`.
406
+
407
+ ```python
408
+ config = ConfigurationBuilder(scrubkeys=True).build()
409
+ config.set('X-foo.bar', 'hello')
410
+ print(config.X_FOO_BAR) # 'hello'
411
+ ```
412
+
413
+ ---
414
+
415
+ ## 7. Dictionary Conversion
416
+
417
+ ### Transform Configuration to dict
418
+
419
+ ```python
420
+ d = config.to_dict()
421
+ ```
422
+
423
+ Returns a recursive copy. Nested Configuration objects become nested dicts.
424
+
425
+ ### Create Configuration from dict (static factory)
426
+
427
+ ```python
428
+ from appsettings2 import Configuration
429
+
430
+ config = Configuration.from_dict({
431
+ 'Section': {
432
+ 'Key1': 'value1',
433
+ 'Key2': 42
434
+ }
435
+ })
436
+
437
+ # With custom options
438
+ config = Configuration.from_dict(data, normalize=True, scrubkeys=True)
439
+ ```
440
+
441
+ ---
442
+
443
+ ## 8. ConfigurationException
444
+
445
+ ```python
446
+ from appsettings2 import ConfigurationException
447
+
448
+ try:
449
+ config = ConfigurationBuilder()\
450
+ .add_json('missing_required.json', required=True)\
451
+ .build()
452
+ except ConfigurationException as e:
453
+ print(e.reason) # "Missing required file: missing_required.json"
454
+ ```
455
+
456
+ Raised when a required configuration source (file) is not found.
457
+
458
+ ---
459
+
460
+ ## Export summary
461
+
462
+ | Symbol | Module | Description |
463
+ |---|---|---|
464
+ | `Configuration` | `appsettings2` | Main config object |
465
+ | `ConfigurationBuilder` | `appsettings2` | Builder with fluent API |
466
+ | `ConfigurationException` | `appsettings2` | Error type |
467
+ | `get_configuration()` | `appsettings2` | Quick-start helper |
468
+ | `helpers` | `appsettings2` | Namespace for helpers module |
469
+ | `providers` | `appsettings2` | Namespace for provider modules |
470
+ | `ConfigurationProvider` | `appsettings2.providers` | Abstract base for custom providers |
471
+ | `JsonConfigurationProvider` | `appsettings2.providers` | JSON file/string |
472
+ | `YamlConfigurationProvider` | `appsettings2.providers` | YAML file/string |
473
+ | `TomlConfigurationProvider` | `appsettings2.providers` | TOML file/string |
474
+ | `EnvironmentConfigurationProvider` | `appsettings2.providers` | os.environ |
475
+ | `CommandLineConfigurationProvider` | `appsettings2.providers` | CLI args |
@@ -10,8 +10,8 @@ from . import helpers
10
10
  from . import providers
11
11
 
12
12
 
13
- __version__ = '2.0.9'
14
- __commit__ = 'b510516'
13
+ __version__ = '2.0.14'
14
+ __commit__ = '654e291'
15
15
  __all__ = [
16
16
  '__version__', '__commit__',
17
17
  'Configuration',
@@ -8,7 +8,17 @@ from .Configuration import Configuration
8
8
  from .ConfigurationBuilder import ConfigurationBuilder
9
9
 
10
10
 
11
- def get_configuration(basename: str | pathlib.PosixPath | pathlib.Path = 'appsettings', json: bool = True, toml: bool = True, yaml: bool = True, cli: bool = True, environment: bool = True, variations: Optional[list[str | None]] = ['', 'prod', 'production', 'stage', 'staging', 'qa', 'dev', 'development', 'local']) -> Configuration:
11
+ def get_configuration(
12
+ basename: str | pathlib.PosixPath | pathlib.Path = 'appsettings',
13
+ *,
14
+ json: bool = True,
15
+ toml: bool = True,
16
+ yaml: bool = True,
17
+ cli: bool = True,
18
+ environment: bool = True,
19
+ variations: Optional[list[str | None]] = ['', 'prod', 'production', 'stage', 'staging', 'qa', 'dev', 'development', 'local'],
20
+ env_prefix: str | None = None
21
+ ) -> Configuration:
12
22
  """
13
23
  Get a ``Configuration`` instance by loading from a set of well-known providers and their variations.
14
24
 
@@ -19,6 +29,7 @@ def get_configuration(basename: str | pathlib.PosixPath | pathlib.Path = 'appset
19
29
  :param cli: ``True`` to load configuration from Command-Line, defaults to True
20
30
  :param environment: ``True`` to load configuration from process Environment, defaults to True
21
31
  :param variations: A set of variations to apply to use with file-based Configuration Providers, defaults to ['', 'prod', 'production', 'stage', 'staging', 'qa', 'dev', 'development', 'local']
32
+ :param env_prefix: (OPTIONAL) When specified, only env vars having the specified prefix are bound as configuration settings, the prefix is stripped from the resulting setting name.
22
33
  :return: An ``appsettings2.Configuration`` instance.
23
34
  """
24
35
  if type(basename) is pathlib.Path or type(basename) is pathlib.PosixPath:
@@ -36,7 +47,7 @@ def get_configuration(basename: str | pathlib.PosixPath | pathlib.Path = 'appset
36
47
  if cli:
37
48
  builder.add_command_line()
38
49
  if environment:
39
- builder.add_environment()
50
+ builder.add_environment(required_prefix=env_prefix)
40
51
  return builder.build()
41
52
 
42
53
 
@@ -0,0 +1,43 @@
1
+ # SPDX-FileCopyrightText: © 2025 Shaun Wilson
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ import os
5
+ from typing import Any
6
+
7
+ from ..Configuration import Configuration
8
+ from .ConfigurationProvider import ConfigurationProvider
9
+
10
+
11
+ class EnvironmentConfigurationProvider(ConfigurationProvider):
12
+ """A ``ConfigurationProvider`` that populates configuration data from Environment variables."""
13
+
14
+ __required_prefix: str | None
15
+
16
+ def __init__(self, /, required_prefix: str | None = None, **kwargs: Any) -> None:
17
+ self.__required_prefix = required_prefix.upper() if required_prefix is not None else None
18
+
19
+ def populate_configuration(self, configuration: Configuration) -> None:
20
+ """
21
+ Populate the provided :py:class:`~appsettings2.Configuration` from environment variables.
22
+
23
+ Usage::
24
+
25
+ # All env vars become config keys:
26
+ provider = EnvironmentConfigurationProvider()
27
+ provider.populate_configuration(config)
28
+
29
+ # Only keys with a specific prefix, stripped in output:
30
+ provider = EnvironmentConfigurationProvider(required_prefix="APP_")
31
+ # APP_DATABASE_URL=postgres://... → config["DATABASE_URL"] == "postgres://..."
32
+ provider.populate_configuration(config)
33
+
34
+ When ``required_prefix`` is set, only keys starting with that prefix are included,
35
+ and the prefix is stripped from each key in the output configuration.
36
+ """
37
+ for k in os.environ:
38
+ key = (
39
+ k
40
+ if self.__required_prefix is None
41
+ else k[len(self.__required_prefix):]
42
+ )
43
+ configuration.set(key, os.environ[k])
@@ -1,30 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appsettings2
3
- Version: 2.0.9
3
+ Version: 2.0.14
4
4
  Summary: Unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data.
5
5
  Author-email: Shaun Wilson <mrshaunwilson@msn.com>
6
- License: MIT License
7
-
8
- © 2025 Shaun Wilson
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
6
+ License-Expression: MIT
28
7
  Project-URL: Documentation, https://appsettings2.readthedocs.io/
29
8
  Project-URL: Homepage, https://github.com/wilson0x4d/appsettings2
30
9
  Project-URL: Repository, https://github.com/wilson0x4d/appsettings2.git
@@ -42,7 +21,7 @@ Requires-Dist: flake8; extra == "dev"
42
21
  Requires-Dist: flake8-pyproject; extra == "dev"
43
22
  Requires-Dist: mypy; extra == "dev"
44
23
  Requires-Dist: pep8-naming; extra == "dev"
45
- Requires-Dist: punit>=1.4.9; extra == "dev"
24
+ Requires-Dist: punit>=1.10.8; extra == "dev"
46
25
  Requires-Dist: twine; extra == "dev"
47
26
  Requires-Dist: types-PyYAML; extra == "dev"
48
27
  Dynamic: license-file
@@ -4,6 +4,7 @@ pyproject.toml
4
4
  src/appsettings2/Configuration.py
5
5
  src/appsettings2/ConfigurationBuilder.py
6
6
  src/appsettings2/ConfigurationException.py
7
+ src/appsettings2/SKILL.md
7
8
  src/appsettings2/__init__.py
8
9
  src/appsettings2/helpers.py
9
10
  src/appsettings2/py.typed
@@ -6,6 +6,6 @@ flake8
6
6
  flake8-pyproject
7
7
  mypy
8
8
  pep8-naming
9
- punit>=1.4.9
9
+ punit>=1.10.8
10
10
  twine
11
11
  types-PyYAML
@@ -1,14 +0,0 @@
1
- # SPDX-FileCopyrightText: © 2025 Shaun Wilson
2
- # SPDX-License-Identifier: MIT
3
-
4
- from .ConfigurationProvider import ConfigurationProvider
5
- from ..Configuration import Configuration
6
- import os
7
-
8
-
9
- class EnvironmentConfigurationProvider(ConfigurationProvider):
10
- """A ``ConfigurationProvider`` that populates configuration data from Environment variables."""
11
-
12
- def populate_configuration(self, configuration: Configuration) -> None: # noqa: D102
13
- for k in os.environ:
14
- configuration.set(k, os.environ[k])
File without changes
File without changes
File without changes