dataconf 3.5.0__tar.gz → 3.6.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dataconf
3
- Version: 3.5.0
3
+ Version: 3.6.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.6.1,<0.8.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.1,<4.0.0)
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
@@ -50,14 +50,16 @@ def __parse_type(value: Any, clazz: Type, path: str, check: bool):
50
50
  except TypeError:
51
51
  pass
52
52
 
53
- raise TypeConfigException(f"expected type {clazz} at {path}, got {type(value)}")
53
+ raise TypeConfigException(
54
+ f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
55
+ )
54
56
 
55
57
 
56
58
  def is_union(origin):
57
59
  return origin is Union or (PY310up and origin is UnionType)
58
60
 
59
61
 
60
- def is_optional(type: Type):
62
+ def is_optional(type: Type | Any | str):
61
63
  # Optional = Union[T, NoneType]
62
64
  return is_union(get_origin(type)) and NoneType in get_args(type)
63
65
 
@@ -66,7 +68,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
66
68
  if is_dataclass(clazz):
67
69
  if not isinstance(value, ConfigTree):
68
70
  raise TypeConfigException(
69
- f"expected type {clazz} at {path}, got {type(value)}"
71
+ f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
70
72
  )
71
73
 
72
74
  fs = {}
@@ -96,15 +98,34 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
96
98
  # Optional not found
97
99
  fs[f.name] = None
98
100
 
101
+ elif is_dataclass(f.type):
102
+ if all(
103
+ not isinstance(field.default, _MISSING_TYPE)
104
+ or not isinstance(field.default_factory, _MISSING_TYPE)
105
+ or is_optional(field.type)
106
+ for field in fields(f.type)
107
+ ):
108
+ fs[f.name] = f.type(
109
+ **{
110
+ field.name: None
111
+ for field in fields(f.type)
112
+ if is_optional(field.type)
113
+ }
114
+ )
115
+ else:
116
+ raise MalformedConfigException(
117
+ f'expected type {clazz} at {path_to_str(path)}, no field "{f.name}" found and {f.type} cannot be implicitly created'
118
+ )
119
+
99
120
  else:
100
121
  raise MalformedConfigException(
101
- f"expected type {clazz} at {path}, no {f.name} found in dataclass"
122
+ f'expected type {clazz} at {path_to_str(path)}, no field "{f.name}" found'
102
123
  )
103
124
 
104
125
  unexpected_keys = value.keys() - {renamings.get(k, k) for k in fs.keys()}
105
126
  if len(unexpected_keys) > 0 and not ignore_unexpected:
106
127
  raise UnexpectedKeysException(
107
- f'unexpected key(s) "{", ".join(unexpected_keys)}" detected for type {clazz} at {path}'
128
+ f'unexpected key(s) "{", ".join(unexpected_keys)}" detected for type {clazz} at {path_to_str(path)}'
108
129
  )
109
130
 
110
131
  return clazz(**fs)
@@ -114,7 +135,9 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
114
135
 
115
136
  if origin is list:
116
137
  if value is None:
117
- raise MalformedConfigException(f"expected list at {path} but received None")
138
+ raise MalformedConfigException(
139
+ f"expected list at {path_to_str(path)} but received None"
140
+ )
118
141
 
119
142
  if len(args) != 1:
120
143
  raise MissingTypeException("expected list with type information: List[?]")
@@ -128,7 +151,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
128
151
  if origin is tuple:
129
152
  if value is None:
130
153
  raise MalformedConfigException(
131
- f"expected tuple at {path} but received None"
154
+ f"expected tuple at {path_to_str(path)} but received None"
132
155
  )
133
156
 
134
157
  if len(args) < 1:
@@ -181,7 +204,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
181
204
  return None
182
205
 
183
206
  raise TypeConfigException(
184
- f"expected one of {', '.join(map(str, args))} at {path}, got {type(value)}"
207
+ f"expected one of {', '.join(map(str, args))} at {path_to_str(path)}, got {type(value)}"
185
208
  )
186
209
 
187
210
  if clazz is bool:
@@ -226,7 +249,9 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
226
249
  return clazz(value)
227
250
  elif isinstance(value, str):
228
251
  return clazz.__getitem__(value)
229
- raise TypeConfigException(f"expected str or int at {path}, got {type(value)}")
252
+ raise TypeConfigException(
253
+ f"expected str or int at {path_to_str(path)}, got {type(value)}"
254
+ )
230
255
 
231
256
  if isclass(clazz) and issubclass(clazz, Path):
232
257
  return clazz.__call__(value)
@@ -235,7 +260,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
235
260
  if value in args:
236
261
  return value
237
262
  raise TypeConfigException(
238
- f"expected one of {', '.join(map(str, args))} at {path}, got {value}"
263
+ f"expected one of {', '.join(map(str, args))} at {path_to_str(path)}, got {value}"
239
264
  )
240
265
 
241
266
  if clazz is datetime:
@@ -244,7 +269,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
244
269
  return isoparse(dt)
245
270
  except ValueError as e:
246
271
  raise ParseException(
247
- f"expected type {clazz} at {path}, cannot parse due to {e}"
272
+ f"expected type {clazz} at {path_to_str(path)}, cannot parse due to {e}"
248
273
  )
249
274
 
250
275
  if clazz is timedelta:
@@ -258,7 +283,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
258
283
  return duration
259
284
  except ValueError as e:
260
285
  raise ParseException(
261
- f"expected type {clazz} at {path}, cannot parse due to {e}"
286
+ f"expected type {clazz} at {path_to_str(path)}, cannot parse due to {e}"
262
287
  )
263
288
 
264
289
  if clazz is relativedelta:
@@ -292,17 +317,19 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
292
317
  elif len(child_successes) > 1:
293
318
  matching_classes = "\n- ".join(map(lambda x: x[0].__name__, child_successes))
294
319
  raise AmbiguousSubclassException(
295
- f"""multiple subtypes of {clazz} matched at {path}, use '_type' to disambiguate:\n- {matching_classes}"""
320
+ f"""multiple subtypes of {clazz} matched at {path_to_str(path)}, use '_type' to disambiguate:\n- {matching_classes}"""
296
321
  )
297
322
 
298
323
  # no need to check length; false if empty
299
324
  if child_failures:
300
325
  failures = "\n- ".join([str(c) for c in child_failures])
301
326
  raise TypeConfigException(
302
- f"expected type {clazz} at {path}, failed subclasses:\n- {failures}"
327
+ f"expected type {clazz} at {path_to_str(path)}, failed subclasses:\n- {failures}"
303
328
  )
304
329
 
305
- raise TypeConfigException(f"expected type {clazz} at {path}, got {type(value)}")
330
+ raise TypeConfigException(
331
+ f"expected type {clazz} at {path_to_str(path)}, got {type(value)}"
332
+ )
306
333
 
307
334
 
308
335
  def __generate(value: object, path: str):
@@ -399,6 +426,10 @@ def __env_vars_parse(prefix: str, obj: Dict[str, Any]):
399
426
  return ret
400
427
 
401
428
 
429
+ def path_to_str(path: str) -> str:
430
+ return path if len(path) > 0 else "root"
431
+
432
+
402
433
  def __cli_parse(argv: List[str]):
403
434
  kvs = {}
404
435
  i = 0
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
 
3
- __version__ = "3.5.0"
3
+ __version__ = "3.6.0"
4
4
 
5
5
  PY310up = sys.version_info >= (3, 10)
6
6
  PY311up = sys.version_info >= (3, 11)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dataconf"
3
- version = "3.5.0"
3
+ version = "3.6.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 = ">=0.6.1,<0.8.0"
38
+ isodate = "^0.7.2"
39
39
  pyhocon = "^0.3.61"
40
- pyparsing = "^3.2.1"
40
+ pyparsing = "^3.2.3"
41
41
 
42
42
  [tool.poetry.group.dev.dependencies]
43
- pytest = ">=8.3.5"
43
+ pytest = ">=8.4.1"
44
44
  pre-commit = ">=4.2.0"
45
- commitizen = ">=4.4.1"
46
- pytest-httpserver = "^1.1.2"
47
- ruff = ">=0.11.2"
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.5.0"
51
+ version = "3.6.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