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.
- {appsettings2-2.0.9/src/appsettings2.egg-info → appsettings2-2.0.14}/PKG-INFO +3 -24
- {appsettings2-2.0.9 → appsettings2-2.0.14}/pyproject.toml +4 -4
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/ConfigurationBuilder.py +3 -2
- appsettings2-2.0.14/src/appsettings2/SKILL.md +475 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/__init__.py +2 -2
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/helpers.py +13 -2
- appsettings2-2.0.14/src/appsettings2/providers/EnvironmentConfigurationProvider.py +43 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14/src/appsettings2.egg-info}/PKG-INFO +3 -24
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/SOURCES.txt +1 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/requires.txt +1 -1
- appsettings2-2.0.9/src/appsettings2/providers/EnvironmentConfigurationProvider.py +0 -14
- {appsettings2-2.0.9 → appsettings2-2.0.14}/LICENSE +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/README.md +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/setup.cfg +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/Configuration.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/ConfigurationException.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/CommandLineConfigurationProvider.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/ConfigurationProvider.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/JsonConfigurationProvider.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/TomlConfigurationProvider.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/YamlConfigurationProvider.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/__init__.py +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/py.typed +0 -0
- {appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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
|
|
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.
|
|
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.
|
|
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 =
|
|
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.
|
|
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 |
|
|
@@ -8,7 +8,17 @@ from .Configuration import Configuration
|
|
|
8
8
|
from .ConfigurationBuilder import ConfigurationBuilder
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def get_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.
|
|
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
|
|
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.
|
|
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,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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/ConfigurationProvider.py
RENAMED
|
File without changes
|
{appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/JsonConfigurationProvider.py
RENAMED
|
File without changes
|
{appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/TomlConfigurationProvider.py
RENAMED
|
File without changes
|
{appsettings2-2.0.9 → appsettings2-2.0.14}/src/appsettings2/providers/YamlConfigurationProvider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|