confarg 0.0.1.dev2__py3-none-any.whl
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.
- confarg/__init__.py +440 -0
- confarg/_argparse.py +958 -0
- confarg/_callable.py +593 -0
- confarg/_completion.py +318 -0
- confarg/_defaults.py +15 -0
- confarg/_errors.py +85 -0
- confarg/_files.py +426 -0
- confarg/_merge.py +284 -0
- confarg/_parse_cli.py +507 -0
- confarg/_parse_env.py +279 -0
- confarg/_serialize.py +206 -0
- confarg/_types.py +614 -0
- confarg/dictexpr/__init__.py +34 -0
- confarg/dictexpr/_expressions.py +566 -0
- confarg/typedload/__init__.py +44 -0
- confarg/typedload/_coerce.py +178 -0
- confarg/typedload/_construct.py +685 -0
- confarg-0.0.1.dev2.dist-info/METADATA +9 -0
- confarg-0.0.1.dev2.dist-info/RECORD +20 -0
- confarg-0.0.1.dev2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
|
|
5
|
+
"""Leaf value coercion for confarg."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from confarg._errors import TypeCoercionError
|
|
13
|
+
from confarg._types import (
|
|
14
|
+
_is_enum,
|
|
15
|
+
_is_literal,
|
|
16
|
+
_is_none_type,
|
|
17
|
+
_literal_values,
|
|
18
|
+
_resolve_type,
|
|
19
|
+
_StrToken,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
_TRUTHY = frozenset({"true", "1", "yes", "on"})
|
|
23
|
+
_FALSY = frozenset({"false", "0", "no", "off"})
|
|
24
|
+
_LEAF_COERCIONS: dict[type, Any] = {Path: Path}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _src_type(value: Any) -> str:
|
|
28
|
+
"""Return the user-visible type name of a value, collapsing _StrToken to 'str'."""
|
|
29
|
+
return "str" if isinstance(value, _StrToken) else type(value).__name__
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _coerce_bool(s: str) -> bool:
|
|
33
|
+
"""Coerce a string to a boolean value.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
s: The string to coerce (e.g. "true", "1", "yes", "on").
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The corresponding boolean value.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
TypeCoercionError: If the string is not a recognized boolean representation.
|
|
43
|
+
"""
|
|
44
|
+
low = s.lower()
|
|
45
|
+
if low in _TRUTHY:
|
|
46
|
+
return True
|
|
47
|
+
if low in _FALSY:
|
|
48
|
+
return False
|
|
49
|
+
valid = sorted(_TRUTHY | _FALSY)
|
|
50
|
+
raise TypeCoercionError(f"Cannot coerce {s!r} to bool. Valid values: {valid}")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _coerce_type_ref(tp: Any, value: Any, path: str = "") -> type:
|
|
54
|
+
"""Coerce a dotted-path string or class object to a class, validated against type[X]."""
|
|
55
|
+
from confarg._callable import _import_dotted
|
|
56
|
+
from confarg._types import _type_ref_constraint
|
|
57
|
+
|
|
58
|
+
if isinstance(value, type):
|
|
59
|
+
constraint = _type_ref_constraint(tp)
|
|
60
|
+
if constraint is not object and not issubclass(value, constraint):
|
|
61
|
+
raise TypeCoercionError(
|
|
62
|
+
f"Class {value.__module__}.{value.__qualname__!r} at '{path}'"
|
|
63
|
+
f" is not a subclass of {constraint.__module__}.{constraint.__name__}."
|
|
64
|
+
)
|
|
65
|
+
return value
|
|
66
|
+
if not isinstance(value, _StrToken):
|
|
67
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "type", path)
|
|
68
|
+
try:
|
|
69
|
+
obj = _import_dotted(str(value))
|
|
70
|
+
except TypeCoercionError as e:
|
|
71
|
+
raise TypeCoercionError(
|
|
72
|
+
f"Cannot import class {str(value)!r} at '{path}': {e}."
|
|
73
|
+
f" Use a fully-qualified dotted path, e.g. 'mypackage.MyClass'."
|
|
74
|
+
) from e
|
|
75
|
+
if not isinstance(obj, type):
|
|
76
|
+
raise TypeCoercionError(
|
|
77
|
+
f"Cannot coerce {str(value)!r} at '{path}': expected a class, got {type(obj).__name__!r}."
|
|
78
|
+
)
|
|
79
|
+
constraint = _type_ref_constraint(tp)
|
|
80
|
+
if constraint is not object and not issubclass(obj, constraint):
|
|
81
|
+
raise TypeCoercionError(
|
|
82
|
+
f"Class {str(value)!r} at '{path}' is not a subclass of {constraint.__module__}.{constraint.__name__}."
|
|
83
|
+
)
|
|
84
|
+
return obj
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _coerce_leaf(tp: Any, value: Any, path: str = "") -> Any:
|
|
88
|
+
"""Coerce a raw value to the target leaf type.
|
|
89
|
+
|
|
90
|
+
Handles bool, int, float, str, Literal, Enum, Path, and NoneType.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
tp: The target type to coerce to.
|
|
94
|
+
value: The raw value to coerce.
|
|
95
|
+
path: Dot-separated field path for error messages.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
The coerced value matching the target type.
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
TypeCoercionError: If the value cannot be coerced to the target type.
|
|
102
|
+
"""
|
|
103
|
+
tp = _resolve_type(tp)
|
|
104
|
+
|
|
105
|
+
if _is_none_type(tp):
|
|
106
|
+
return None
|
|
107
|
+
|
|
108
|
+
if tp is bool:
|
|
109
|
+
if isinstance(value, bool):
|
|
110
|
+
return value
|
|
111
|
+
if isinstance(value, _StrToken):
|
|
112
|
+
return _coerce_bool(str(value))
|
|
113
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "bool", path)
|
|
114
|
+
|
|
115
|
+
if tp is int:
|
|
116
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
117
|
+
return value
|
|
118
|
+
if isinstance(value, _StrToken):
|
|
119
|
+
try:
|
|
120
|
+
return int(str(value))
|
|
121
|
+
except (ValueError, TypeError):
|
|
122
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "int", path) from None
|
|
123
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "int", path)
|
|
124
|
+
|
|
125
|
+
if tp is float:
|
|
126
|
+
if isinstance(value, float) and not isinstance(value, bool):
|
|
127
|
+
return value
|
|
128
|
+
if isinstance(value, _StrToken):
|
|
129
|
+
try:
|
|
130
|
+
return float(str(value))
|
|
131
|
+
except (ValueError, TypeError):
|
|
132
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "float", path) from None
|
|
133
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "float", path)
|
|
134
|
+
|
|
135
|
+
if tp is str:
|
|
136
|
+
if isinstance(value, str): # _StrToken is a str subclass
|
|
137
|
+
return str(value)
|
|
138
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, "str", path)
|
|
139
|
+
|
|
140
|
+
if _is_literal(tp):
|
|
141
|
+
vals = _literal_values(tp)
|
|
142
|
+
if isinstance(value, _StrToken):
|
|
143
|
+
s = str(value)
|
|
144
|
+
for v in vals:
|
|
145
|
+
if str(v) == s:
|
|
146
|
+
return v
|
|
147
|
+
else:
|
|
148
|
+
for v in vals:
|
|
149
|
+
if type(v) is type(value) and v == value:
|
|
150
|
+
return v
|
|
151
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, f"Literal{vals}", path)
|
|
152
|
+
|
|
153
|
+
if _is_enum(tp):
|
|
154
|
+
if isinstance(value, tp):
|
|
155
|
+
return value
|
|
156
|
+
s = str(value)
|
|
157
|
+
for member in tp:
|
|
158
|
+
if str(member.value) == s:
|
|
159
|
+
return member
|
|
160
|
+
try:
|
|
161
|
+
return tp[s]
|
|
162
|
+
except KeyError:
|
|
163
|
+
members = []
|
|
164
|
+
for m in tp:
|
|
165
|
+
sv = str(m.value)
|
|
166
|
+
members.append(f"'{m.name}' ('{sv}')" if sv != m.name else f"'{m.name}'")
|
|
167
|
+
raise TypeCoercionError(
|
|
168
|
+
f"Cannot coerce {_src_type(value)} {value!r} to {tp.__name__} at '{path}'."
|
|
169
|
+
f" Valid members: {', '.join(members)}"
|
|
170
|
+
) from None
|
|
171
|
+
|
|
172
|
+
if tp in _LEAF_COERCIONS:
|
|
173
|
+
try:
|
|
174
|
+
return _LEAF_COERCIONS[tp](value)
|
|
175
|
+
except Exception:
|
|
176
|
+
raise TypeCoercionError.cannot_coerce(_src_type(value), value, tp.__name__, path) from None
|
|
177
|
+
|
|
178
|
+
raise TypeCoercionError(f"Unsupported leaf type {tp} at '{path}'")
|