dataconf 3.6.0__tar.gz → 3.7.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.
@@ -1,24 +1,26 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: dataconf
3
- Version: 3.6.0
3
+ Version: 3.7.1
4
4
  Summary: Simple dataclasses configuration management for Python with hocon/json/yaml/properties/env-vars/dict support.
5
- Home-page: https://github.com/zifeo/dataconf
6
5
  License: MPL-2.0
6
+ License-File: LICENSE
7
7
  Keywords: configuration,dataclasses,yaml,hocon,json,properties,cli,environment variables
8
- Requires-Python: >=3.9,<4.0
8
+ Requires-Python: >=3.10,<4.0
9
9
  Classifier: License :: OSI Approved
10
10
  Classifier: Programming Language :: Python
11
11
  Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.9
13
12
  Classifier: Programming Language :: Python :: 3.10
14
13
  Classifier: Programming Language :: Python :: 3.11
15
14
  Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Programming Language :: Python :: 3.15
16
18
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
19
  Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
18
20
  Requires-Dist: isodate (>=0.7.2,<0.8.0)
19
21
  Requires-Dist: pyhocon (>=0.3.61,<0.4.0)
20
22
  Requires-Dist: pyparsing (>=3.2.3,<4.0.0)
21
- Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
23
+ Requires-Dist: python-dateutil (>=3.0.0,<4.0.0)
22
24
  Project-URL: Repository, https://github.com/zifeo/dataconf
23
25
  Description-Content-Type: text/markdown
24
26
 
@@ -32,7 +34,7 @@ hocon/json/yaml/properties/env-vars/dict/cli support.
32
34
 
33
35
  ## Getting started
34
36
 
35
- Requires at least Python 3.9.
37
+ Requires at least Python 3.10.
36
38
 
37
39
  ```bash
38
40
  # pypi
@@ -8,7 +8,7 @@ hocon/json/yaml/properties/env-vars/dict/cli support.
8
8
 
9
9
  ## Getting started
10
10
 
11
- Requires at least Python 3.9.
11
+ Requires at least Python 3.10.
12
12
 
13
13
  ```bash
14
14
  # pypi
@@ -1,46 +1,30 @@
1
1
  class TypeConfigException(Exception):
2
2
  """Type mismatch exception."""
3
3
 
4
- pass
5
-
6
4
 
7
5
  class MissingTypeException(Exception):
8
6
  """Missing type exception (e.g. List instead of List[int]."""
9
7
 
10
- pass
11
-
12
8
 
13
9
  class AmbiguousSubclassException(Exception):
14
10
  """Multiple subclasses match the the given parameters. Use '_type' field to disambiguate"""
15
11
 
16
- pass
17
-
18
12
 
19
13
  class MalformedConfigException(Exception):
20
14
  """Missing values exception."""
21
15
 
22
- pass
23
-
24
16
 
25
17
  class UnexpectedKeysException(Exception):
26
18
  """Unexpected keys exception."""
27
19
 
28
- pass
29
-
30
20
 
31
21
  class EnvListOrderException(Exception):
32
22
  """Ordering exception."""
33
23
 
34
- pass
35
-
36
24
 
37
25
  class EnvListFormatException(Exception):
38
26
  """Format exception."""
39
27
 
40
- pass
41
-
42
28
 
43
29
  class ParseException(Exception):
44
30
  """Parsing exception."""
45
-
46
- pass
@@ -1,20 +1,18 @@
1
1
  import contextlib
2
2
  import os
3
- from typing import Any, Optional
4
- from typing import Dict
5
- from typing import List
6
- from typing import Type
3
+ from typing import Any
7
4
  from urllib.parse import urlparse
8
5
  from urllib.request import urlopen
9
6
 
10
- from dataconf import utils
11
- from dataconf.exceptions import MalformedConfigException
7
+ import pyparsing
12
8
  from pyhocon import ConfigFactory
13
9
  from pyhocon import HOCONConverter
14
10
  from pyhocon.config_parser import ConfigTree
15
- import pyparsing
16
11
  from yaml import safe_load
17
12
 
13
+ from dataconf import utils
14
+ from dataconf.exceptions import MalformedConfigException
15
+
18
16
  HOCON = 1
19
17
  YAML = 2
20
18
 
@@ -39,19 +37,20 @@ def cli_parse(*args, **kwargs):
39
37
 
40
38
 
41
39
  class Multi:
42
- def __init__(self, confs: List[ConfigTree], strict: bool = True, **kwargs) -> None:
40
+ def __init__(self, confs: list[ConfigTree], strict: bool = True, **kwargs) -> None:
43
41
  self.confs = confs
44
42
  self.strict = strict
45
43
  self.kwargs = kwargs
46
44
 
47
45
  def env(self, prefix: str, **kwargs) -> "Multi":
48
- self.strict = False
49
46
  data = env_vars_parse(prefix, os.environ)
50
- return self.dict(data, **kwargs)
47
+ strict = kwargs.pop("strict", False)
48
+ return self.dict(data, strict=strict, **kwargs)
51
49
 
52
- def dict(self, obj: Dict[str, Any], **kwargs) -> "Multi":
50
+ def dict(self, obj: dict[str, Any], **kwargs) -> "Multi":
53
51
  conf = ConfigFactory.from_dict(obj)
54
- return Multi(self.confs + [conf], self.strict, **kwargs)
52
+ strict = kwargs.pop("strict", self.strict)
53
+ return Multi(self.confs + [conf], strict, **kwargs)
55
54
 
56
55
  def string(self, s: str, loader: str = HOCON, **kwargs) -> "Multi":
57
56
  if loader == YAML:
@@ -63,7 +62,7 @@ class Multi:
63
62
 
64
63
  def url(self, uri: str, timeout: int = 10, **kwargs) -> "Multi":
65
64
  path = urlparse(uri).path
66
- if path.endswith(".yaml") or path.endswith(".yml"):
65
+ if path.endswith((".yaml", ".yml")):
67
66
  with contextlib.closing(urlopen(uri, timeout=timeout)) as fd:
68
67
  s = fd.read().decode("utf-8")
69
68
  return self.string(s, loader=YAML, **kwargs)
@@ -74,16 +73,14 @@ class Multi:
74
73
  def file(
75
74
  self,
76
75
  path: str,
77
- loader: Optional[str] = None,
76
+ loader: str | None = None,
78
77
  allow_missing: bool = False,
79
78
  **kwargs,
80
79
  ) -> "Multi":
81
80
  if allow_missing and not os.path.exists(path):
82
81
  return self.dict({}, **kwargs)
83
82
 
84
- if loader == YAML or (
85
- loader is None and (path.endswith(".yaml") or path.endswith(".yml"))
86
- ):
83
+ if loader == YAML or (loader is None and path.endswith((".yaml", ".yml"))):
87
84
  with open(path, "r") as f:
88
85
  data = safe_load(f)
89
86
  return self.dict(data, **kwargs)
@@ -91,11 +88,11 @@ class Multi:
91
88
  conf = ConfigFactory.parse_file(path)
92
89
  return Multi(self.confs + [conf], self.strict, **kwargs)
93
90
 
94
- def cli(self, argv: List[str], **kwargs) -> "Multi":
91
+ def cli(self, argv: list[str], **kwargs) -> "Multi":
95
92
  data = cli_parse(argv)
96
93
  return self.dict(data, **kwargs)
97
94
 
98
- def on(self, clazz: Type):
95
+ def on(self, clazz: type):
99
96
  conf, *nxts = self.confs
100
97
  for nxt in nxts:
101
98
  conf = ConfigTree.merge_configs(conf, nxt)
@@ -105,35 +102,35 @@ class Multi:
105
102
  multi = Multi([])
106
103
 
107
104
 
108
- def env(prefix: str, clazz: Type, **kwargs):
105
+ def env(prefix: str, clazz: type, **kwargs):
109
106
  return multi.env(prefix, **kwargs).on(clazz)
110
107
 
111
108
 
112
- def dict(obj: Dict[str, Any], clazz: Type, **kwargs):
109
+ def dict(obj: dict[str, Any], clazz: type, **kwargs):
113
110
  return multi.dict(obj, **kwargs).on(clazz)
114
111
 
115
112
 
116
- def string(s: str, clazz: Type, **kwargs):
113
+ def string(s: str, clazz: type, **kwargs):
117
114
  return multi.string(s, **kwargs).on(clazz)
118
115
 
119
116
 
120
- def url(uri: str, clazz: Type, **kwargs):
117
+ def url(uri: str, clazz: type, **kwargs):
121
118
  return multi.url(uri, **kwargs).on(clazz)
122
119
 
123
120
 
124
- def file(path: str, clazz: Type, **kwargs):
121
+ def file(path: str, clazz: type, **kwargs):
125
122
  return multi.file(path, **kwargs).on(clazz)
126
123
 
127
124
 
128
- def cli(argv: List[str], clazz: Type, **kwargs):
125
+ def cli(argv: list[str], clazz: type, **kwargs):
129
126
  return multi.cli(argv, **kwargs).on(clazz)
130
127
 
131
128
 
132
- def load(path: str, clazz: Type, **kwargs):
129
+ def load(path: str, clazz: type, **kwargs):
133
130
  return file(path, clazz, **kwargs)
134
131
 
135
132
 
136
- def loads(s: str, clazz: Type, **kwargs):
133
+ def loads(s: str, clazz: type, **kwargs):
137
134
  return string(s, clazz, **kwargs)
138
135
 
139
136
 
@@ -1,4 +1,4 @@
1
- from dataclasses import _MISSING_TYPE
1
+ from dataclasses import MISSING
2
2
  from dataclasses import asdict
3
3
  from dataclasses import fields
4
4
  from dataclasses import is_dataclass
@@ -8,42 +8,50 @@ from enum import Enum
8
8
  from enum import IntEnum
9
9
  from inspect import isclass
10
10
  from pathlib import Path
11
-
12
- from typing import Any, Literal
13
- from typing import Dict
11
+ from types import UnionType
12
+ from typing import Any
13
+ from typing import Literal
14
+ from typing import Union
14
15
  from typing import get_args
15
16
  from typing import get_origin
16
- from typing import List
17
- from typing import Type
18
- from typing import Union
19
17
 
20
- from dataconf.exceptions import AmbiguousSubclassException, EnvListFormatException
21
- from dataconf.exceptions import EnvListOrderException
22
- from dataconf.exceptions import MalformedConfigException
23
- from dataconf.exceptions import MissingTypeException
24
- from dataconf.exceptions import ParseException
25
- from dataconf.exceptions import TypeConfigException
26
- from dataconf.exceptions import UnexpectedKeysException
18
+ import pyparsing
27
19
  from dateutil.parser import isoparse
28
20
  from dateutil.relativedelta import relativedelta
21
+ from isodate import Duration
22
+ from isodate import parse_duration
29
23
  from pyhocon import ConfigFactory
30
24
  from pyhocon.config_tree import ConfigList
31
25
  from pyhocon.config_tree import ConfigTree
32
- from isodate import parse_duration
33
- from isodate import Duration
34
- import pyparsing
35
26
 
36
- from dataconf.version import PY310up
27
+ from dataconf.exceptions import AmbiguousSubclassException
28
+ from dataconf.exceptions import EnvListFormatException
29
+ from dataconf.exceptions import EnvListOrderException
30
+ from dataconf.exceptions import MalformedConfigException
31
+ from dataconf.exceptions import MissingTypeException
32
+ from dataconf.exceptions import ParseException
33
+ from dataconf.exceptions import TypeConfigException
34
+ from dataconf.exceptions import UnexpectedKeysException
37
35
 
36
+ NoneType = type(None)
38
37
 
39
- if PY310up:
40
- from types import UnionType
38
+ _BOOL_TRUE = {"true", "1", "yes", "on", "t", "y"}
39
+ _BOOL_FALSE = {"false", "0", "no", "off", "f", "n"}
41
40
 
42
41
 
43
- NoneType = type(None)
42
+ def __parse_bool(value: Any):
43
+ if isinstance(value, bool):
44
+ return value
45
+ if isinstance(value, str):
46
+ normalised = value.strip().lower()
47
+ if normalised in _BOOL_TRUE:
48
+ return True
49
+ if normalised in _BOOL_FALSE:
50
+ return False
51
+ return None
44
52
 
45
53
 
46
- def __parse_type(value: Any, clazz: Type, path: str, check: bool):
54
+ def __parse_type(value: Any, clazz: type, path: str, check: bool):
47
55
  try:
48
56
  if check:
49
57
  return value
@@ -56,15 +64,15 @@ def __parse_type(value: Any, clazz: Type, path: str, check: bool):
56
64
 
57
65
 
58
66
  def is_union(origin):
59
- return origin is Union or (PY310up and origin is UnionType)
67
+ return origin is Union or origin is UnionType
60
68
 
61
69
 
62
- def is_optional(type: Type | Any | str):
70
+ def is_optional(type: type | Any | str):
63
71
  # Optional = Union[T, NoneType]
64
72
  return is_union(get_origin(type)) and NoneType in get_args(type)
65
73
 
66
74
 
67
- def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected: bool):
75
+ def __parse(value: any, clazz: type, path: str, strict: bool, ignore_unexpected: bool):
68
76
  if is_dataclass(clazz):
69
77
  if not isinstance(value, ConfigTree):
70
78
  raise TypeConfigException(
@@ -89,7 +97,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
89
97
  # if val is a dataclass, convert to ConfigTree
90
98
  val = ConfigFactory.from_dict(asdict(val))
91
99
 
92
- if not isinstance(val, _MISSING_TYPE):
100
+ if val is not MISSING:
93
101
  fs[f.name] = __parse(
94
102
  val, f.type, f"{path}.{f.name}", strict, ignore_unexpected
95
103
  )
@@ -100,8 +108,8 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
100
108
 
101
109
  elif is_dataclass(f.type):
102
110
  if all(
103
- not isinstance(field.default, _MISSING_TYPE)
104
- or not isinstance(field.default_factory, _MISSING_TYPE)
111
+ field.default is not MISSING
112
+ or field.default_factory is not MISSING
105
113
  or is_optional(field.type)
106
114
  for field in fields(f.type)
107
115
  ):
@@ -122,7 +130,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
122
130
  f'expected type {clazz} at {path_to_str(path)}, no field "{f.name}" found'
123
131
  )
124
132
 
125
- unexpected_keys = value.keys() - {renamings.get(k, k) for k in fs.keys()}
133
+ unexpected_keys = value.keys() - {renamings.get(k, k) for k in fs}
126
134
  if len(unexpected_keys) > 0 and not ignore_unexpected:
127
135
  raise UnexpectedKeysException(
128
136
  f'unexpected key(s) "{", ".join(unexpected_keys)}" detected for type {clazz} at {path_to_str(path)}'
@@ -133,30 +141,30 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
133
141
  origin = get_origin(clazz)
134
142
  args = get_args(clazz)
135
143
 
136
- if origin is list:
144
+ if clazz is list or origin is list:
145
+ if len(args) != 1:
146
+ raise MissingTypeException("expected list with type information: List[?]")
147
+
137
148
  if value is None:
138
149
  raise MalformedConfigException(
139
150
  f"expected list at {path_to_str(path)} but received None"
140
151
  )
141
152
 
142
- if len(args) != 1:
143
- raise MissingTypeException("expected list with type information: List[?]")
144
-
145
153
  parse_candidate = args[0]
146
154
  return [
147
155
  __parse(v, parse_candidate, f"{path}[]", strict, ignore_unexpected)
148
156
  for v in value
149
157
  ]
150
158
 
151
- if origin is tuple:
159
+ if clazz is tuple or origin is tuple:
160
+ if len(args) < 1:
161
+ raise MissingTypeException("expected tuple with type information: Tuple[?]")
162
+
152
163
  if value is None:
153
164
  raise MalformedConfigException(
154
165
  f"expected tuple at {path_to_str(path)} but received None"
155
166
  )
156
167
 
157
- if len(args) < 1:
158
- raise MissingTypeException("expected tuple with type information: Tuple[?]")
159
-
160
168
  has_ellipsis = args[-1] == Ellipsis
161
169
  if has_ellipsis and len(args) != 2:
162
170
  raise MissingTypeException(
@@ -172,7 +180,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
172
180
  for v, arg in zip(value, _args)
173
181
  )
174
182
 
175
- if origin is dict:
183
+ if clazz is dict or origin is dict:
176
184
  if len(args) != 2:
177
185
  raise MissingTypeException(
178
186
  "expected dict with type information: Dict[?, ?]"
@@ -208,14 +216,14 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
208
216
  )
209
217
 
210
218
  if clazz is bool:
211
- if not strict and value is not None:
212
- try:
213
- value = bool(value)
214
- except ValueError:
215
- pass
216
- return __parse_type(value, clazz, path, isinstance(value, bool))
219
+ parsed = __parse_bool(value)
220
+ if parsed is not None:
221
+ return parsed
222
+ return __parse_type(value, clazz, path, False)
217
223
 
218
224
  if clazz is int:
225
+ if isinstance(value, bool):
226
+ return __parse_type(value, clazz, path, False)
219
227
  if not strict and value is not None:
220
228
  try:
221
229
  value = int(value)
@@ -224,14 +232,14 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
224
232
  return __parse_type(value, clazz, path, isinstance(value, int))
225
233
 
226
234
  if clazz is float:
235
+ if isinstance(value, bool):
236
+ return __parse_type(value, clazz, path, False)
227
237
  if not strict and value is not None:
228
238
  try:
229
239
  value = float(value)
230
240
  except ValueError:
231
241
  pass
232
- return __parse_type(
233
- value, clazz, path, isinstance(value, float) or isinstance(value, int)
234
- )
242
+ return __parse_type(value, clazz, path, isinstance(value, (float, int)))
235
243
 
236
244
  if clazz is str:
237
245
  return __parse_type(value, clazz, path, isinstance(value, str))
@@ -243,7 +251,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
243
251
  return value
244
252
 
245
253
  if isclass(clazz) and (issubclass(clazz, Enum) or issubclass(clazz, IntEnum)):
246
- if isinstance(value, int):
254
+ if isinstance(value, int) and not isinstance(value, bool):
247
255
  return clazz.__call__(value)
248
256
  elif issubclass(clazz, str):
249
257
  return clazz(value)
@@ -315,7 +323,7 @@ def __parse(value: any, clazz: Type, path: str, strict: bool, ignore_unexpected:
315
323
  if len(child_successes) == 1:
316
324
  return child_successes[0][1]
317
325
  elif len(child_successes) > 1:
318
- matching_classes = "\n- ".join(map(lambda x: x[0].__name__, child_successes))
326
+ matching_classes = "\n- ".join(x[0].__name__ for x in child_successes)
319
327
  raise AmbiguousSubclassException(
320
328
  f"""multiple subtypes of {clazz} matched at {path_to_str(path)}, use '_type' to disambiguate:\n- {matching_classes}"""
321
329
  )
@@ -352,7 +360,7 @@ def __generate(value: object, path: str):
352
360
  return value
353
361
 
354
362
 
355
- def __env_vars_parse(prefix: str, obj: Dict[str, Any]):
363
+ def __env_vars_parse(prefix: str, obj: dict[str, Any]):
356
364
  ret = {}
357
365
 
358
366
  def set_lens(p, focus, v):
@@ -430,11 +438,11 @@ def path_to_str(path: str) -> str:
430
438
  return path if len(path) > 0 else "root"
431
439
 
432
440
 
433
- def __cli_parse(argv: List[str]):
441
+ def __cli_parse(argv: list[str]):
434
442
  kvs = {}
435
443
  i = 0
436
444
 
437
- if not argv[0].startswith("--"):
445
+ if argv and not argv[0].startswith("--"):
438
446
  i += 1
439
447
 
440
448
  while i < len(argv):
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ __version__ = "3.7.1"
4
+
5
+ PY311up = sys.version_info >= (3, 11)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dataconf"
3
- version = "3.6.0"
3
+ version = "3.7.1"
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"
@@ -12,10 +12,12 @@ include = [
12
12
  classifiers = [
13
13
  "Topic :: Software Development :: Libraries :: Python Modules",
14
14
  "Programming Language :: Python",
15
- "Programming Language :: Python :: 3.9",
16
15
  "Programming Language :: Python :: 3.10",
17
16
  "Programming Language :: Python :: 3.11",
18
- "Programming Language :: Python :: 3.12"
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Programming Language :: Python :: 3.14",
20
+ "Programming Language :: Python :: 3.15",
19
21
  ]
20
22
  keywords = [
21
23
  "configuration",
@@ -32,8 +34,8 @@ keywords = [
32
34
  dataconf = 'dataconf.cli:run'
33
35
 
34
36
  [tool.poetry.dependencies]
35
- python = "^3.9"
36
- python-dateutil = "^2.9.0.post0"
37
+ python = "^3.10"
38
+ python-dateutil = "^3.0.0"
37
39
  PyYAML = "^6.0.2"
38
40
  isodate = "^0.7.2"
39
41
  pyhocon = "^0.3.61"
@@ -48,7 +50,7 @@ ruff = ">=0.12.7"
48
50
 
49
51
  [tool.commitizen]
50
52
  name = "cz_conventional_commits"
51
- version = "3.6.0"
53
+ version = "3.7.1"
52
54
  tag_format = "v$major.$minor.$patch$prerelease"
53
55
  version_files = [
54
56
  "pyproject.toml:version",
@@ -1,6 +0,0 @@
1
- import sys
2
-
3
- __version__ = "3.6.0"
4
-
5
- PY310up = sys.version_info >= (3, 10)
6
- PY311up = sys.version_info >= (3, 11)
File without changes
@@ -1,33 +1,33 @@
1
+ from dataconf.main import HOCON
2
+ from dataconf.main import YAML
1
3
  from dataconf.main import cli
2
4
  from dataconf.main import dict
3
5
  from dataconf.main import dump
4
6
  from dataconf.main import dumps
5
7
  from dataconf.main import env
6
8
  from dataconf.main import file
7
- from dataconf.main import HOCON
8
9
  from dataconf.main import load
9
10
  from dataconf.main import loads
10
11
  from dataconf.main import multi
11
12
  from dataconf.main import parse
12
13
  from dataconf.main import string
13
14
  from dataconf.main import url
14
- from dataconf.main import YAML
15
15
  from dataconf.version import __version__
16
16
 
17
17
  __all__ = [
18
- "load",
19
- "loads",
18
+ "HOCON",
19
+ "YAML",
20
+ "__version__",
21
+ "cli",
22
+ "dict",
20
23
  "dump",
21
24
  "dumps",
22
25
  "env",
23
- "url",
24
- "dict",
25
26
  "file",
26
- "string",
27
- "cli",
27
+ "load",
28
+ "loads",
28
29
  "multi",
29
30
  "parse",
30
- "YAML",
31
- "HOCON",
32
- "__version__",
31
+ "string",
32
+ "url",
33
33
  ]
File without changes
File without changes