dataconf 3.5.0__tar.gz → 3.7.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.
- {dataconf-3.5.0 → dataconf-3.7.0}/PKG-INFO +3 -3
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/main.py +4 -3
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/utils.py +70 -22
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/version.py +1 -1
- {dataconf-3.5.0 → dataconf-3.7.0}/pyproject.toml +8 -8
- {dataconf-3.5.0 → dataconf-3.7.0}/LICENSE +0 -0
- {dataconf-3.5.0 → dataconf-3.7.0}/README.md +0 -0
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/__init__.py +0 -0
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/cli.py +0 -0
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/exceptions.py +0 -0
- {dataconf-3.5.0 → dataconf-3.7.0}/dataconf/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dataconf
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.0
|
|
4
4
|
Summary: Simple dataclasses configuration management for Python with hocon/json/yaml/properties/env-vars/dict support.
|
|
5
5
|
Home-page: https://github.com/zifeo/dataconf
|
|
6
6
|
License: MPL-2.0
|
|
@@ -15,9 +15,9 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
16
16
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
17
|
Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
|
|
18
|
-
Requires-Dist: isodate (>=0.
|
|
18
|
+
Requires-Dist: isodate (>=0.7.2,<0.8.0)
|
|
19
19
|
Requires-Dist: pyhocon (>=0.3.61,<0.4.0)
|
|
20
|
-
Requires-Dist: pyparsing (>=3.2.
|
|
20
|
+
Requires-Dist: pyparsing (>=3.2.3,<4.0.0)
|
|
21
21
|
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
22
22
|
Project-URL: Repository, https://github.com/zifeo/dataconf
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
@@ -45,13 +45,14 @@ class Multi:
|
|
|
45
45
|
self.kwargs = kwargs
|
|
46
46
|
|
|
47
47
|
def env(self, prefix: str, **kwargs) -> "Multi":
|
|
48
|
-
self.strict = False
|
|
49
48
|
data = env_vars_parse(prefix, os.environ)
|
|
50
|
-
|
|
49
|
+
strict = kwargs.pop("strict", False)
|
|
50
|
+
return self.dict(data, strict=strict, **kwargs)
|
|
51
51
|
|
|
52
52
|
def dict(self, obj: Dict[str, Any], **kwargs) -> "Multi":
|
|
53
53
|
conf = ConfigFactory.from_dict(obj)
|
|
54
|
-
|
|
54
|
+
strict = kwargs.pop("strict", self.strict)
|
|
55
|
+
return Multi(self.confs + [conf], strict, **kwargs)
|
|
55
56
|
|
|
56
57
|
def string(self, s: str, loader: str = HOCON, **kwargs) -> "Multi":
|
|
57
58
|
if loader == YAML:
|
|
@@ -42,6 +42,21 @@ if PY310up:
|
|
|
42
42
|
|
|
43
43
|
NoneType = type(None)
|
|
44
44
|
|
|
45
|
+
_BOOL_TRUE = {"true", "1", "yes", "on", "t", "y"}
|
|
46
|
+
_BOOL_FALSE = {"false", "0", "no", "off", "f", "n"}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def __parse_bool(value: Any):
|
|
50
|
+
if isinstance(value, bool):
|
|
51
|
+
return value
|
|
52
|
+
if isinstance(value, str):
|
|
53
|
+
normalised = value.strip().lower()
|
|
54
|
+
if normalised in _BOOL_TRUE:
|
|
55
|
+
return True
|
|
56
|
+
if normalised in _BOOL_FALSE:
|
|
57
|
+
return False
|
|
58
|
+
return None
|
|
59
|
+
|
|
45
60
|
|
|
46
61
|
def __parse_type(value: Any, clazz: Type, path: str, check: bool):
|
|
47
62
|
try:
|
|
@@ -50,14 +65,16 @@ def __parse_type(value: Any, clazz: Type, path: str, check: bool):
|
|
|
50
65
|
except TypeError:
|
|
51
66
|
pass
|
|
52
67
|
|
|
53
|
-
raise TypeConfigException(
|
|
68
|
+
raise TypeConfigException(
|
|
69
|
+
f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
|
|
70
|
+
)
|
|
54
71
|
|
|
55
72
|
|
|
56
73
|
def is_union(origin):
|
|
57
74
|
return origin is Union or (PY310up and origin is UnionType)
|
|
58
75
|
|
|
59
76
|
|
|
60
|
-
def is_optional(type: Type):
|
|
77
|
+
def is_optional(type: Type | Any | str):
|
|
61
78
|
# Optional = Union[T, NoneType]
|
|
62
79
|
return is_union(get_origin(type)) and NoneType in get_args(type)
|
|
63
80
|
|
|
@@ -66,7 +83,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
66
83
|
if is_dataclass(clazz):
|
|
67
84
|
if not isinstance(value, ConfigTree):
|
|
68
85
|
raise TypeConfigException(
|
|
69
|
-
f"expected type {clazz} at {path}, got {type(value)}"
|
|
86
|
+
f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
|
|
70
87
|
)
|
|
71
88
|
|
|
72
89
|
fs = {}
|
|
@@ -96,15 +113,34 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
96
113
|
# Optional not found
|
|
97
114
|
fs[f.name] = None
|
|
98
115
|
|
|
116
|
+
elif is_dataclass(f.type):
|
|
117
|
+
if all(
|
|
118
|
+
not isinstance(field.default, _MISSING_TYPE)
|
|
119
|
+
or not isinstance(field.default_factory, _MISSING_TYPE)
|
|
120
|
+
or is_optional(field.type)
|
|
121
|
+
for field in fields(f.type)
|
|
122
|
+
):
|
|
123
|
+
fs[f.name] = f.type(
|
|
124
|
+
**{
|
|
125
|
+
field.name: None
|
|
126
|
+
for field in fields(f.type)
|
|
127
|
+
if is_optional(field.type)
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
raise MalformedConfigException(
|
|
132
|
+
f'expected type {clazz} at {path_to_str(path)}, no field "{f.name}" found and {f.type} cannot be implicitly created'
|
|
133
|
+
)
|
|
134
|
+
|
|
99
135
|
else:
|
|
100
136
|
raise MalformedConfigException(
|
|
101
|
-
f
|
|
137
|
+
f'expected type {clazz} at {path_to_str(path)}, no field "{f.name}" found'
|
|
102
138
|
)
|
|
103
139
|
|
|
104
140
|
unexpected_keys = value.keys() - {renamings.get(k, k) for k in fs.keys()}
|
|
105
141
|
if len(unexpected_keys) > 0 and not ignore_unexpected:
|
|
106
142
|
raise UnexpectedKeysException(
|
|
107
|
-
f'unexpected key(s) "{", ".join(unexpected_keys)}" detected for type {clazz} at {path}'
|
|
143
|
+
f'unexpected key(s) "{", ".join(unexpected_keys)}" detected for type {clazz} at {path_to_str(path)}'
|
|
108
144
|
)
|
|
109
145
|
|
|
110
146
|
return clazz(**fs)
|
|
@@ -114,7 +150,9 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
114
150
|
|
|
115
151
|
if origin is list:
|
|
116
152
|
if value is None:
|
|
117
|
-
raise MalformedConfigException(
|
|
153
|
+
raise MalformedConfigException(
|
|
154
|
+
f"expected list at {path_to_str(path)} but received None"
|
|
155
|
+
)
|
|
118
156
|
|
|
119
157
|
if len(args) != 1:
|
|
120
158
|
raise MissingTypeException("expected list with type information: List[?]")
|
|
@@ -128,7 +166,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
128
166
|
if origin is tuple:
|
|
129
167
|
if value is None:
|
|
130
168
|
raise MalformedConfigException(
|
|
131
|
-
f"expected tuple at {path} but received None"
|
|
169
|
+
f"expected tuple at {path_to_str(path)} but received None"
|
|
132
170
|
)
|
|
133
171
|
|
|
134
172
|
if len(args) < 1:
|
|
@@ -181,18 +219,18 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
181
219
|
return None
|
|
182
220
|
|
|
183
221
|
raise TypeConfigException(
|
|
184
|
-
f"expected one of {', '.join(map(str, args))} at {path}, got {type(value)}"
|
|
222
|
+
f"expected one of {', '.join(map(str, args))} at {path_to_str(path)}, got {type(value)}"
|
|
185
223
|
)
|
|
186
224
|
|
|
187
225
|
if clazz is bool:
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
pass
|
|
193
|
-
return __parse_type(value, clazz, path, isinstance(value, bool))
|
|
226
|
+
parsed = __parse_bool(value)
|
|
227
|
+
if parsed is not None:
|
|
228
|
+
return parsed
|
|
229
|
+
return __parse_type(value, clazz, path, False)
|
|
194
230
|
|
|
195
231
|
if clazz is int:
|
|
232
|
+
if isinstance(value, bool):
|
|
233
|
+
return __parse_type(value, clazz, path, False)
|
|
196
234
|
if not strict and value is not None:
|
|
197
235
|
try:
|
|
198
236
|
value = int(value)
|
|
@@ -201,6 +239,8 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
201
239
|
return __parse_type(value, clazz, path, isinstance(value, int))
|
|
202
240
|
|
|
203
241
|
if clazz is float:
|
|
242
|
+
if isinstance(value, bool):
|
|
243
|
+
return __parse_type(value, clazz, path, False)
|
|
204
244
|
if not strict and value is not None:
|
|
205
245
|
try:
|
|
206
246
|
value = float(value)
|
|
@@ -220,13 +260,15 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
220
260
|
return value
|
|
221
261
|
|
|
222
262
|
if isclass(clazz) and (issubclass(clazz, Enum) or issubclass(clazz, IntEnum)):
|
|
223
|
-
if isinstance(value, int):
|
|
263
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
224
264
|
return clazz.__call__(value)
|
|
225
265
|
elif issubclass(clazz, str):
|
|
226
266
|
return clazz(value)
|
|
227
267
|
elif isinstance(value, str):
|
|
228
268
|
return clazz.__getitem__(value)
|
|
229
|
-
raise TypeConfigException(
|
|
269
|
+
raise TypeConfigException(
|
|
270
|
+
f"expected str or int at {path_to_str(path)}, got {type(value)}"
|
|
271
|
+
)
|
|
230
272
|
|
|
231
273
|
if isclass(clazz) and issubclass(clazz, Path):
|
|
232
274
|
return clazz.__call__(value)
|
|
@@ -235,7 +277,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
235
277
|
if value in args:
|
|
236
278
|
return value
|
|
237
279
|
raise TypeConfigException(
|
|
238
|
-
f"expected one of {', '.join(map(str, args))} at {path}, got {value}"
|
|
280
|
+
f"expected one of {', '.join(map(str, args))} at {path_to_str(path)}, got {value}"
|
|
239
281
|
)
|
|
240
282
|
|
|
241
283
|
if clazz is datetime:
|
|
@@ -244,7 +286,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
244
286
|
return isoparse(dt)
|
|
245
287
|
except ValueError as e:
|
|
246
288
|
raise ParseException(
|
|
247
|
-
f"expected type {clazz} at {path}, cannot parse due to {e}"
|
|
289
|
+
f"expected type {clazz} at {path_to_str(path)}, cannot parse due to {e}"
|
|
248
290
|
)
|
|
249
291
|
|
|
250
292
|
if clazz is timedelta:
|
|
@@ -258,7 +300,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
258
300
|
return duration
|
|
259
301
|
except ValueError as e:
|
|
260
302
|
raise ParseException(
|
|
261
|
-
f"expected type {clazz} at {path}, cannot parse due to {e}"
|
|
303
|
+
f"expected type {clazz} at {path_to_str(path)}, cannot parse due to {e}"
|
|
262
304
|
)
|
|
263
305
|
|
|
264
306
|
if clazz is relativedelta:
|
|
@@ -292,17 +334,19 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
|
|
|
292
334
|
elif len(child_successes) > 1:
|
|
293
335
|
matching_classes = "\n- ".join(map(lambda x: x[0].__name__, child_successes))
|
|
294
336
|
raise AmbiguousSubclassException(
|
|
295
|
-
f"""multiple subtypes of {clazz} matched at {path}, use '_type' to disambiguate:\n- {matching_classes}"""
|
|
337
|
+
f"""multiple subtypes of {clazz} matched at {path_to_str(path)}, use '_type' to disambiguate:\n- {matching_classes}"""
|
|
296
338
|
)
|
|
297
339
|
|
|
298
340
|
# no need to check length; false if empty
|
|
299
341
|
if child_failures:
|
|
300
342
|
failures = "\n- ".join([str(c) for c in child_failures])
|
|
301
343
|
raise TypeConfigException(
|
|
302
|
-
f"expected type {clazz} at {path}, failed subclasses:\n- {failures}"
|
|
344
|
+
f"expected type {clazz} at {path_to_str(path)}, failed subclasses:\n- {failures}"
|
|
303
345
|
)
|
|
304
346
|
|
|
305
|
-
raise TypeConfigException(
|
|
347
|
+
raise TypeConfigException(
|
|
348
|
+
f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
|
|
349
|
+
)
|
|
306
350
|
|
|
307
351
|
|
|
308
352
|
def __generate(value: object, path: str):
|
|
@@ -399,6 +443,10 @@ def __env_vars_parse(prefix: str, obj: Dict[str, Any]):
|
|
|
399
443
|
return ret
|
|
400
444
|
|
|
401
445
|
|
|
446
|
+
def path_to_str(path: str) -> str:
|
|
447
|
+
return path if len(path) > 0 else "root"
|
|
448
|
+
|
|
449
|
+
|
|
402
450
|
def __cli_parse(argv: List[str]):
|
|
403
451
|
kvs = {}
|
|
404
452
|
i = 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "dataconf"
|
|
3
|
-
version = "3.
|
|
3
|
+
version = "3.7.0"
|
|
4
4
|
description = "Simple dataclasses configuration management for Python with hocon/json/yaml/properties/env-vars/dict support."
|
|
5
5
|
authors = []
|
|
6
6
|
license = "MPL-2.0"
|
|
@@ -35,20 +35,20 @@ dataconf = 'dataconf.cli:run'
|
|
|
35
35
|
python = "^3.9"
|
|
36
36
|
python-dateutil = "^2.9.0.post0"
|
|
37
37
|
PyYAML = "^6.0.2"
|
|
38
|
-
isodate = "
|
|
38
|
+
isodate = "^0.7.2"
|
|
39
39
|
pyhocon = "^0.3.61"
|
|
40
|
-
pyparsing = "^3.2.
|
|
40
|
+
pyparsing = "^3.2.3"
|
|
41
41
|
|
|
42
42
|
[tool.poetry.group.dev.dependencies]
|
|
43
|
-
pytest = ">=8.
|
|
43
|
+
pytest = ">=8.4.1"
|
|
44
44
|
pre-commit = ">=4.2.0"
|
|
45
|
-
commitizen = ">=4.
|
|
46
|
-
pytest-httpserver = "^1.1.
|
|
47
|
-
ruff = ">=0.
|
|
45
|
+
commitizen = ">=4.8.3"
|
|
46
|
+
pytest-httpserver = "^1.1.3"
|
|
47
|
+
ruff = ">=0.12.7"
|
|
48
48
|
|
|
49
49
|
[tool.commitizen]
|
|
50
50
|
name = "cz_conventional_commits"
|
|
51
|
-
version = "3.
|
|
51
|
+
version = "3.7.0"
|
|
52
52
|
tag_format = "v$major.$minor.$patch$prerelease"
|
|
53
53
|
version_files = [
|
|
54
54
|
"pyproject.toml:version",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|