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.
Files changed (23) hide show
  1. {appsettings2-2.0.1/src/appsettings2.egg-info → appsettings2-2.0.6}/PKG-INFO +7 -8
  2. {appsettings2-2.0.1 → appsettings2-2.0.6}/README.md +5 -5
  3. {appsettings2-2.0.1 → appsettings2-2.0.6}/pyproject.toml +3 -4
  4. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/Configuration.py +44 -22
  5. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/__init__.py +2 -2
  6. {appsettings2-2.0.1 → appsettings2-2.0.6/src/appsettings2.egg-info}/PKG-INFO +7 -8
  7. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/requires.txt +1 -1
  8. {appsettings2-2.0.1 → appsettings2-2.0.6}/LICENSE +0 -0
  9. {appsettings2-2.0.1 → appsettings2-2.0.6}/setup.cfg +0 -0
  10. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/ConfigurationBuilder.py +0 -0
  11. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/ConfigurationException.py +0 -0
  12. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/helpers.py +0 -0
  13. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/CommandLineConfigurationProvider.py +0 -0
  14. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/ConfigurationProvider.py +0 -0
  15. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/EnvironmentConfigurationProvider.py +0 -0
  16. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/JsonConfigurationProvider.py +0 -0
  17. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/TomlConfigurationProvider.py +0 -0
  18. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/YamlConfigurationProvider.py +0 -0
  19. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/providers/__init__.py +0 -0
  20. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2/py.typed +0 -0
  21. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/SOURCES.txt +0 -0
  22. {appsettings2-2.0.1 → appsettings2-2.0.6}/src/appsettings2.egg-info/dependency_links.txt +0 -0
  23. {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.1
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.0; extra == "dev"
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.toDictionary()
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.toDictionary()
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.1"
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.0)",
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 logging
4
+ from __future__ import annotations
5
+
5
6
  import json
7
+ import logging
6
8
  import re
7
9
  import types
8
- import typing
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 = None if not scrubkeys else re.compile(
33
- r'[^A-Za-z0-9_]', re.IGNORECASE | re.UNICODE)
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 __recursive_bind(self, target: object, source: 'Configuration|dict') -> Any:
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 = typing.get_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 = typing.get_type_hints(target)
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) or isinstance(lval, types.MethodType):
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 = typing.get_type_hints(getattr(prop, 'fget'))
124
- if phints is None or (not issubclass(type(phints), dict)):
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 typing.get_origin(ahint) is dict:
141
- lval = rval.toDictionary()
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 typing.get_origin(ahint) is list:
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 | dict):
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.toDictionary()
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.toDictionary())
389
+ tmp.append(e.to_dict())
368
390
  else:
369
391
  tmp.append(e)
370
392
  result[k] = tmp
@@ -10,8 +10,8 @@ from . import helpers
10
10
  from . import providers
11
11
 
12
12
 
13
- __version__ = '2.0.1'
14
- __commit__ = '0c59d3c'
13
+ __version__ = '2.0.6'
14
+ __commit__ = '7ba903a'
15
15
  __all__ = [
16
16
  '__version__', '__commit__',
17
17
  'Configuration',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appsettings2
3
- Version: 2.0.1
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.0; extra == "dev"
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.toDictionary()
172
+ d = configuration.to_dict()
174
173
  print(d)
175
174
  ```
176
175
 
@@ -6,6 +6,6 @@ flake8
6
6
  flake8-pyproject
7
7
  mypy
8
8
  pep8-naming
9
- punit>=1.4.0
9
+ punit>=1.4.9
10
10
  twine
11
11
  types-PyYAML
File without changes
File without changes