appsettings2 0.1.4__tar.gz → 0.3.0__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.
Potentially problematic release.
This version of appsettings2 might be problematic. Click here for more details.
- {appsettings2-0.1.4 → appsettings2-0.3.0}/PKG-INFO +75 -12
- {appsettings2-0.1.4 → appsettings2-0.3.0}/README.md +74 -11
- {appsettings2-0.1.4 → appsettings2-0.3.0}/pyproject.toml +1 -1
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/Configuration.py +99 -71
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/ConfigurationBuilder.py +3 -2
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/__init__.py +1 -1
- {appsettings2-0.1.4 → appsettings2-0.3.0}/LICENSE +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/ConfigurationException.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/CommandLineConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/ConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/EnvironmentConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/JsonConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/TomlConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/YamlConfigurationProvider.py +0 -0
- {appsettings2-0.1.4 → appsettings2-0.3.0}/src/providers/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: appsettings2
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A python library that unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data.
|
|
5
5
|
License: MIT
|
|
6
6
|
Author: Shaun Wilson
|
|
@@ -199,7 +199,7 @@ When loaded using `JsonConfigurationProvider` the resulting `Configuration` obje
|
|
|
199
199
|
from appsettings2 import *
|
|
200
200
|
from appsettings2.providers import *
|
|
201
201
|
|
|
202
|
-
config = ConfigurationBuilder(
|
|
202
|
+
config = ConfigurationBuilder()\
|
|
203
203
|
.addProvider(JsonConfigurationProvider('example.json'))\
|
|
204
204
|
.build()
|
|
205
205
|
|
|
@@ -215,25 +215,88 @@ The `TomlConfigurationProvider` should be functionally identical to the JSON pro
|
|
|
215
215
|
|
|
216
216
|
The `YamlConfigurationProvider` should be functionally identical to the JSON and TOML providers, with the obvious difference that it loads Configuration data from a yaml file.
|
|
217
217
|
|
|
218
|
-
##
|
|
218
|
+
## Special Attribute Name Handling
|
|
219
219
|
|
|
220
|
-
|
|
220
|
+
### Upper-Case Attribute Names
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
`ConfigurationBuilder` accepts a parameter `normalize:bool` which defaults to `False` causing `Configuration` attributes to preserve the casing of keys from the input configuration.
|
|
223
223
|
|
|
224
|
-
|
|
224
|
+
If you pass `normalize=True`, all `Configuration` instances created by that builder will normalize `Configuration` attributes to upper-case.
|
|
225
225
|
|
|
226
|
-
|
|
226
|
+
In both cases the `Configuration` object allows access to data in a case-insensitive fashion. Normalization _ONLY_ affects the resulting object attributes.
|
|
227
227
|
|
|
228
|
-
|
|
228
|
+
To illustrate, consider the following snippet:
|
|
229
229
|
|
|
230
230
|
```python
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
231
|
+
configuration = Configuration(normalize=True)
|
|
232
|
+
configuration.set('ConnectionStrings__SampleDb', 'blah')
|
|
233
|
+
# normalized attributes looks like this:
|
|
234
|
+
value = configuration.CONNECTIONSTRINGS.SAMPLEDB
|
|
235
|
+
# but you can still access the data case-insensitive:
|
|
236
|
+
value = configuration.get('connectionstrings__SAMPLEDB')
|
|
237
|
+
value = configuration['cOnNeCtioNsTriNGs']['sampledb']
|
|
238
|
+
# however, by default (where normalize=False):
|
|
239
|
+
configuration = Configuration()
|
|
240
|
+
configuration.set('ConnectionStrings__SampleDb', 'blah')
|
|
241
|
+
# attributes look like this:
|
|
242
|
+
value = configuration.ConnectionStrings.SampleDb
|
|
243
|
+
# and as before, you have case-insensitive access:
|
|
244
|
+
value = configuration.get('connectionstrings__SAMPLEDB')
|
|
245
|
+
value = configuration['cOnNeCtioNsTriNGs']['sampledb']
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Normalization has no effect on `bind(...)`, which operates in a case-insensitive fashion internally.
|
|
249
|
+
|
|
250
|
+
### Lexer-friendly Attribute Names
|
|
251
|
+
|
|
252
|
+
`ConfigurationBuilder` accepts a parameter `scrubkeys:bool` which defaults to `False` causing `Configuration` attributes to preserve the keys from the input configuration even if they would be inaccessible from Python code.
|
|
253
|
+
|
|
254
|
+
If you pass `scrubkeys=True`, all `Configuration` instances created by that builder will generate lexically accessible `Configuration` attributes.
|
|
255
|
+
|
|
256
|
+
This is not enabled by default because it introduces an edge case where configuration keys may collide, albeit very unlikely, this is explained below.
|
|
257
|
+
|
|
258
|
+
Some configuration sources might produce attribute names which are not accessible from Python code. Consider the following JSON snippet:
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"query-tab" : {
|
|
263
|
+
"fragment#left": {
|
|
264
|
+
"input": true
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
This configuration will load, and you can still access the data using keyed methods such as `get(...)` and `set(...)`, but the resulting `Configuration` object will have attributes that cannot be accessed from Python code:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
if True == configuration.query-tab.fragment#left.input:
|
|
274
|
+
pass
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
To accomodate configurations such as these and make them accessible via `Configuration` attributes you may pass `scrubkeys=True`. This will cause any lexically invalid characters to be transformed into an underscore `_` character.
|
|
278
|
+
|
|
279
|
+
Although attribute names are transformed, keys are not. Therefore, given the above JSON snippet the following are equivalent:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
# access via object attributes
|
|
283
|
+
configuration.query_tab.fragement_left.input
|
|
284
|
+
# access using indexers
|
|
285
|
+
configuration['query-tab']['fragment#left']
|
|
286
|
+
# access using other 'key' methods
|
|
287
|
+
configuration.get('query-tab').has_key('fragment#left')
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Lastly, this _does_ make it possible for configuration values to collide, for example, these two keys will collide:
|
|
291
|
+
|
|
292
|
+
```json
|
|
293
|
+
{
|
|
294
|
+
"key#1": true,
|
|
295
|
+
"key-1": false
|
|
296
|
+
}
|
|
234
297
|
```
|
|
235
298
|
|
|
236
|
-
|
|
299
|
+
Attempting to access these values will result in a value of `False` being returned, and setting the value of one key will affect the value of the other, this is because internally there will be only one storage slot/attribute that is shared between them. Although an unlikely scenario, it is for this reason the feature is opt-in only.
|
|
237
300
|
|
|
238
301
|
## Conclusion
|
|
239
302
|
|
|
@@ -178,7 +178,7 @@ When loaded using `JsonConfigurationProvider` the resulting `Configuration` obje
|
|
|
178
178
|
from appsettings2 import *
|
|
179
179
|
from appsettings2.providers import *
|
|
180
180
|
|
|
181
|
-
config = ConfigurationBuilder(
|
|
181
|
+
config = ConfigurationBuilder()\
|
|
182
182
|
.addProvider(JsonConfigurationProvider('example.json'))\
|
|
183
183
|
.build()
|
|
184
184
|
|
|
@@ -194,25 +194,88 @@ The `TomlConfigurationProvider` should be functionally identical to the JSON pro
|
|
|
194
194
|
|
|
195
195
|
The `YamlConfigurationProvider` should be functionally identical to the JSON and TOML providers, with the obvious difference that it loads Configuration data from a yaml file.
|
|
196
196
|
|
|
197
|
-
##
|
|
197
|
+
## Special Attribute Name Handling
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
### Upper-Case Attribute Names
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
`ConfigurationBuilder` accepts a parameter `normalize:bool` which defaults to `False` causing `Configuration` attributes to preserve the casing of keys from the input configuration.
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
If you pass `normalize=True`, all `Configuration` instances created by that builder will normalize `Configuration` attributes to upper-case.
|
|
204
204
|
|
|
205
|
-
|
|
205
|
+
In both cases the `Configuration` object allows access to data in a case-insensitive fashion. Normalization _ONLY_ affects the resulting object attributes.
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
To illustrate, consider the following snippet:
|
|
208
208
|
|
|
209
209
|
```python
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
configuration = Configuration(normalize=True)
|
|
211
|
+
configuration.set('ConnectionStrings__SampleDb', 'blah')
|
|
212
|
+
# normalized attributes looks like this:
|
|
213
|
+
value = configuration.CONNECTIONSTRINGS.SAMPLEDB
|
|
214
|
+
# but you can still access the data case-insensitive:
|
|
215
|
+
value = configuration.get('connectionstrings__SAMPLEDB')
|
|
216
|
+
value = configuration['cOnNeCtioNsTriNGs']['sampledb']
|
|
217
|
+
# however, by default (where normalize=False):
|
|
218
|
+
configuration = Configuration()
|
|
219
|
+
configuration.set('ConnectionStrings__SampleDb', 'blah')
|
|
220
|
+
# attributes look like this:
|
|
221
|
+
value = configuration.ConnectionStrings.SampleDb
|
|
222
|
+
# and as before, you have case-insensitive access:
|
|
223
|
+
value = configuration.get('connectionstrings__SAMPLEDB')
|
|
224
|
+
value = configuration['cOnNeCtioNsTriNGs']['sampledb']
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Normalization has no effect on `bind(...)`, which operates in a case-insensitive fashion internally.
|
|
228
|
+
|
|
229
|
+
### Lexer-friendly Attribute Names
|
|
230
|
+
|
|
231
|
+
`ConfigurationBuilder` accepts a parameter `scrubkeys:bool` which defaults to `False` causing `Configuration` attributes to preserve the keys from the input configuration even if they would be inaccessible from Python code.
|
|
232
|
+
|
|
233
|
+
If you pass `scrubkeys=True`, all `Configuration` instances created by that builder will generate lexically accessible `Configuration` attributes.
|
|
234
|
+
|
|
235
|
+
This is not enabled by default because it introduces an edge case where configuration keys may collide, albeit very unlikely, this is explained below.
|
|
236
|
+
|
|
237
|
+
Some configuration sources might produce attribute names which are not accessible from Python code. Consider the following JSON snippet:
|
|
238
|
+
|
|
239
|
+
```json
|
|
240
|
+
{
|
|
241
|
+
"query-tab" : {
|
|
242
|
+
"fragment#left": {
|
|
243
|
+
"input": true
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
This configuration will load, and you can still access the data using keyed methods such as `get(...)` and `set(...)`, but the resulting `Configuration` object will have attributes that cannot be accessed from Python code:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
if True == configuration.query-tab.fragment#left.input:
|
|
253
|
+
pass
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
To accomodate configurations such as these and make them accessible via `Configuration` attributes you may pass `scrubkeys=True`. This will cause any lexically invalid characters to be transformed into an underscore `_` character.
|
|
257
|
+
|
|
258
|
+
Although attribute names are transformed, keys are not. Therefore, given the above JSON snippet the following are equivalent:
|
|
259
|
+
|
|
260
|
+
```python
|
|
261
|
+
# access via object attributes
|
|
262
|
+
configuration.query_tab.fragement_left.input
|
|
263
|
+
# access using indexers
|
|
264
|
+
configuration['query-tab']['fragment#left']
|
|
265
|
+
# access using other 'key' methods
|
|
266
|
+
configuration.get('query-tab').has_key('fragment#left')
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Lastly, this _does_ make it possible for configuration values to collide, for example, these two keys will collide:
|
|
270
|
+
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"key#1": true,
|
|
274
|
+
"key-1": false
|
|
275
|
+
}
|
|
213
276
|
```
|
|
214
277
|
|
|
215
|
-
|
|
278
|
+
Attempting to access these values will result in a value of `False` being returned, and setting the value of one key will affect the value of the other, this is because internally there will be only one storage slot/attribute that is shared between them. Although an unlikely scenario, it is for this reason the feature is opt-in only.
|
|
216
279
|
|
|
217
280
|
## Conclusion
|
|
218
281
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "appsettings2"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "A python library that unifies configuration sources into a Configuration object that can be bound to complex types, or accessed directly for configuration data."
|
|
5
5
|
authors = ["Shaun Wilson <mrshaunwilson@msn.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -3,44 +3,52 @@
|
|
|
3
3
|
|
|
4
4
|
from . import ConfigurationException
|
|
5
5
|
import json
|
|
6
|
+
import re
|
|
6
7
|
import types
|
|
7
8
|
from typing import Any, TypeAlias, get_type_hints
|
|
9
|
+
import unicodedata
|
|
8
10
|
|
|
9
11
|
any:TypeAlias = Any
|
|
10
12
|
|
|
11
13
|
class Configuration:
|
|
12
14
|
"""A configuration class which creates a layer of indirection between configuration providers and configuration consumers."""
|
|
13
15
|
|
|
16
|
+
__key_scrub_re:re.Pattern
|
|
17
|
+
__keys:dict[str, str]
|
|
14
18
|
__normalize:bool
|
|
15
|
-
__len:int = 0
|
|
16
19
|
|
|
17
|
-
def __init__(self, *, normalize:bool = False):
|
|
20
|
+
def __init__(self, *, normalize:bool = False, scrubkeys:bool = False):
|
|
18
21
|
"""By default keys are ingest as-is, pass `True` for `normalize` to normalize kets to lower case."""
|
|
22
|
+
self.__keys = {}
|
|
19
23
|
self.__normalize = normalize
|
|
24
|
+
self.__key_scrub_re = None if not scrubkeys else re.compile(r'[^A-Za-z0-9_]', re.IGNORECASE | re.UNICODE)
|
|
20
25
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
def __delitem__(self, key:str) -> None:
|
|
27
|
+
key = key.upper()
|
|
28
|
+
k = self.__keys.get(key)
|
|
29
|
+
if k != None:
|
|
30
|
+
delattr(self, k)
|
|
31
|
+
self.__keys.pop(key)
|
|
24
32
|
|
|
25
33
|
def __getitem__(self, key:str) -> any:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
parts = key.replace('__', '.').replace(':', '.').split('.')
|
|
35
|
+
o = self
|
|
36
|
+
for part in parts:
|
|
37
|
+
if o == self:
|
|
38
|
+
k = self.__keys.get(part.upper())
|
|
39
|
+
if k == None:
|
|
40
|
+
raise KeyError()
|
|
41
|
+
else:
|
|
42
|
+
o = getattr(o, self.__scrub_key(k))
|
|
43
|
+
else:
|
|
44
|
+
o = o[part]
|
|
45
|
+
return o
|
|
30
46
|
|
|
31
47
|
def __iter__(self):
|
|
32
|
-
return iter(self.
|
|
48
|
+
return iter(self.__keys.values())
|
|
33
49
|
|
|
34
50
|
def __len__(self) -> int:
|
|
35
|
-
return self.
|
|
36
|
-
|
|
37
|
-
def __setitem__(self, key:str, value:any) -> None:
|
|
38
|
-
self.set(key, value)
|
|
39
|
-
|
|
40
|
-
def __delitem__(self, key:str) -> None:
|
|
41
|
-
if hasattr(self, key):
|
|
42
|
-
delattr(self, key)
|
|
43
|
-
self.__len -= 1
|
|
51
|
+
return len(self.__keys)
|
|
44
52
|
|
|
45
53
|
def __recursiveBind(self, target:object, source:'Configuration') -> any:
|
|
46
54
|
targetTypeHints = get_type_hints(target)
|
|
@@ -58,8 +66,7 @@ class Configuration:
|
|
|
58
66
|
lval = rval.toDictionary()
|
|
59
67
|
setattr(target, aname, lval)
|
|
60
68
|
elif ahint is list or ahintstr.startswith('list['):
|
|
61
|
-
|
|
62
|
-
setattr(target, aname, rval)
|
|
69
|
+
raise ConfigurationException(f'lval ({ahint!r}) is incompatible with rval (Configuration)')
|
|
63
70
|
else:
|
|
64
71
|
if lval == None:
|
|
65
72
|
lval = ahint()
|
|
@@ -82,6 +89,31 @@ class Configuration:
|
|
|
82
89
|
setattr(target, aname, None)
|
|
83
90
|
return target
|
|
84
91
|
|
|
92
|
+
def __scrub_key(self, key:str) -> str:
|
|
93
|
+
"""Scrubs a key for use as an attribute/identifier according to the Python lexer/standard."""
|
|
94
|
+
key = key\
|
|
95
|
+
.replace('__', '.')\
|
|
96
|
+
.replace(':', '.')
|
|
97
|
+
return key if None == self.__key_scrub_re else \
|
|
98
|
+
self.__key_scrub_re.sub(
|
|
99
|
+
self.__scrub_uc,
|
|
100
|
+
unicodedata.normalize(
|
|
101
|
+
'NFKC',
|
|
102
|
+
key))
|
|
103
|
+
|
|
104
|
+
def __scrub_uc(self, m:re.Match) -> str:
|
|
105
|
+
match unicodedata.category(m[0]):
|
|
106
|
+
case 'Lu' | 'Ll' | 'Lt' | 'Lm' | 'Lo' | 'Nl' | 'Mn' | 'Mc' | 'Nd' | 'Pc' :
|
|
107
|
+
return m[0]
|
|
108
|
+
case _:
|
|
109
|
+
return '_'
|
|
110
|
+
|
|
111
|
+
def __setitem__(self, key:str, value:any) -> None:
|
|
112
|
+
self.set(key, value)
|
|
113
|
+
|
|
114
|
+
def __str__(self) -> str:
|
|
115
|
+
return json.dumps(self.toDictionary())
|
|
116
|
+
|
|
85
117
|
def bind(self, target:object, key:str|None = None) -> any:
|
|
86
118
|
"""Binds the configuration values into the target object.
|
|
87
119
|
|
|
@@ -94,46 +126,37 @@ class Configuration:
|
|
|
94
126
|
return self.__recursiveBind(target, self.get(key))
|
|
95
127
|
|
|
96
128
|
def clear(self) -> None:
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
self
|
|
129
|
+
while len(self.__keys) > 0:
|
|
130
|
+
t = self.__keys.popitem()
|
|
131
|
+
delattr(self, t[1])
|
|
100
132
|
|
|
101
133
|
def get(self, key:str, default:any = None) -> any:
|
|
102
134
|
"""Gets the configuration element associated with the specified key."""
|
|
103
|
-
if self.__normalize:
|
|
104
|
-
key = key.lower()
|
|
105
135
|
parts = key.replace('__', '.').replace(':', '.').split('.')
|
|
106
136
|
o = self
|
|
107
137
|
for part in parts:
|
|
108
|
-
if
|
|
109
|
-
|
|
110
|
-
|
|
138
|
+
if o == self:
|
|
139
|
+
k = self.__keys.get(part.upper())
|
|
140
|
+
if k == None:
|
|
141
|
+
return default
|
|
142
|
+
else:
|
|
143
|
+
o = getattr(o, self.__scrub_key(k))
|
|
144
|
+
else:
|
|
145
|
+
o = o.get(part, default)
|
|
111
146
|
return o
|
|
112
147
|
|
|
113
148
|
def has_key(self, key:str) -> bool:
|
|
114
|
-
return
|
|
149
|
+
return self.__keys.get(key.upper()) != None
|
|
115
150
|
|
|
116
151
|
def items(self) -> list[tuple[str,any]]:
|
|
117
152
|
it = []
|
|
118
|
-
for k in
|
|
119
|
-
|
|
120
|
-
continue
|
|
121
|
-
v = getattr(self, k)
|
|
122
|
-
if isinstance(v, types.FunctionType) or isinstance(v, types.MethodType):
|
|
123
|
-
continue
|
|
153
|
+
for k in self.keys():
|
|
154
|
+
v = self.get(k)
|
|
124
155
|
it.append((k, v))
|
|
125
156
|
return it
|
|
126
157
|
|
|
127
158
|
def keys(self) -> list[str]:
|
|
128
|
-
|
|
129
|
-
for k in dir(self):
|
|
130
|
-
if k.startswith('_'):
|
|
131
|
-
continue
|
|
132
|
-
v = getattr(self, k)
|
|
133
|
-
if isinstance(v, types.FunctionType) or isinstance(v, types.MethodType):
|
|
134
|
-
continue
|
|
135
|
-
keys.append(k)
|
|
136
|
-
return keys
|
|
159
|
+
return self.__keys.values()
|
|
137
160
|
|
|
138
161
|
def pop(self, key:str) -> any:
|
|
139
162
|
value = self[key]
|
|
@@ -143,46 +166,51 @@ class Configuration:
|
|
|
143
166
|
def set(self, key:str, value:any) -> None:
|
|
144
167
|
"""Sets the configuration element for the specified key."""
|
|
145
168
|
if self.__normalize:
|
|
146
|
-
key = key.
|
|
169
|
+
key = key.upper()
|
|
147
170
|
parts = key.replace('__', '.').replace(':', '.').split('.')
|
|
148
171
|
o = self
|
|
149
172
|
for i in range(len(parts) - 1):
|
|
150
|
-
if
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
173
|
+
if o == self:
|
|
174
|
+
k = self.__keys.get(parts[i].upper())
|
|
175
|
+
if k == None:
|
|
176
|
+
c = Configuration(normalize=self.__normalize, scrubkeys=(None != self.__key_scrub_re))
|
|
177
|
+
setattr(o, self.__scrub_key(parts[i]), c)
|
|
178
|
+
self.__keys[parts[i].upper()] = parts[i]
|
|
179
|
+
o = c
|
|
180
|
+
else:
|
|
181
|
+
o = getattr(self, self.__scrub_key(k))
|
|
154
182
|
else:
|
|
155
|
-
|
|
183
|
+
if not o.has_key(parts[i]):
|
|
184
|
+
c = Configuration(normalize=self.__normalize, scrubkeys=(None != self.__key_scrub_re))
|
|
185
|
+
o.set(parts[i], c)
|
|
186
|
+
o = c
|
|
187
|
+
else:
|
|
188
|
+
o = o.get(parts[i])
|
|
189
|
+
key = parts[-1]
|
|
156
190
|
if o == self:
|
|
157
|
-
|
|
158
|
-
|
|
191
|
+
k = self.__keys.get(key.upper())
|
|
192
|
+
if k != None:
|
|
193
|
+
setattr(self, self.__scrub_key(k), value)
|
|
159
194
|
else:
|
|
160
|
-
self.
|
|
161
|
-
|
|
195
|
+
self.__keys[key.upper()] = key
|
|
196
|
+
setattr(self, self.__scrub_key(key), value)
|
|
162
197
|
else:
|
|
163
|
-
o.set(
|
|
198
|
+
o.set(key, value)
|
|
164
199
|
|
|
165
200
|
def toDictionary(self) -> dict:
|
|
166
201
|
"""Projects a dictionary from the configuration object."""
|
|
167
202
|
result = {}
|
|
168
|
-
for
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if isinstance(aval, Configuration):
|
|
175
|
-
aval = aval.toDictionary()
|
|
176
|
-
result[aname] = aval
|
|
203
|
+
for k in self.__keys.values():
|
|
204
|
+
v = getattr(self, self.__scrub_key(k))
|
|
205
|
+
if isinstance(v, Configuration):
|
|
206
|
+
result[k] = v.toDictionary()
|
|
207
|
+
else:
|
|
208
|
+
result[k] = v
|
|
177
209
|
return result
|
|
178
210
|
|
|
179
211
|
def values(self) -> list[any]:
|
|
180
212
|
values = []
|
|
181
|
-
for k in
|
|
182
|
-
|
|
183
|
-
continue
|
|
184
|
-
v = getattr(self, k)
|
|
185
|
-
if isinstance(v, types.FunctionType) or isinstance(v, types.MethodType):
|
|
186
|
-
continue
|
|
213
|
+
for k in self.__keys.values():
|
|
214
|
+
v = getattr(self, self.__scrub_key(k))
|
|
187
215
|
values.append(v)
|
|
188
216
|
return values
|
|
@@ -13,8 +13,9 @@ class ConfigurationBuilder:
|
|
|
13
13
|
__normalize:bool
|
|
14
14
|
__providers:list[ConfigurationProvider]
|
|
15
15
|
|
|
16
|
-
def __init__(self, *, normalize:bool = False):
|
|
16
|
+
def __init__(self, *, normalize:bool = False, scrubkeys:bool = False):
|
|
17
17
|
self.__normalize = normalize
|
|
18
|
+
self.__scrubkeys = scrubkeys
|
|
18
19
|
self.__providers = []
|
|
19
20
|
|
|
20
21
|
def addProvider(self, provider:ConfigurationProvider) -> 'ConfigurationBuilder':
|
|
@@ -26,7 +27,7 @@ class ConfigurationBuilder:
|
|
|
26
27
|
|
|
27
28
|
def build(self) -> Configuration:
|
|
28
29
|
"""Builds a Configuration object from all providers which have been added to this builder."""
|
|
29
|
-
configuration = Configuration(normalize=self.__normalize)
|
|
30
|
+
configuration = Configuration(normalize=self.__normalize, scrubkeys=self.__scrubkeys)
|
|
30
31
|
for provider in self.__providers:
|
|
31
32
|
provider.populateConfiguration(configuration)
|
|
32
33
|
return configuration
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|