appsettings2 1.3.7__tar.gz → 2.0.1__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-1.3.7/src/appsettings2.egg-info → appsettings2-2.0.1}/PKG-INFO +13 -13
- {appsettings2-1.3.7 → appsettings2-2.0.1}/README.md +8 -8
- {appsettings2-1.3.7 → appsettings2-2.0.1}/pyproject.toml +12 -5
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/Configuration.py +107 -72
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/ConfigurationBuilder.py +59 -27
- appsettings2-2.0.1/src/appsettings2/ConfigurationException.py +12 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/__init__.py +8 -5
- appsettings2-2.0.1/src/appsettings2/helpers.py +44 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/CommandLineConfigurationProvider.py +8 -8
- appsettings2-2.0.1/src/appsettings2/providers/ConfigurationProvider.py +20 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/EnvironmentConfigurationProvider.py +3 -4
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/JsonConfigurationProvider.py +29 -27
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/TomlConfigurationProvider.py +27 -26
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/YamlConfigurationProvider.py +30 -25
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/providers/__init__.py +1 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1/src/appsettings2.egg-info}/PKG-INFO +13 -13
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2.egg-info/requires.txt +3 -0
- appsettings2-1.3.7/src/appsettings2/ConfigurationException.py +0 -10
- appsettings2-1.3.7/src/appsettings2/helpers.py +0 -29
- appsettings2-1.3.7/src/appsettings2/providers/ConfigurationProvider.py +0 -17
- {appsettings2-1.3.7 → appsettings2-2.0.1}/LICENSE +0 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/setup.cfg +0 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2/py.typed +0 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2.egg-info/SOURCES.txt +0 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/src/appsettings2.egg-info/dependency_links.txt +0 -0
- {appsettings2-1.3.7 → appsettings2-2.0.1}/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:
|
|
3
|
+
Version: 2.0.1
|
|
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
|
|
@@ -31,10 +31,7 @@ Project-URL: Repository, https://github.com/wilson0x4d/appsettings2.git
|
|
|
31
31
|
Keywords: appsettings,configuration
|
|
32
32
|
Classifier: Intended Audience :: Developers
|
|
33
33
|
Classifier: Operating System :: OS Independent
|
|
34
|
-
Classifier: Programming Language :: Python
|
|
35
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
34
|
+
Classifier: Programming Language :: Python
|
|
38
35
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
39
36
|
Requires-Python: >=3.11
|
|
40
37
|
Description-Content-Type: text/markdown
|
|
@@ -42,7 +39,10 @@ License-File: LICENSE
|
|
|
42
39
|
Requires-Dist: pyyaml>=6.0.2
|
|
43
40
|
Provides-Extra: dev
|
|
44
41
|
Requires-Dist: coverage; extra == "dev"
|
|
42
|
+
Requires-Dist: flake8; extra == "dev"
|
|
43
|
+
Requires-Dist: flake8-pyproject; extra == "dev"
|
|
45
44
|
Requires-Dist: mypy; extra == "dev"
|
|
45
|
+
Requires-Dist: pep8-naming; extra == "dev"
|
|
46
46
|
Requires-Dist: punit>=1.4.0; extra == "dev"
|
|
47
47
|
Requires-Dist: twine; extra == "dev"
|
|
48
48
|
Requires-Dist: types-PyYAML; extra == "dev"
|
|
@@ -69,9 +69,9 @@ from appsettings2 import *
|
|
|
69
69
|
from appsettings2.providers import *
|
|
70
70
|
|
|
71
71
|
config = ConfigurationBuilder()\
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
.
|
|
72
|
+
.add_provider(JsonConfigurationProvider(f'appsettings.json'))\
|
|
73
|
+
.add_provider(JsonConfigurationProvider(f'appsettings.Development.json', required=False))\
|
|
74
|
+
.add_provider(EnvironmentConfigurationProvider())\
|
|
75
75
|
.build()
|
|
76
76
|
|
|
77
77
|
print(config)
|
|
@@ -140,7 +140,7 @@ class AppSettings:
|
|
|
140
140
|
MaxBatchSize:int
|
|
141
141
|
|
|
142
142
|
configuration = ConfigurationBuilder()\
|
|
143
|
-
.
|
|
143
|
+
.add_provider(JsonConfigurationProvider(json=json))
|
|
144
144
|
.build()
|
|
145
145
|
|
|
146
146
|
settings = AppSettings()
|
|
@@ -184,7 +184,7 @@ print(d)
|
|
|
184
184
|
|
|
185
185
|
### Custom Provider Development
|
|
186
186
|
|
|
187
|
-
You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `
|
|
187
|
+
You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populate_configuration(...)`.
|
|
188
188
|
|
|
189
189
|
For a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:
|
|
190
190
|
|
|
@@ -192,7 +192,7 @@ For a peek at the simplicity of provider implementation, this is `ConfigurationP
|
|
|
192
192
|
class ConfigurationProvider(abstract):
|
|
193
193
|
|
|
194
194
|
@abstractmethod
|
|
195
|
-
def
|
|
195
|
+
def populate_configuration(self, configuration:Configuration) -> None:
|
|
196
196
|
"""The ConfigurationProvider will populate the provided Configuration instance."""
|
|
197
197
|
pass
|
|
198
198
|
```
|
|
@@ -206,10 +206,10 @@ There are several helpers available which simplify loading configurations. Consi
|
|
|
206
206
|
```python
|
|
207
207
|
import appsettings2
|
|
208
208
|
|
|
209
|
-
config = appsettings2.
|
|
209
|
+
config = appsettings2.get_configuration()
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
-
You can read more about `
|
|
212
|
+
You can read more about `get_configuration` in [the docs](https://appsettings2.readthedocs.io/en/latest/ref/helpers/get_configuration.html).
|
|
213
213
|
|
|
214
214
|
## Contact
|
|
215
215
|
|
|
@@ -19,9 +19,9 @@ from appsettings2 import *
|
|
|
19
19
|
from appsettings2.providers import *
|
|
20
20
|
|
|
21
21
|
config = ConfigurationBuilder()\
|
|
22
|
-
.
|
|
23
|
-
.
|
|
24
|
-
.
|
|
22
|
+
.add_provider(JsonConfigurationProvider(f'appsettings.json'))\
|
|
23
|
+
.add_provider(JsonConfigurationProvider(f'appsettings.Development.json', required=False))\
|
|
24
|
+
.add_provider(EnvironmentConfigurationProvider())\
|
|
25
25
|
.build()
|
|
26
26
|
|
|
27
27
|
print(config)
|
|
@@ -90,7 +90,7 @@ class AppSettings:
|
|
|
90
90
|
MaxBatchSize:int
|
|
91
91
|
|
|
92
92
|
configuration = ConfigurationBuilder()\
|
|
93
|
-
.
|
|
93
|
+
.add_provider(JsonConfigurationProvider(json=json))
|
|
94
94
|
.build()
|
|
95
95
|
|
|
96
96
|
settings = AppSettings()
|
|
@@ -134,7 +134,7 @@ print(d)
|
|
|
134
134
|
|
|
135
135
|
### Custom Provider Development
|
|
136
136
|
|
|
137
|
-
You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `
|
|
137
|
+
You can implement custom Configuration Providers by subclassing `ConfigurationProvider` and implementing `populate_configuration(...)`.
|
|
138
138
|
|
|
139
139
|
For a peek at the simplicity of provider implementation, this is `ConfigurationProvider`:
|
|
140
140
|
|
|
@@ -142,7 +142,7 @@ For a peek at the simplicity of provider implementation, this is `ConfigurationP
|
|
|
142
142
|
class ConfigurationProvider(abstract):
|
|
143
143
|
|
|
144
144
|
@abstractmethod
|
|
145
|
-
def
|
|
145
|
+
def populate_configuration(self, configuration:Configuration) -> None:
|
|
146
146
|
"""The ConfigurationProvider will populate the provided Configuration instance."""
|
|
147
147
|
pass
|
|
148
148
|
```
|
|
@@ -156,10 +156,10 @@ There are several helpers available which simplify loading configurations. Consi
|
|
|
156
156
|
```python
|
|
157
157
|
import appsettings2
|
|
158
158
|
|
|
159
|
-
config = appsettings2.
|
|
159
|
+
config = appsettings2.get_configuration()
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
-
You can read more about `
|
|
162
|
+
You can read more about `get_configuration` in [the docs](https://appsettings2.readthedocs.io/en/latest/ref/helpers/get_configuration.html).
|
|
163
163
|
|
|
164
164
|
## Contact
|
|
165
165
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "appsettings2"
|
|
3
|
-
version = "
|
|
3
|
+
version = "2.0.1"
|
|
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,10 +11,7 @@ license = {file = "LICENSE"}
|
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Intended Audience :: Developers",
|
|
13
13
|
"Operating System :: OS Independent",
|
|
14
|
-
"Programming Language :: Python
|
|
15
|
-
"Programming Language :: Python :: 3.12",
|
|
16
|
-
"Programming Language :: Python :: 3.13",
|
|
17
|
-
"Programming Language :: Python :: 3.14",
|
|
14
|
+
"Programming Language :: Python",
|
|
18
15
|
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
19
16
|
]
|
|
20
17
|
requires-python = ">=3.11"
|
|
@@ -25,7 +22,10 @@ dependencies = [
|
|
|
25
22
|
[project.optional-dependencies]
|
|
26
23
|
dev = [
|
|
27
24
|
"coverage",
|
|
25
|
+
"flake8",
|
|
26
|
+
"flake8-pyproject",
|
|
28
27
|
"mypy",
|
|
28
|
+
"pep8-naming",
|
|
29
29
|
"punit (>=1.4.0)",
|
|
30
30
|
"twine",
|
|
31
31
|
"types-PyYAML"
|
|
@@ -50,3 +50,10 @@ exclude = [
|
|
|
50
50
|
|
|
51
51
|
[tool.setuptools.package-data]
|
|
52
52
|
"appsettings2" = ["py.typed"]
|
|
53
|
+
|
|
54
|
+
[tool.flake8]
|
|
55
|
+
max-line-length = 120
|
|
56
|
+
ignore = ["D100","E501"]
|
|
57
|
+
per-file-ignore = [
|
|
58
|
+
"tests/*.py:D012",
|
|
59
|
+
]
|
|
@@ -6,48 +6,54 @@ import json
|
|
|
6
6
|
import re
|
|
7
7
|
import types
|
|
8
8
|
import typing
|
|
9
|
-
from typing import Any, TYPE_CHECKING
|
|
9
|
+
from typing import Any, Iterator, TYPE_CHECKING
|
|
10
10
|
import unicodedata
|
|
11
11
|
|
|
12
12
|
from .ConfigurationException import ConfigurationException
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Configuration:
|
|
16
|
-
"""
|
|
17
|
-
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.
|
|
18
|
-
"""
|
|
16
|
+
"""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."""
|
|
19
17
|
|
|
20
|
-
__key_scrub_re:re.Pattern|None
|
|
21
|
-
__keys:dict[str, str]
|
|
22
|
-
__normalize:bool
|
|
18
|
+
__key_scrub_re: re.Pattern | None
|
|
19
|
+
__keys: dict[str, str]
|
|
20
|
+
__normalize: bool
|
|
23
21
|
|
|
24
|
-
def __init__(self,
|
|
22
|
+
def __init__(self, normalize: bool = False, scrubkeys: bool = False) -> None:
|
|
25
23
|
"""
|
|
24
|
+
Initialize *Configuration* instance.
|
|
25
|
+
|
|
26
26
|
:param normalize: Option indicating whether or not attribute names should be normalized to upper-case on the resulting :py:class:`~appsettings2.Configuration` object, defaults to False.
|
|
27
27
|
:param scrubkeys: Option indicating whether or not attribute names should be scrubbed to be compatible with the Python lexer, defaults to False.
|
|
28
28
|
"""
|
|
29
29
|
self.__keys = {}
|
|
30
30
|
self.__logger = logging.getLogger('appsettings2')
|
|
31
31
|
self.__normalize = normalize
|
|
32
|
-
self.__key_scrub_re = None if not scrubkeys else re.compile(
|
|
32
|
+
self.__key_scrub_re = None if not scrubkeys else re.compile(
|
|
33
|
+
r'[^A-Za-z0-9_]', re.IGNORECASE | re.UNICODE)
|
|
33
34
|
|
|
34
35
|
if TYPE_CHECKING:
|
|
35
|
-
|
|
36
|
-
def __getattr__(self, name: str) -> Any:
|
|
37
|
-
|
|
36
|
+
|
|
37
|
+
def __getattr__(self, name: str) -> Any:
|
|
38
|
+
"""Tell type checkers that accessing any attribute returns any type."""
|
|
39
|
+
...
|
|
40
|
+
|
|
38
41
|
# This tells mypy that setting any attribute is allowed
|
|
39
|
-
def __setattr__(self, name: str, value: Any) -> None:
|
|
42
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
43
|
+
"""Tell type checkers that setting any attribute to any value is allowed."""
|
|
44
|
+
...
|
|
40
45
|
|
|
41
|
-
def __delitem__(self, key:str) -> None:
|
|
46
|
+
def __delitem__(self, key: str) -> None:
|
|
47
|
+
"""Delete the specified configuration key."""
|
|
42
48
|
key = key.upper()
|
|
43
49
|
k = self.__keys.get(key)
|
|
44
50
|
if k is not None:
|
|
45
51
|
delattr(self, k)
|
|
46
52
|
self.__keys.pop(key)
|
|
47
53
|
|
|
48
|
-
def __getitem__(self, key:str) -> Any:
|
|
54
|
+
def __getitem__(self, key: str) -> Any:
|
|
49
55
|
"""
|
|
50
|
-
|
|
56
|
+
Get the configuration data associated with the specified configuration key.
|
|
51
57
|
|
|
52
58
|
:param key: The configuration key to get data for. Supports `__` and `:` hierarchical delimiters.
|
|
53
59
|
:return: The configuration data associated with `key`, otherwise raises `KeyError` if `key` was not found.
|
|
@@ -65,27 +71,31 @@ class Configuration:
|
|
|
65
71
|
o = o[part]
|
|
66
72
|
return o
|
|
67
73
|
|
|
68
|
-
def __iter__(self):
|
|
74
|
+
def __iter__(self) -> Iterator[str]:
|
|
75
|
+
"""Iterate configuration keys."""
|
|
69
76
|
return iter(self.__keys.values())
|
|
70
77
|
|
|
71
78
|
def __len__(self) -> int:
|
|
79
|
+
"""Get the number of configuration keys."""
|
|
72
80
|
return len(self.__keys)
|
|
73
81
|
|
|
74
|
-
def
|
|
82
|
+
def __recursive_bind(self, target: object, source: 'Configuration|dict') -> Any:
|
|
75
83
|
if target is None:
|
|
76
84
|
return None
|
|
77
85
|
if hasattr(target, '__class__'):
|
|
78
|
-
|
|
86
|
+
target_type_hints = typing.get_type_hints(
|
|
87
|
+
getattr(target, '__class__'))
|
|
79
88
|
else:
|
|
80
|
-
|
|
81
|
-
names = set(dir(target) |
|
|
89
|
+
target_type_hints = typing.get_type_hints(target)
|
|
90
|
+
names = set(dir(target) | target_type_hints.keys())
|
|
82
91
|
for aname in names:
|
|
83
92
|
if aname.startswith('_'):
|
|
84
93
|
continue
|
|
85
|
-
lval = None if not hasattr(
|
|
94
|
+
lval = None if not hasattr(
|
|
95
|
+
target, aname) else getattr(target, aname)
|
|
86
96
|
if isinstance(lval, types.FunctionType) or isinstance(lval, types.MethodType):
|
|
87
97
|
continue
|
|
88
|
-
ahint =
|
|
98
|
+
ahint = target_type_hints.get(aname)
|
|
89
99
|
rval = source.get(aname)
|
|
90
100
|
if ahint is None:
|
|
91
101
|
# attr has no type hints, attempt to treat as a property
|
|
@@ -95,7 +105,8 @@ class Configuration:
|
|
|
95
105
|
lval = getattr(target, aname)
|
|
96
106
|
except AttributeError:
|
|
97
107
|
lval = None
|
|
98
|
-
self.__logger.debug(
|
|
108
|
+
self.__logger.debug(
|
|
109
|
+
f'Failed to bind {aname}', exc_info=True)
|
|
99
110
|
if (not hasattr(prop, 'fset') or getattr(prop, 'fset') is None):
|
|
100
111
|
# NOTE: lval is not settable
|
|
101
112
|
if rval is None:
|
|
@@ -107,7 +118,8 @@ class Configuration:
|
|
|
107
118
|
continue
|
|
108
119
|
elif not (issubclass(type(lval), list) and issubclass(type(rval), list)):
|
|
109
120
|
# non-list values cannot be merged
|
|
110
|
-
raise Exception(
|
|
121
|
+
raise Exception(
|
|
122
|
+
f'Cannot bind `None` to attribute `{aname}`')
|
|
111
123
|
phints = typing.get_type_hints(getattr(prop, 'fget'))
|
|
112
124
|
if phints is None or (not issubclass(type(phints), dict)):
|
|
113
125
|
# NOTE: can't get hints from getter, can't bind
|
|
@@ -132,60 +144,64 @@ class Configuration:
|
|
|
132
144
|
if lval is None:
|
|
133
145
|
lval = ahint()
|
|
134
146
|
setattr(target, aname, lval)
|
|
135
|
-
self.
|
|
147
|
+
self.__recursive_bind(lval, rval)
|
|
136
148
|
elif typing.get_origin(ahint) is list:
|
|
137
|
-
|
|
149
|
+
element_type = ahint.__args__[0]
|
|
138
150
|
if lval is None:
|
|
139
151
|
lval = ahint()
|
|
140
152
|
setattr(target, aname, lval)
|
|
141
153
|
for e in rval:
|
|
142
154
|
lval.append(
|
|
143
|
-
self.
|
|
155
|
+
self.__recursive_bind_type(element_type, e)
|
|
144
156
|
)
|
|
145
157
|
else:
|
|
146
158
|
setattr(target, aname, rval)
|
|
147
159
|
return target
|
|
148
160
|
|
|
149
|
-
def
|
|
150
|
-
if isinstance(source,
|
|
161
|
+
def __recursive_bind_type(self, element_type: type, source: Any) -> Any:
|
|
162
|
+
if isinstance(source, element_type):
|
|
151
163
|
return source
|
|
152
|
-
elif
|
|
164
|
+
elif element_type is float:
|
|
153
165
|
return float(source)
|
|
154
|
-
elif
|
|
166
|
+
elif element_type is int:
|
|
155
167
|
return int(source)
|
|
156
|
-
elif
|
|
168
|
+
elif element_type is str:
|
|
157
169
|
return str(source)
|
|
158
170
|
elif isinstance(source, Configuration | dict):
|
|
159
|
-
v =
|
|
160
|
-
return self.
|
|
171
|
+
v = element_type()
|
|
172
|
+
return self.__recursive_bind(v, source)
|
|
161
173
|
else:
|
|
162
|
-
raise ConfigurationException(
|
|
174
|
+
raise ConfigurationException(
|
|
175
|
+
f'Recursive bind to type `{element_type}` from `{type(source)}` is not supported.')
|
|
163
176
|
|
|
164
|
-
def __scrub_key(self, key:str) -> str:
|
|
177
|
+
def __scrub_key(self, key: str) -> str:
|
|
165
178
|
"""Scrubs a key for use as an attribute/identifier according to the Python lexer/standard."""
|
|
166
179
|
key = key.replace(':', '__').replace('.', '_')
|
|
167
180
|
return key if self.__key_scrub_re is None else self.__key_scrub_re.sub(
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
181
|
+
self.__scrub_uc,
|
|
182
|
+
unicodedata.normalize(
|
|
183
|
+
'NFKC',
|
|
184
|
+
key))
|
|
172
185
|
|
|
173
|
-
def __scrub_uc(self, m:re.Match) -> str:
|
|
186
|
+
def __scrub_uc(self, m: re.Match) -> str:
|
|
174
187
|
match unicodedata.category(m[0]):
|
|
175
|
-
case 'Lu' | 'Ll' | 'Lt' | 'Lm' | 'Lo' | 'Nl' | 'Mn' | 'Mc' | 'Nd' | 'Pc'
|
|
188
|
+
case 'Lu' | 'Ll' | 'Lt' | 'Lm' | 'Lo' | 'Nl' | 'Mn' | 'Mc' | 'Nd' | 'Pc':
|
|
176
189
|
return m[0]
|
|
177
190
|
case _:
|
|
178
191
|
return '_'
|
|
179
192
|
|
|
180
|
-
def __setitem__(self, key:str, value:Any) -> None:
|
|
193
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
194
|
+
"""Set configuration key to specified *value*."""
|
|
181
195
|
self.set(key, value)
|
|
182
196
|
|
|
183
197
|
def __str__(self) -> str:
|
|
184
|
-
|
|
198
|
+
"""Render the configuration as a JSON string."""
|
|
199
|
+
return json.dumps(self.to_dict(), indent=None, ensure_ascii=False)
|
|
185
200
|
|
|
186
|
-
def bind(self, target:object, key:str|None = None) -> Any:
|
|
201
|
+
def bind(self, target: object, key: str | None = None) -> Any:
|
|
187
202
|
"""
|
|
188
203
|
Binds the configuration values into the target object.
|
|
204
|
+
|
|
189
205
|
Can optionally specify a configuration key to bind from.
|
|
190
206
|
|
|
191
207
|
:param target: The object to bind configuration data into.
|
|
@@ -195,43 +211,50 @@ class Configuration:
|
|
|
195
211
|
if target is None:
|
|
196
212
|
raise ConfigurationException('Missing required argument: target')
|
|
197
213
|
if key is None:
|
|
198
|
-
return self.
|
|
214
|
+
return self.__recursive_bind(target, self)
|
|
199
215
|
else:
|
|
200
216
|
source = self.get(key)
|
|
201
217
|
if source is not None:
|
|
202
|
-
|
|
203
|
-
if
|
|
204
|
-
return self.
|
|
218
|
+
source_type = type(source)
|
|
219
|
+
if source_type is Configuration or source_type is dict:
|
|
220
|
+
return self.__recursive_bind(target, source)
|
|
205
221
|
else:
|
|
206
|
-
raise ConfigurationException(
|
|
222
|
+
raise ConfigurationException(
|
|
223
|
+
f'Bind of source type `{type(source)}` is not supported.')
|
|
207
224
|
return target
|
|
208
225
|
|
|
209
226
|
def clear(self) -> None:
|
|
227
|
+
"""Clear all configuration data."""
|
|
210
228
|
while len(self.__keys) > 0:
|
|
211
229
|
t = self.__keys.popitem()
|
|
212
230
|
delattr(self, t[1])
|
|
213
231
|
|
|
214
232
|
@staticmethod
|
|
215
|
-
def
|
|
233
|
+
def from_dict(source: dict, normalize: bool = False, scrubkeys: bool = False) -> 'Configuration':
|
|
216
234
|
"""
|
|
217
|
-
|
|
235
|
+
Construct a :py:class:`~appsettings2.Configuration` instance from the supplied dictionary `source`.
|
|
218
236
|
|
|
219
237
|
:param source: The dictionary object to populate from.
|
|
220
238
|
:param normalize: Option indicating whether or not attribute names should be normalized to upper-case on the resulting :py:class:`~appsettings2.Configuration` object, defaults to False.
|
|
221
239
|
:param scrubkeys: Option indicating whether or not attribute names should be scrubbed to be compatible with the Python lexer, defaults to False.
|
|
222
240
|
:return: A :py:class:`~appsettings2.Configuration` object derived from the `source` parameter.
|
|
223
241
|
"""
|
|
224
|
-
config:Configuration = Configuration(
|
|
242
|
+
config: Configuration = Configuration(
|
|
243
|
+
normalize=normalize, scrubkeys=scrubkeys)
|
|
225
244
|
for kvp in source.items():
|
|
226
245
|
v = kvp[1]
|
|
227
246
|
if issubclass(type(v), dict):
|
|
228
|
-
v = Configuration.
|
|
247
|
+
v = Configuration.from_dict(
|
|
248
|
+
v, normalize=normalize, scrubkeys=scrubkeys)
|
|
229
249
|
config.set(kvp[0], v)
|
|
230
250
|
return config
|
|
231
251
|
|
|
232
|
-
|
|
252
|
+
fromDictionary = from_dict # noqa: N815
|
|
253
|
+
"""⚠️ DEPRECATED: use ``from_dict(...)`` instead."""
|
|
254
|
+
|
|
255
|
+
def get(self, key: str, default: Any = None) -> Any:
|
|
233
256
|
"""
|
|
234
|
-
|
|
257
|
+
Get the configuration data associated with the specified `key`.
|
|
235
258
|
|
|
236
259
|
:param key: The configuration key to get data for. Supports `__` and `:` hierarchical delimiters.
|
|
237
260
|
:param default: The value to be returned if `key` does not exist, defaults to None
|
|
@@ -250,10 +273,12 @@ class Configuration:
|
|
|
250
273
|
o = o.get(part, default)
|
|
251
274
|
return o
|
|
252
275
|
|
|
253
|
-
def has_key(self, key:str) -> bool:
|
|
276
|
+
def has_key(self, key: str) -> bool:
|
|
277
|
+
"""Check for a specific configuration key."""
|
|
254
278
|
return self.__keys.get(key.upper()) is not None
|
|
255
279
|
|
|
256
|
-
def items(self) -> list[tuple[str,Any]]:
|
|
280
|
+
def items(self) -> list[tuple[str, Any]]:
|
|
281
|
+
"""Get all key-value pairs as individal ``tuple`` items."""
|
|
257
282
|
it = []
|
|
258
283
|
for k in self.keys():
|
|
259
284
|
v = self.get(k)
|
|
@@ -261,19 +286,21 @@ class Configuration:
|
|
|
261
286
|
return it
|
|
262
287
|
|
|
263
288
|
def keys(self) -> list[str]:
|
|
289
|
+
"""Get a list of all configuration keys."""
|
|
264
290
|
return list(self.__keys.values())
|
|
265
291
|
|
|
266
|
-
def pop(self, key:str) -> Any:
|
|
292
|
+
def pop(self, key: str) -> Any:
|
|
293
|
+
"""Delete a specific configuration key."""
|
|
267
294
|
value = self[key]
|
|
268
295
|
del self[key]
|
|
269
296
|
return value
|
|
270
297
|
|
|
271
|
-
def set(self, key:str, value:Any) -> None:
|
|
298
|
+
def set(self, key: str, value: Any) -> None:
|
|
272
299
|
"""
|
|
273
|
-
|
|
300
|
+
Set the configuration value for a configuration key.
|
|
274
301
|
|
|
275
|
-
:param key: The key to associate the configuration
|
|
276
|
-
:param value: The
|
|
302
|
+
:param key: The *key* to associate the configuration *value*.
|
|
303
|
+
:param value: The *value* to be associated with the configuration *key*.
|
|
277
304
|
"""
|
|
278
305
|
if self.__normalize:
|
|
279
306
|
key = key.upper()
|
|
@@ -283,7 +310,8 @@ class Configuration:
|
|
|
283
310
|
if o == self:
|
|
284
311
|
k = self.__keys.get(parts[i].upper())
|
|
285
312
|
if k is None:
|
|
286
|
-
c = Configuration(normalize=self.__normalize, scrubkeys=(
|
|
313
|
+
c = Configuration(normalize=self.__normalize, scrubkeys=(
|
|
314
|
+
self.__key_scrub_re is None))
|
|
287
315
|
setattr(o, self.__scrub_key(parts[i]), c)
|
|
288
316
|
self.__keys[parts[i].upper()] = parts[i]
|
|
289
317
|
o = c
|
|
@@ -291,19 +319,22 @@ class Configuration:
|
|
|
291
319
|
o = getattr(self, self.__scrub_key(k))
|
|
292
320
|
else:
|
|
293
321
|
if not o.has_key(parts[i]):
|
|
294
|
-
c = Configuration(normalize=self.__normalize, scrubkeys=(
|
|
322
|
+
c = Configuration(normalize=self.__normalize, scrubkeys=(
|
|
323
|
+
self.__key_scrub_re is None))
|
|
295
324
|
o.set(parts[i], c)
|
|
296
325
|
o = c
|
|
297
326
|
else:
|
|
298
327
|
o = o.get(parts[i])
|
|
299
328
|
vtype = type(value)
|
|
300
329
|
if issubclass(vtype, dict):
|
|
301
|
-
value = Configuration.
|
|
330
|
+
value = Configuration.from_dict(
|
|
331
|
+
value, normalize=self.__normalize, scrubkeys=self.__key_scrub_re is not None)
|
|
302
332
|
elif issubclass(vtype, list):
|
|
303
|
-
l = []
|
|
333
|
+
l: list[Any] = list[Any]()
|
|
304
334
|
for e in value:
|
|
305
335
|
if issubclass(type(e), dict):
|
|
306
|
-
l.append(Configuration.
|
|
336
|
+
l.append(Configuration.from_dict(
|
|
337
|
+
e, normalize=self.__normalize, scrubkeys=self.__key_scrub_re is not None))
|
|
307
338
|
else:
|
|
308
339
|
l.append(e)
|
|
309
340
|
value = l
|
|
@@ -318,13 +349,13 @@ class Configuration:
|
|
|
318
349
|
else:
|
|
319
350
|
o.set(key, value)
|
|
320
351
|
|
|
321
|
-
def
|
|
352
|
+
def to_dict(self) -> dict[str, Any]:
|
|
322
353
|
"""
|
|
323
|
-
|
|
354
|
+
Create a dictionary from the `Configuration` object.
|
|
324
355
|
|
|
325
356
|
:return: A dictionary containing all keys and their associated values, in a structure that mimics the structure if the data contained within the `Configuration` object.
|
|
326
357
|
"""
|
|
327
|
-
result = dict[str,Any]()
|
|
358
|
+
result = dict[str, Any]()
|
|
328
359
|
for k in self.__keys.values():
|
|
329
360
|
v = getattr(self, self.__scrub_key(k))
|
|
330
361
|
if isinstance(v, Configuration):
|
|
@@ -341,7 +372,11 @@ class Configuration:
|
|
|
341
372
|
result[k] = v
|
|
342
373
|
return result
|
|
343
374
|
|
|
375
|
+
toDictionary = to_dict # noqa: N815
|
|
376
|
+
"""⚠️ DEPRECATED: use ``to_dict(...)`` instead."""
|
|
377
|
+
|
|
344
378
|
def values(self) -> list[Any]:
|
|
379
|
+
"""Get a list of all configuration values."""
|
|
345
380
|
values = []
|
|
346
381
|
for k in self.__keys.values():
|
|
347
382
|
v = getattr(self, self.__scrub_key(k))
|