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.
@@ -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}'")