appsettings2 2.0.1__tar.gz → 2.0.6__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.1/src/appsettings2.egg-info → appsettings2-2.0.6}/PKG-INFO +7 -8
- {appsettings2-2.0.1 → appsettings2-2.0.6}/README.md +5 -5
- {appsettings2-2.0.1 → appsettings2-2.0.6}/pyproject.toml +3 -4
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/Configuration.py +44 -22
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/__init__.py +2 -2
- {appsettings2-2.0.1 → appsettings2-2.0.6/src/appsettings2.egg-info}/PKG-INFO +7 -8
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/requires.txt +1 -1
- {appsettings2-2.0.1 → appsettings2-2.0.6}/LICENSE +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/setup.cfg +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/ConfigurationBuilder.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/ConfigurationException.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/helpers.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/CommandLineConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/ConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/EnvironmentConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/JsonConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/TomlConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/YamlConfigurationProvider.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/__init__.py +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/py.typed +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/SOURCES.txt +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/dependency_links.txt +0 -0
- {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appsettings2
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
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
6
|
License: MIT License
|
|
@@ -32,7 +32,6 @@ Keywords: appsettings,configuration
|
|
|
32
32
|
Classifier: Intended Audience :: Developers
|
|
33
33
|
Classifier: Operating System :: OS Independent
|
|
34
34
|
Classifier: Programming Language :: Python
|
|
35
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
36
35
|
Requires-Python: >=3.11
|
|
37
36
|
Description-Content-Type: text/markdown
|
|
38
37
|
License-File: LICENSE
|
|
@@ -43,7 +42,7 @@ Requires-Dist: flake8; extra == "dev"
|
|
|
43
42
|
Requires-Dist: flake8-pyproject; extra == "dev"
|
|
44
43
|
Requires-Dist: mypy; extra == "dev"
|
|
45
44
|
Requires-Dist: pep8-naming; extra == "dev"
|
|
46
|
-
Requires-Dist: punit>=1.4.
|
|
45
|
+
Requires-Dist: punit>=1.4.9; extra == "dev"
|
|
47
46
|
Requires-Dist: twine; extra == "dev"
|
|
48
47
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
49
48
|
Dynamic: license-file
|
|
@@ -132,12 +131,12 @@ json = """{
|
|
|
132
131
|
|
|
133
132
|
class ConnStrs:
|
|
134
133
|
"""An ugly class name to demonstrate the class name does not matter."""
|
|
135
|
-
SampleDB:str
|
|
134
|
+
SampleDB: str
|
|
136
135
|
|
|
137
136
|
class AppSettings:
|
|
138
|
-
ConnectionStrings:ConnStrs
|
|
139
|
-
EnableSwagger:bool
|
|
140
|
-
MaxBatchSize:int
|
|
137
|
+
ConnectionStrings: ConnStrs
|
|
138
|
+
EnableSwagger: bool
|
|
139
|
+
MaxBatchSize: int
|
|
141
140
|
|
|
142
141
|
configuration = ConfigurationBuilder()\
|
|
143
142
|
.add_provider(JsonConfigurationProvider(json=json))
|
|
@@ -170,7 +169,7 @@ You can transform a `Configuration` instance into a dictionary (as a copy.) If y
|
|
|
170
169
|
|
|
171
170
|
```python
|
|
172
171
|
configuration = builder.build()
|
|
173
|
-
d = configuration.
|
|
172
|
+
d = configuration.to_dict()
|
|
174
173
|
print(d)
|
|
175
174
|
```
|
|
176
175
|
|
|
@@ -82,12 +82,12 @@ json = """{
|
|
|
82
82
|
|
|
83
83
|
class ConnStrs:
|
|
84
84
|
"""An ugly class name to demonstrate the class name does not matter."""
|
|
85
|
-
SampleDB:str
|
|
85
|
+
SampleDB: str
|
|
86
86
|
|
|
87
87
|
class AppSettings:
|
|
88
|
-
ConnectionStrings:ConnStrs
|
|
89
|
-
EnableSwagger:bool
|
|
90
|
-
MaxBatchSize:int
|
|
88
|
+
ConnectionStrings: ConnStrs
|
|
89
|
+
EnableSwagger: bool
|
|
90
|
+
MaxBatchSize: int
|
|
91
91
|
|
|
92
92
|
configuration = ConfigurationBuilder()\
|
|
93
93
|
.add_provider(JsonConfigurationProvider(json=json))
|
|
@@ -120,7 +120,7 @@ You can transform a `Configuration` instance into a dictionary (as a copy.) If y
|
|
|
120
120
|
|
|
121
121
|
```python
|
|
122
122
|
configuration = builder.build()
|
|
123
|
-
d = configuration.
|
|
123
|
+
d = configuration.to_dict()
|
|
124
124
|
print(d)
|
|
125
125
|
```
|
|
126
126
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "appsettings2"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.6"
|
|
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 = [
|
|
@@ -11,8 +11,7 @@ license = {file = "LICENSE"}
|
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Intended Audience :: Developers",
|
|
13
13
|
"Operating System :: OS Independent",
|
|
14
|
-
"Programming Language :: Python"
|
|
15
|
-
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
14
|
+
"Programming Language :: Python"
|
|
16
15
|
]
|
|
17
16
|
requires-python = ">=3.11"
|
|
18
17
|
dependencies = [
|
|
@@ -26,7 +25,7 @@ dev = [
|
|
|
26
25
|
"flake8-pyproject",
|
|
27
26
|
"mypy",
|
|
28
27
|
"pep8-naming",
|
|
29
|
-
"punit (>=1.4.
|
|
28
|
+
"punit (>=1.4.9)",
|
|
30
29
|
"twine",
|
|
31
30
|
"types-PyYAML"
|
|
32
31
|
]
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# SPDX-FileCopyrightText: © 2025 Shaun Wilson
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
5
6
|
import json
|
|
7
|
+
import logging
|
|
6
8
|
import re
|
|
7
9
|
import types
|
|
8
|
-
import
|
|
9
|
-
from typing import Any, Iterator, TYPE_CHECKING
|
|
10
|
+
from types import NoneType
|
|
11
|
+
from typing import Any, Iterator, Union, TYPE_CHECKING, get_args, get_origin, get_type_hints
|
|
10
12
|
import unicodedata
|
|
11
13
|
|
|
12
14
|
from .ConfigurationException import ConfigurationException
|
|
@@ -15,7 +17,7 @@ from .ConfigurationException import ConfigurationException
|
|
|
15
17
|
class Configuration:
|
|
16
18
|
"""The :py:class:`~appsettings2.Configuration` class is how applications access configuration data populated by :py:class:`~appsettings2.providers.ConfigurationProvider` objects. It exposes configuration data through dynamic object attributes as well as a dictionary-like interface."""
|
|
17
19
|
|
|
18
|
-
__key_scrub_re: re.Pattern | None
|
|
20
|
+
__key_scrub_re: re.Pattern[str] | None
|
|
19
21
|
__keys: dict[str, str]
|
|
20
22
|
__normalize: bool
|
|
21
23
|
|
|
@@ -29,8 +31,11 @@ class Configuration:
|
|
|
29
31
|
self.__keys = {}
|
|
30
32
|
self.__logger = logging.getLogger('appsettings2')
|
|
31
33
|
self.__normalize = normalize
|
|
32
|
-
self.__key_scrub_re =
|
|
33
|
-
|
|
34
|
+
self.__key_scrub_re = (
|
|
35
|
+
None
|
|
36
|
+
if not scrubkeys
|
|
37
|
+
else re.compile(r'[^A-Za-z0-9_]', re.IGNORECASE | re.UNICODE)
|
|
38
|
+
)
|
|
34
39
|
|
|
35
40
|
if TYPE_CHECKING:
|
|
36
41
|
|
|
@@ -38,7 +43,6 @@ class Configuration:
|
|
|
38
43
|
"""Tell type checkers that accessing any attribute returns any type."""
|
|
39
44
|
...
|
|
40
45
|
|
|
41
|
-
# This tells mypy that setting any attribute is allowed
|
|
42
46
|
def __setattr__(self, name: str, value: Any) -> None:
|
|
43
47
|
"""Tell type checkers that setting any attribute to any value is allowed."""
|
|
44
48
|
...
|
|
@@ -79,21 +83,27 @@ class Configuration:
|
|
|
79
83
|
"""Get the number of configuration keys."""
|
|
80
84
|
return len(self.__keys)
|
|
81
85
|
|
|
82
|
-
def
|
|
86
|
+
def __deunionize(self, t: type) -> type:
|
|
87
|
+
if get_origin(t) is Union:
|
|
88
|
+
t = [e for e in get_args(t) if e is not NoneType][0]
|
|
89
|
+
return t
|
|
90
|
+
|
|
91
|
+
def __recursive_bind(self, target: object, source: Configuration | dict[str, Any]) -> Any:
|
|
83
92
|
if target is None:
|
|
84
93
|
return None
|
|
85
94
|
if hasattr(target, '__class__'):
|
|
86
|
-
target_type_hints =
|
|
87
|
-
getattr(target, '__class__')
|
|
95
|
+
target_type_hints = get_type_hints(
|
|
96
|
+
getattr(target, '__class__')
|
|
97
|
+
)
|
|
88
98
|
else:
|
|
89
|
-
target_type_hints =
|
|
99
|
+
target_type_hints = get_type_hints(target)
|
|
90
100
|
names = set(dir(target) | target_type_hints.keys())
|
|
91
101
|
for aname in names:
|
|
92
102
|
if aname.startswith('_'):
|
|
93
103
|
continue
|
|
94
104
|
lval = None if not hasattr(
|
|
95
105
|
target, aname) else getattr(target, aname)
|
|
96
|
-
if isinstance(lval, types.FunctionType
|
|
106
|
+
if isinstance(lval, (types.FunctionType, types.MethodType)):
|
|
97
107
|
continue
|
|
98
108
|
ahint = target_type_hints.get(aname)
|
|
99
109
|
rval = source.get(aname)
|
|
@@ -120,8 +130,8 @@ class Configuration:
|
|
|
120
130
|
# non-list values cannot be merged
|
|
121
131
|
raise Exception(
|
|
122
132
|
f'Cannot bind `None` to attribute `{aname}`')
|
|
123
|
-
phints =
|
|
124
|
-
if phints is None
|
|
133
|
+
phints = get_type_hints(getattr(prop, 'fget'))
|
|
134
|
+
if phints is None:
|
|
125
135
|
# NOTE: can't get hints from getter, can't bind
|
|
126
136
|
continue
|
|
127
137
|
ahint = phints.get('return')
|
|
@@ -137,23 +147,35 @@ class Configuration:
|
|
|
137
147
|
elif ahint is str:
|
|
138
148
|
setattr(target, aname, str(rval))
|
|
139
149
|
elif isinstance(rval, Configuration):
|
|
140
|
-
if
|
|
141
|
-
lval = rval.
|
|
150
|
+
if get_origin(ahint) is dict:
|
|
151
|
+
lval = rval.to_dict()
|
|
142
152
|
setattr(target, aname, lval)
|
|
143
153
|
else:
|
|
144
154
|
if lval is None:
|
|
155
|
+
ahint = self.__deunionize(ahint)
|
|
145
156
|
lval = ahint()
|
|
146
157
|
setattr(target, aname, lval)
|
|
147
158
|
self.__recursive_bind(lval, rval)
|
|
148
|
-
elif
|
|
159
|
+
elif get_origin(ahint) is list:
|
|
149
160
|
element_type = ahint.__args__[0]
|
|
150
161
|
if lval is None:
|
|
162
|
+
ahint = self.__deunionize(ahint)
|
|
151
163
|
lval = ahint()
|
|
152
164
|
setattr(target, aname, lval)
|
|
153
165
|
for e in rval:
|
|
154
166
|
lval.append(
|
|
155
167
|
self.__recursive_bind_type(element_type, e)
|
|
156
168
|
)
|
|
169
|
+
elif get_origin(ahint) is set:
|
|
170
|
+
element_type = ahint.__args__[0]
|
|
171
|
+
if lval is None:
|
|
172
|
+
ahint = self.__deunionize(ahint)
|
|
173
|
+
lval = ahint()
|
|
174
|
+
setattr(target, aname, lval)
|
|
175
|
+
for e in rval:
|
|
176
|
+
lval.add(
|
|
177
|
+
self.__recursive_bind_type(element_type, e)
|
|
178
|
+
)
|
|
157
179
|
else:
|
|
158
180
|
setattr(target, aname, rval)
|
|
159
181
|
return target
|
|
@@ -167,7 +189,7 @@ class Configuration:
|
|
|
167
189
|
return int(source)
|
|
168
190
|
elif element_type is str:
|
|
169
191
|
return str(source)
|
|
170
|
-
elif isinstance(source, Configuration
|
|
192
|
+
elif isinstance(source, (Configuration, dict)):
|
|
171
193
|
v = element_type()
|
|
172
194
|
return self.__recursive_bind(v, source)
|
|
173
195
|
else:
|
|
@@ -183,7 +205,7 @@ class Configuration:
|
|
|
183
205
|
'NFKC',
|
|
184
206
|
key))
|
|
185
207
|
|
|
186
|
-
def __scrub_uc(self, m: re.Match) -> str:
|
|
208
|
+
def __scrub_uc(self, m: re.Match[str]) -> str:
|
|
187
209
|
match unicodedata.category(m[0]):
|
|
188
210
|
case 'Lu' | 'Ll' | 'Lt' | 'Lm' | 'Lo' | 'Nl' | 'Mn' | 'Mc' | 'Nd' | 'Pc':
|
|
189
211
|
return m[0]
|
|
@@ -230,7 +252,7 @@ class Configuration:
|
|
|
230
252
|
delattr(self, t[1])
|
|
231
253
|
|
|
232
254
|
@staticmethod
|
|
233
|
-
def from_dict(source: dict, normalize: bool = False, scrubkeys: bool = False) -> 'Configuration':
|
|
255
|
+
def from_dict(source: dict[str, Any], normalize: bool = False, scrubkeys: bool = False) -> 'Configuration':
|
|
234
256
|
"""
|
|
235
257
|
Construct a :py:class:`~appsettings2.Configuration` instance from the supplied dictionary `source`.
|
|
236
258
|
|
|
@@ -359,12 +381,12 @@ class Configuration:
|
|
|
359
381
|
for k in self.__keys.values():
|
|
360
382
|
v = getattr(self, self.__scrub_key(k))
|
|
361
383
|
if isinstance(v, Configuration):
|
|
362
|
-
result[k] = v.
|
|
384
|
+
result[k] = v.to_dict()
|
|
363
385
|
elif issubclass(type(v), list):
|
|
364
386
|
tmp = []
|
|
365
387
|
for e in v:
|
|
366
388
|
if isinstance(e, Configuration):
|
|
367
|
-
tmp.append(e.
|
|
389
|
+
tmp.append(e.to_dict())
|
|
368
390
|
else:
|
|
369
391
|
tmp.append(e)
|
|
370
392
|
result[k] = tmp
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: appsettings2
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.6
|
|
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
6
|
License: MIT License
|
|
@@ -32,7 +32,6 @@ Keywords: appsettings,configuration
|
|
|
32
32
|
Classifier: Intended Audience :: Developers
|
|
33
33
|
Classifier: Operating System :: OS Independent
|
|
34
34
|
Classifier: Programming Language :: Python
|
|
35
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
36
35
|
Requires-Python: >=3.11
|
|
37
36
|
Description-Content-Type: text/markdown
|
|
38
37
|
License-File: LICENSE
|
|
@@ -43,7 +42,7 @@ Requires-Dist: flake8; extra == "dev"
|
|
|
43
42
|
Requires-Dist: flake8-pyproject; extra == "dev"
|
|
44
43
|
Requires-Dist: mypy; extra == "dev"
|
|
45
44
|
Requires-Dist: pep8-naming; extra == "dev"
|
|
46
|
-
Requires-Dist: punit>=1.4.
|
|
45
|
+
Requires-Dist: punit>=1.4.9; extra == "dev"
|
|
47
46
|
Requires-Dist: twine; extra == "dev"
|
|
48
47
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
49
48
|
Dynamic: license-file
|
|
@@ -132,12 +131,12 @@ json = """{
|
|
|
132
131
|
|
|
133
132
|
class ConnStrs:
|
|
134
133
|
"""An ugly class name to demonstrate the class name does not matter."""
|
|
135
|
-
SampleDB:str
|
|
134
|
+
SampleDB: str
|
|
136
135
|
|
|
137
136
|
class AppSettings:
|
|
138
|
-
ConnectionStrings:ConnStrs
|
|
139
|
-
EnableSwagger:bool
|
|
140
|
-
MaxBatchSize:int
|
|
137
|
+
ConnectionStrings: ConnStrs
|
|
138
|
+
EnableSwagger: bool
|
|
139
|
+
MaxBatchSize: int
|
|
141
140
|
|
|
142
141
|
configuration = ConfigurationBuilder()\
|
|
143
142
|
.add_provider(JsonConfigurationProvider(json=json))
|
|
@@ -170,7 +169,7 @@ You can transform a `Configuration` instance into a dictionary (as a copy.) If y
|
|
|
170
169
|
|
|
171
170
|
```python
|
|
172
171
|
configuration = builder.build()
|
|
173
|
-
d = configuration.
|
|
172
|
+
d = configuration.to_dict()
|
|
174
173
|
print(d)
|
|
175
174
|
```
|
|
176
175
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/ConfigurationProvider.py
RENAMED
|
File without changes
|
|
File without changes
|
{appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/JsonConfigurationProvider.py
RENAMED
|
File without changes
|
{appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/TomlConfigurationProvider.py
RENAMED
|
File without changes
|
{appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/YamlConfigurationProvider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|