click-extended 1.0.0__py3-none-any.whl → 1.0.2__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.
- click_extended/__init__.py +2 -0
- click_extended/core/__init__.py +10 -0
- click_extended/core/decorators/__init__.py +21 -0
- click_extended/core/decorators/argument.py +227 -0
- click_extended/core/decorators/command.py +93 -0
- click_extended/core/decorators/context.py +56 -0
- click_extended/core/decorators/env.py +155 -0
- click_extended/core/decorators/group.py +96 -0
- click_extended/core/decorators/option.py +347 -0
- click_extended/core/decorators/prompt.py +69 -0
- click_extended/core/decorators/selection.py +155 -0
- click_extended/core/decorators/tag.py +109 -0
- click_extended/core/nodes/__init__.py +21 -0
- click_extended/core/nodes/_root_node.py +1012 -0
- click_extended/core/nodes/argument_node.py +165 -0
- click_extended/core/nodes/child_node.py +555 -0
- click_extended/core/nodes/child_validation_node.py +100 -0
- click_extended/core/nodes/node.py +55 -0
- click_extended/core/nodes/option_node.py +205 -0
- click_extended/core/nodes/parent_node.py +220 -0
- click_extended/core/nodes/validation_node.py +124 -0
- click_extended/core/other/__init__.py +7 -0
- click_extended/core/other/_click_command.py +60 -0
- click_extended/core/other/_click_group.py +246 -0
- click_extended/core/other/_tree.py +491 -0
- click_extended/core/other/context.py +496 -0
- click_extended/decorators/__init__.py +29 -0
- click_extended/decorators/check/__init__.py +57 -0
- click_extended/decorators/check/conflicts.py +149 -0
- click_extended/decorators/check/contains.py +69 -0
- click_extended/decorators/check/dependencies.py +115 -0
- click_extended/decorators/check/divisible_by.py +48 -0
- click_extended/decorators/check/ends_with.py +85 -0
- click_extended/decorators/check/exclusive.py +75 -0
- click_extended/decorators/check/falsy.py +37 -0
- click_extended/decorators/check/is_email.py +43 -0
- click_extended/decorators/check/is_hex_color.py +41 -0
- click_extended/decorators/check/is_hostname.py +47 -0
- click_extended/decorators/check/is_ipv4.py +46 -0
- click_extended/decorators/check/is_ipv6.py +46 -0
- click_extended/decorators/check/is_json.py +40 -0
- click_extended/decorators/check/is_mac_address.py +40 -0
- click_extended/decorators/check/is_negative.py +37 -0
- click_extended/decorators/check/is_non_zero.py +37 -0
- click_extended/decorators/check/is_port.py +39 -0
- click_extended/decorators/check/is_positive.py +37 -0
- click_extended/decorators/check/is_url.py +75 -0
- click_extended/decorators/check/is_uuid.py +40 -0
- click_extended/decorators/check/length.py +68 -0
- click_extended/decorators/check/not_empty.py +49 -0
- click_extended/decorators/check/regex.py +47 -0
- click_extended/decorators/check/requires.py +190 -0
- click_extended/decorators/check/starts_with.py +87 -0
- click_extended/decorators/check/truthy.py +37 -0
- click_extended/decorators/compare/__init__.py +15 -0
- click_extended/decorators/compare/at_least.py +57 -0
- click_extended/decorators/compare/at_most.py +57 -0
- click_extended/decorators/compare/between.py +119 -0
- click_extended/decorators/compare/greater_than.py +183 -0
- click_extended/decorators/compare/less_than.py +183 -0
- click_extended/decorators/convert/__init__.py +31 -0
- click_extended/decorators/convert/convert_angle.py +94 -0
- click_extended/decorators/convert/convert_area.py +123 -0
- click_extended/decorators/convert/convert_bits.py +211 -0
- click_extended/decorators/convert/convert_distance.py +154 -0
- click_extended/decorators/convert/convert_energy.py +155 -0
- click_extended/decorators/convert/convert_power.py +128 -0
- click_extended/decorators/convert/convert_pressure.py +131 -0
- click_extended/decorators/convert/convert_speed.py +122 -0
- click_extended/decorators/convert/convert_temperature.py +89 -0
- click_extended/decorators/convert/convert_time.py +108 -0
- click_extended/decorators/convert/convert_volume.py +218 -0
- click_extended/decorators/convert/convert_weight.py +158 -0
- click_extended/decorators/load/__init__.py +13 -0
- click_extended/decorators/load/load_csv.py +117 -0
- click_extended/decorators/load/load_json.py +61 -0
- click_extended/decorators/load/load_toml.py +47 -0
- click_extended/decorators/load/load_yaml.py +72 -0
- click_extended/decorators/math/__init__.py +37 -0
- click_extended/decorators/math/absolute.py +35 -0
- click_extended/decorators/math/add.py +48 -0
- click_extended/decorators/math/ceil.py +36 -0
- click_extended/decorators/math/clamp.py +51 -0
- click_extended/decorators/math/divide.py +42 -0
- click_extended/decorators/math/floor.py +36 -0
- click_extended/decorators/math/maximum.py +39 -0
- click_extended/decorators/math/minimum.py +39 -0
- click_extended/decorators/math/modulo.py +39 -0
- click_extended/decorators/math/multiply.py +51 -0
- click_extended/decorators/math/normalize.py +76 -0
- click_extended/decorators/math/power.py +39 -0
- click_extended/decorators/math/rounded.py +39 -0
- click_extended/decorators/math/sqrt.py +39 -0
- click_extended/decorators/math/subtract.py +39 -0
- click_extended/decorators/math/to_percent.py +63 -0
- click_extended/decorators/misc/__init__.py +17 -0
- click_extended/decorators/misc/choice.py +139 -0
- click_extended/decorators/misc/confirm_if.py +147 -0
- click_extended/decorators/misc/default.py +95 -0
- click_extended/decorators/misc/deprecated.py +131 -0
- click_extended/decorators/misc/experimental.py +79 -0
- click_extended/decorators/misc/now.py +42 -0
- click_extended/decorators/random/__init__.py +21 -0
- click_extended/decorators/random/random_bool.py +49 -0
- click_extended/decorators/random/random_choice.py +63 -0
- click_extended/decorators/random/random_datetime.py +140 -0
- click_extended/decorators/random/random_float.py +62 -0
- click_extended/decorators/random/random_integer.py +56 -0
- click_extended/decorators/random/random_prime.py +196 -0
- click_extended/decorators/random/random_string.py +77 -0
- click_extended/decorators/random/random_uuid.py +119 -0
- click_extended/decorators/transform/__init__.py +71 -0
- click_extended/decorators/transform/add_prefix.py +58 -0
- click_extended/decorators/transform/add_suffix.py +58 -0
- click_extended/decorators/transform/apply.py +35 -0
- click_extended/decorators/transform/basename.py +44 -0
- click_extended/decorators/transform/dirname.py +44 -0
- click_extended/decorators/transform/expand_vars.py +36 -0
- click_extended/decorators/transform/remove_prefix.py +57 -0
- click_extended/decorators/transform/remove_suffix.py +57 -0
- click_extended/decorators/transform/replace.py +46 -0
- click_extended/decorators/transform/slugify.py +45 -0
- click_extended/decorators/transform/split.py +43 -0
- click_extended/decorators/transform/strip.py +148 -0
- click_extended/decorators/transform/to_case.py +216 -0
- click_extended/decorators/transform/to_date.py +75 -0
- click_extended/decorators/transform/to_datetime.py +83 -0
- click_extended/decorators/transform/to_path.py +274 -0
- click_extended/decorators/transform/to_time.py +77 -0
- click_extended/decorators/transform/to_timestamp.py +114 -0
- click_extended/decorators/transform/truncate.py +47 -0
- click_extended/utils/__init__.py +13 -0
- click_extended/utils/casing.py +169 -0
- click_extended/utils/checks.py +48 -0
- click_extended/utils/dispatch.py +1016 -0
- click_extended/utils/format.py +101 -0
- click_extended/utils/humanize.py +209 -0
- click_extended/utils/naming.py +238 -0
- click_extended/utils/process.py +294 -0
- click_extended/utils/selection.py +267 -0
- click_extended/utils/time.py +46 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/METADATA +2 -1
- click_extended-1.0.2.dist-info/RECORD +150 -0
- click_extended-1.0.0.dist-info/RECORD +0 -10
- {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/WHEEL +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/licenses/AUTHORS.md +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/licenses/LICENSE +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1016 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dispatcher utilities for child node handler routing.
|
|
3
|
+
|
|
4
|
+
This module provides the core dispatching logic that routes values to
|
|
5
|
+
appropriate child node handlers based on type, context, and handler
|
|
6
|
+
availability. It supports both synchronous and asynchronous handlers with
|
|
7
|
+
automatic detection and routing.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# pylint: disable=too-many-return-statements
|
|
11
|
+
# pylint: disable=too-many-branches
|
|
12
|
+
# pylint: disable=too-many-statements
|
|
13
|
+
# pylint: disable=too-many-locals
|
|
14
|
+
# pylint: disable=too-many-nested-blocks
|
|
15
|
+
# pylint: disable=import-outside-toplevel
|
|
16
|
+
# pylint: disable=too-many-lines
|
|
17
|
+
|
|
18
|
+
import asyncio
|
|
19
|
+
from datetime import date, datetime, time
|
|
20
|
+
from decimal import Decimal
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from types import UnionType
|
|
23
|
+
from typing import (
|
|
24
|
+
TYPE_CHECKING,
|
|
25
|
+
Any,
|
|
26
|
+
Union,
|
|
27
|
+
cast,
|
|
28
|
+
get_args,
|
|
29
|
+
get_origin,
|
|
30
|
+
get_type_hints,
|
|
31
|
+
)
|
|
32
|
+
from uuid import UUID
|
|
33
|
+
|
|
34
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
35
|
+
from click_extended.errors import (
|
|
36
|
+
InvalidHandlerError,
|
|
37
|
+
ProcessError,
|
|
38
|
+
UnhandledTypeError,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from click_extended.core.other.context import Context
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
TYPE_SPECIFIC_HANDLERS = [
|
|
46
|
+
"handle_str",
|
|
47
|
+
"handle_int",
|
|
48
|
+
"handle_float",
|
|
49
|
+
"handle_bool",
|
|
50
|
+
"handle_numeric",
|
|
51
|
+
"handle_list",
|
|
52
|
+
"handle_dict",
|
|
53
|
+
"handle_tuple",
|
|
54
|
+
"handle_path",
|
|
55
|
+
"handle_uuid",
|
|
56
|
+
"handle_datetime",
|
|
57
|
+
"handle_date",
|
|
58
|
+
"handle_time",
|
|
59
|
+
"handle_bytes",
|
|
60
|
+
"handle_decimal",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
ALL_HANDLER_NAMES = [
|
|
64
|
+
"handle_all",
|
|
65
|
+
"handle_none",
|
|
66
|
+
*TYPE_SPECIFIC_HANDLERS,
|
|
67
|
+
"handle_tag",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _extract_inner_types(type_hint: Any) -> set[type]:
|
|
72
|
+
"""
|
|
73
|
+
Extract the expected types from a type hint.
|
|
74
|
+
|
|
75
|
+
Examples:
|
|
76
|
+
- `int` -> `{int}`
|
|
77
|
+
- `int | str` -> `{int, str}`
|
|
78
|
+
- `tuple[int, ...]` -> `{int}`
|
|
79
|
+
- `tuple[int | str, ...]` -> `{int, str}`
|
|
80
|
+
- `tuple[tuple[int, ...], ...]` -> `{int}` (from innermost)
|
|
81
|
+
- `list[str]` -> `{str}`
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
type_hint (Any):
|
|
85
|
+
The type hint to extract from.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
set[type]:
|
|
89
|
+
Set of expected types.
|
|
90
|
+
"""
|
|
91
|
+
if type_hint is Any:
|
|
92
|
+
return set()
|
|
93
|
+
|
|
94
|
+
origin = get_origin(type_hint)
|
|
95
|
+
args = get_args(type_hint)
|
|
96
|
+
|
|
97
|
+
if origin is UnionType or (
|
|
98
|
+
origin is not None and str(origin).startswith("typing.Union")
|
|
99
|
+
):
|
|
100
|
+
result: set[type] = set()
|
|
101
|
+
for arg in args:
|
|
102
|
+
if arg is type(None) or arg is Any:
|
|
103
|
+
continue
|
|
104
|
+
result.update(_extract_inner_types(arg))
|
|
105
|
+
return result
|
|
106
|
+
|
|
107
|
+
# tuple[T, ...]
|
|
108
|
+
if origin is tuple and args:
|
|
109
|
+
if args[-1] is Ellipsis:
|
|
110
|
+
return _extract_inner_types(args[0])
|
|
111
|
+
|
|
112
|
+
result = set()
|
|
113
|
+
for arg in args:
|
|
114
|
+
if arg is not Any:
|
|
115
|
+
result.update(_extract_inner_types(arg))
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
# list[T], set[T], etc.
|
|
119
|
+
if origin in (list, set, frozenset) and args:
|
|
120
|
+
if args[0] is Any:
|
|
121
|
+
return set()
|
|
122
|
+
return _extract_inner_types(args[0])
|
|
123
|
+
|
|
124
|
+
# dict[K, V]
|
|
125
|
+
if origin is dict and len(args) >= 2:
|
|
126
|
+
if args[1] is Any:
|
|
127
|
+
return set()
|
|
128
|
+
return _extract_inner_types(args[1])
|
|
129
|
+
|
|
130
|
+
if isinstance(type_hint, type):
|
|
131
|
+
return {type_hint}
|
|
132
|
+
|
|
133
|
+
return set()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _validate_handler_type(
|
|
137
|
+
handler_name: str, value: Any, type_hint: Any
|
|
138
|
+
) -> tuple[bool, str]:
|
|
139
|
+
"""
|
|
140
|
+
Validate that value matches handler's type hint.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
handler_name (str):
|
|
144
|
+
Name of the handler being called.
|
|
145
|
+
value (Any):
|
|
146
|
+
The runtime value to validate.
|
|
147
|
+
type_hint (Any):
|
|
148
|
+
The type hint from the handler's signature.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
tuple[bool, str]:
|
|
152
|
+
`(is_valid, error_message)` where `error_message` is empty if valid.
|
|
153
|
+
"""
|
|
154
|
+
origin = get_origin(type_hint)
|
|
155
|
+
|
|
156
|
+
if type_hint is Any:
|
|
157
|
+
return True, ""
|
|
158
|
+
|
|
159
|
+
if isinstance(type_hint, type) and origin is None:
|
|
160
|
+
if not isinstance(value, type_hint):
|
|
161
|
+
expected_name = type_hint.__name__
|
|
162
|
+
actual_type = type(value).__name__
|
|
163
|
+
suggestion = ""
|
|
164
|
+
|
|
165
|
+
if actual_type == "str" and type_hint in (int, float):
|
|
166
|
+
suggestion = (
|
|
167
|
+
"\nTip: Add type=int to your option/argument "
|
|
168
|
+
"to convert strings to integers."
|
|
169
|
+
)
|
|
170
|
+
elif actual_type == "int" and type_hint == str:
|
|
171
|
+
suggestion = (
|
|
172
|
+
"\nTip: Change type=int to type=str in "
|
|
173
|
+
"your option/argument."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
False,
|
|
178
|
+
f"Expected {expected_name}, got {actual_type}.{suggestion}",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if handler_name in (
|
|
182
|
+
"handle_str",
|
|
183
|
+
"handle_int",
|
|
184
|
+
"handle_float",
|
|
185
|
+
"handle_bool",
|
|
186
|
+
"handle_numeric",
|
|
187
|
+
):
|
|
188
|
+
expected_type_map: dict[str, type | tuple[type, ...]] = {
|
|
189
|
+
"handle_str": str,
|
|
190
|
+
"handle_int": int,
|
|
191
|
+
"handle_float": float,
|
|
192
|
+
"handle_bool": bool,
|
|
193
|
+
"handle_numeric": (int, float),
|
|
194
|
+
}
|
|
195
|
+
expected = expected_type_map[handler_name]
|
|
196
|
+
|
|
197
|
+
if not isinstance(value, expected):
|
|
198
|
+
if handler_name == "handle_numeric":
|
|
199
|
+
return (
|
|
200
|
+
False,
|
|
201
|
+
f"Expected int or float, got {type(value).__name__}",
|
|
202
|
+
)
|
|
203
|
+
expected_name = (
|
|
204
|
+
expected.__name__ if isinstance(expected, type) else "number"
|
|
205
|
+
)
|
|
206
|
+
actual_type = type(value).__name__
|
|
207
|
+
suggestion = ""
|
|
208
|
+
|
|
209
|
+
if actual_type == "str" and expected in (int, (int, float)):
|
|
210
|
+
suggestion = (
|
|
211
|
+
"\nTip: Add type=int to your option/argument "
|
|
212
|
+
"to convert strings to integers."
|
|
213
|
+
)
|
|
214
|
+
elif actual_type == "int" and expected == str:
|
|
215
|
+
suggestion = (
|
|
216
|
+
"\nTip: Change type=int to type=str in "
|
|
217
|
+
"your option/argument."
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
False,
|
|
222
|
+
f"Expected {expected_name}, got {actual_type}.{suggestion}",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
elif handler_name == "handle_tuple":
|
|
226
|
+
if not isinstance(value, tuple):
|
|
227
|
+
return False, f"Expected tuple, got {type(value).__name__}"
|
|
228
|
+
|
|
229
|
+
elif handler_name == "handle_list":
|
|
230
|
+
if not isinstance(value, list):
|
|
231
|
+
return False, f"Expected list, got {type(value).__name__}"
|
|
232
|
+
|
|
233
|
+
expected_types = _extract_inner_types(type_hint)
|
|
234
|
+
if expected_types and value:
|
|
235
|
+
list_mismatches: list[tuple[int, str, Any]] = []
|
|
236
|
+
value = cast(Any, value)
|
|
237
|
+
for i, item in enumerate(value):
|
|
238
|
+
if not any(isinstance(item, t) for t in expected_types):
|
|
239
|
+
list_mismatches.append((i, type(item).__name__, item))
|
|
240
|
+
|
|
241
|
+
if list_mismatches:
|
|
242
|
+
type_names = " | ".join(
|
|
243
|
+
sorted(t.__name__ for t in expected_types)
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
examples: list[str] = []
|
|
247
|
+
for (
|
|
248
|
+
_,
|
|
249
|
+
item_type,
|
|
250
|
+
item_val,
|
|
251
|
+
) in list_mismatches[:3]:
|
|
252
|
+
examples.append(f"{repr(item_val)} ({item_type})")
|
|
253
|
+
|
|
254
|
+
error_msg = "".join(
|
|
255
|
+
f"Expected list[{type_names}], but found "
|
|
256
|
+
f"{len(list_mismatches)} item(s) with wrong type."
|
|
257
|
+
f"\nExamples: {', '.join(examples)}"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return False, error_msg
|
|
261
|
+
|
|
262
|
+
elif handler_name == "handle_dict":
|
|
263
|
+
if not isinstance(value, dict):
|
|
264
|
+
return False, f"Expected dict, got {type(value).__name__}"
|
|
265
|
+
|
|
266
|
+
return True, ""
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def dispatch_to_child(
|
|
270
|
+
child: "ChildNode",
|
|
271
|
+
value: Any,
|
|
272
|
+
context: "Context",
|
|
273
|
+
) -> Any:
|
|
274
|
+
"""
|
|
275
|
+
Dispatch value to appropriate child handler with priority system.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
child (ChildNode):
|
|
279
|
+
The child node to dispatch to.
|
|
280
|
+
value (Any):
|
|
281
|
+
The value to process.
|
|
282
|
+
context (Context):
|
|
283
|
+
Processing context with parent, siblings, tags.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Any:
|
|
287
|
+
The processed value. Returns original value if the
|
|
288
|
+
handler returns `None`.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
UnhandledTypeError:
|
|
292
|
+
If no handler is implemented for this value type.
|
|
293
|
+
InvalidHandlerError:
|
|
294
|
+
If `handle_tag` returns a modified dictionary.
|
|
295
|
+
"""
|
|
296
|
+
if isinstance(value, tuple):
|
|
297
|
+
meta = context.click_context.meta.get("click_extended", {})
|
|
298
|
+
is_container = meta.get("is_container_tuple", False)
|
|
299
|
+
|
|
300
|
+
if is_container:
|
|
301
|
+
return _process_container_tuple(
|
|
302
|
+
child,
|
|
303
|
+
value, # type: ignore
|
|
304
|
+
context,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if value is None:
|
|
308
|
+
# Handle None
|
|
309
|
+
if _is_handler_implemented(child, "handle_none"):
|
|
310
|
+
try:
|
|
311
|
+
result = child.handle_none(
|
|
312
|
+
context, *child.process_args, **child.process_kwargs
|
|
313
|
+
)
|
|
314
|
+
return value if result is None else result # type: ignore
|
|
315
|
+
except NotImplementedError:
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
for handler_name in TYPE_SPECIFIC_HANDLERS:
|
|
319
|
+
if _is_handler_implemented(child, handler_name):
|
|
320
|
+
if _should_call_handler(child, handler_name, value):
|
|
321
|
+
try:
|
|
322
|
+
handler = getattr(child, handler_name)
|
|
323
|
+
result = handler(
|
|
324
|
+
value,
|
|
325
|
+
context,
|
|
326
|
+
*child.process_args,
|
|
327
|
+
**child.process_kwargs,
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
if result is None:
|
|
331
|
+
return value
|
|
332
|
+
return result
|
|
333
|
+
except NotImplementedError:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
# Handle all
|
|
337
|
+
try:
|
|
338
|
+
if _should_call_handler(child, "handle_all", value):
|
|
339
|
+
result = child.handle_all(
|
|
340
|
+
value, context, *child.process_args, **child.process_kwargs
|
|
341
|
+
)
|
|
342
|
+
return value if result is None else result
|
|
343
|
+
except NotImplementedError:
|
|
344
|
+
pass
|
|
345
|
+
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
handler_name = _determine_handler(
|
|
349
|
+
child,
|
|
350
|
+
value,
|
|
351
|
+
context,
|
|
352
|
+
) # type: ignore[assignment]
|
|
353
|
+
|
|
354
|
+
# Handle specific
|
|
355
|
+
if handler_name:
|
|
356
|
+
try:
|
|
357
|
+
if _should_call_handler(child, handler_name, value):
|
|
358
|
+
if "click_extended" in context.click_context.meta:
|
|
359
|
+
context.click_context.meta["click_extended"][
|
|
360
|
+
"handler_method"
|
|
361
|
+
] = handler_name # type: ignore[assignment]
|
|
362
|
+
|
|
363
|
+
handler = getattr(child, handler_name)
|
|
364
|
+
hints = get_type_hints(handler)
|
|
365
|
+
|
|
366
|
+
if "value" in hints:
|
|
367
|
+
is_valid, error_msg = _validate_handler_type(
|
|
368
|
+
handler_name, value, hints["value"]
|
|
369
|
+
)
|
|
370
|
+
if not is_valid:
|
|
371
|
+
raise ProcessError(
|
|
372
|
+
f"Type mismatch in {handler_name}: " f"{error_msg}"
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
result = handler(
|
|
376
|
+
value, context, *child.process_args, **child.process_kwargs
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if handler_name == "handle_tag" and result is not None:
|
|
380
|
+
message = (
|
|
381
|
+
"Method handle_tag() is validation-only and "
|
|
382
|
+
"does not support transformations."
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
tip = (
|
|
386
|
+
"Remove the return statement to make it "
|
|
387
|
+
"validation-only or move the "
|
|
388
|
+
"transformation logic to the parent node."
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
raise InvalidHandlerError(message=message, tip=tip)
|
|
392
|
+
|
|
393
|
+
return value if result is None else result # type: ignore
|
|
394
|
+
except NotImplementedError:
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
try:
|
|
398
|
+
if _should_call_handler(child, "handle_all", value):
|
|
399
|
+
if "click_extended" in context.click_context.meta:
|
|
400
|
+
context.click_context.meta["click_extended"][
|
|
401
|
+
"handler_method"
|
|
402
|
+
] = "handle_all"
|
|
403
|
+
|
|
404
|
+
result = child.handle_all(
|
|
405
|
+
value, context, *child.process_args, **child.process_kwargs
|
|
406
|
+
)
|
|
407
|
+
return value if result is None else result # type: ignore
|
|
408
|
+
except NotImplementedError:
|
|
409
|
+
pass
|
|
410
|
+
|
|
411
|
+
raise UnhandledTypeError(
|
|
412
|
+
child_name=child.name,
|
|
413
|
+
value_type=type(value).__name__, # type: ignore
|
|
414
|
+
implemented_handlers=_get_implemented_handlers(child),
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _determine_handler(
|
|
419
|
+
child: "ChildNode", value: Any, context: "Context"
|
|
420
|
+
) -> str | None:
|
|
421
|
+
"""
|
|
422
|
+
Determine which handler should process this value based on priority.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
child (ChildNode):
|
|
426
|
+
The child node to check for implemented handlers.
|
|
427
|
+
value (Any):
|
|
428
|
+
The value to check.
|
|
429
|
+
context (Context):
|
|
430
|
+
The processing context.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
str | None:
|
|
434
|
+
Handler method name, or `None` if no handler found.
|
|
435
|
+
"""
|
|
436
|
+
if context.is_tag() and _is_handler_implemented(child, "handle_tag"):
|
|
437
|
+
return "handle_tag"
|
|
438
|
+
|
|
439
|
+
if isinstance(value, bytes):
|
|
440
|
+
if _is_handler_implemented(child, "handle_bytes"):
|
|
441
|
+
return "handle_bytes"
|
|
442
|
+
elif isinstance(value, Decimal):
|
|
443
|
+
if _is_handler_implemented(child, "handle_decimal"):
|
|
444
|
+
return "handle_decimal"
|
|
445
|
+
elif isinstance(value, datetime):
|
|
446
|
+
if _is_handler_implemented(child, "handle_datetime"):
|
|
447
|
+
return "handle_datetime"
|
|
448
|
+
elif isinstance(value, date):
|
|
449
|
+
if _is_handler_implemented(child, "handle_date"):
|
|
450
|
+
return "handle_date"
|
|
451
|
+
elif isinstance(value, time):
|
|
452
|
+
if _is_handler_implemented(child, "handle_time"):
|
|
453
|
+
return "handle_time"
|
|
454
|
+
elif isinstance(value, UUID):
|
|
455
|
+
if _is_handler_implemented(child, "handle_uuid"):
|
|
456
|
+
return "handle_uuid"
|
|
457
|
+
elif isinstance(value, Path):
|
|
458
|
+
if _is_handler_implemented(child, "handle_path"):
|
|
459
|
+
return "handle_path"
|
|
460
|
+
elif isinstance(value, dict):
|
|
461
|
+
if _is_handler_implemented(child, "handle_dict"):
|
|
462
|
+
return "handle_dict"
|
|
463
|
+
elif isinstance(value, str):
|
|
464
|
+
if _is_handler_implemented(child, "handle_str"):
|
|
465
|
+
return "handle_str"
|
|
466
|
+
elif isinstance(
|
|
467
|
+
value, bool
|
|
468
|
+
): # Must check bool before int since bool is subclass of int
|
|
469
|
+
if _is_handler_implemented(child, "handle_bool"):
|
|
470
|
+
return "handle_bool"
|
|
471
|
+
elif isinstance(value, int):
|
|
472
|
+
if _is_handler_implemented(child, "handle_int"):
|
|
473
|
+
return "handle_int"
|
|
474
|
+
if _is_handler_implemented(child, "handle_numeric"):
|
|
475
|
+
return "handle_numeric"
|
|
476
|
+
elif isinstance(value, float):
|
|
477
|
+
if _is_handler_implemented(child, "handle_float"):
|
|
478
|
+
return "handle_float"
|
|
479
|
+
if _is_handler_implemented(child, "handle_numeric"):
|
|
480
|
+
return "handle_numeric"
|
|
481
|
+
elif isinstance(value, list):
|
|
482
|
+
if _is_handler_implemented(child, "handle_list"):
|
|
483
|
+
return "handle_list"
|
|
484
|
+
elif isinstance(value, tuple):
|
|
485
|
+
if _is_handler_implemented(child, "handle_tuple"):
|
|
486
|
+
return "handle_tuple"
|
|
487
|
+
return None
|
|
488
|
+
|
|
489
|
+
if _is_handler_implemented(child, "handle_all"):
|
|
490
|
+
return "handle_all"
|
|
491
|
+
|
|
492
|
+
return None
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _should_call_handler(
|
|
496
|
+
child: "ChildNode", handler_name: str, value: Any
|
|
497
|
+
) -> bool:
|
|
498
|
+
"""
|
|
499
|
+
Check if handler should be called for this value.
|
|
500
|
+
|
|
501
|
+
Checks type hints to see if `None` values are accepted.
|
|
502
|
+
|
|
503
|
+
Args:
|
|
504
|
+
child (ChildNode):
|
|
505
|
+
The child node.
|
|
506
|
+
handler_name (str):
|
|
507
|
+
Name of the handler method.
|
|
508
|
+
value (Any):
|
|
509
|
+
The value to check.
|
|
510
|
+
|
|
511
|
+
Returns:
|
|
512
|
+
bool:
|
|
513
|
+
`True` if handler should be called, `False` if
|
|
514
|
+
value should be skipped.
|
|
515
|
+
"""
|
|
516
|
+
if value is not None:
|
|
517
|
+
return True
|
|
518
|
+
|
|
519
|
+
try:
|
|
520
|
+
method = getattr(child, handler_name, None)
|
|
521
|
+
if method is None:
|
|
522
|
+
return False
|
|
523
|
+
|
|
524
|
+
hints = get_type_hints(method)
|
|
525
|
+
if "value" not in hints:
|
|
526
|
+
return True
|
|
527
|
+
|
|
528
|
+
value_hint = hints["value"]
|
|
529
|
+
|
|
530
|
+
if value_hint is Any:
|
|
531
|
+
return True
|
|
532
|
+
|
|
533
|
+
origin = get_origin(value_hint)
|
|
534
|
+
|
|
535
|
+
if origin is UnionType:
|
|
536
|
+
args = get_args(value_hint)
|
|
537
|
+
return type(None) in args
|
|
538
|
+
|
|
539
|
+
if origin is Union:
|
|
540
|
+
args = get_args(value_hint)
|
|
541
|
+
return type(None) in args
|
|
542
|
+
|
|
543
|
+
return False
|
|
544
|
+
except (AttributeError, ImportError):
|
|
545
|
+
return True
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _is_handler_implemented(child: "ChildNode", handler_name: str) -> bool:
|
|
549
|
+
"""
|
|
550
|
+
Check if a handler is implemented by the child (not just inherited).
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
child (ChildNode):
|
|
554
|
+
The child node instance.
|
|
555
|
+
handler_name (str):
|
|
556
|
+
Name of the handler method to check.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
bool:
|
|
560
|
+
`True` if handler is implemented by child class, `False` otherwise.
|
|
561
|
+
"""
|
|
562
|
+
for cls in type(child).__mro__:
|
|
563
|
+
if handler_name in cls.__dict__:
|
|
564
|
+
return cls is not ChildNode
|
|
565
|
+
|
|
566
|
+
return False
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _get_implemented_handlers(child: "ChildNode") -> list[str]:
|
|
570
|
+
"""
|
|
571
|
+
Get list of implemented handler names by checking class hierarchy.
|
|
572
|
+
|
|
573
|
+
Args:
|
|
574
|
+
child (ChildNode):
|
|
575
|
+
The child node instance.
|
|
576
|
+
|
|
577
|
+
Returns:
|
|
578
|
+
list[str]:
|
|
579
|
+
List of handler names (without `'handle_'` prefix)
|
|
580
|
+
that are implemented.
|
|
581
|
+
"""
|
|
582
|
+
handlers: list[str] = []
|
|
583
|
+
|
|
584
|
+
for handler_name in ALL_HANDLER_NAMES:
|
|
585
|
+
for cls in type(child).__mro__:
|
|
586
|
+
if handler_name in cls.__dict__:
|
|
587
|
+
if cls is not ChildNode:
|
|
588
|
+
handlers.append(handler_name.replace("handle_", ""))
|
|
589
|
+
break
|
|
590
|
+
|
|
591
|
+
return handlers
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _process_container_tuple(
|
|
595
|
+
child: "ChildNode",
|
|
596
|
+
value: tuple[Any, ...],
|
|
597
|
+
context: "Context",
|
|
598
|
+
path: list[int] | None = None,
|
|
599
|
+
) -> tuple[Any, ...]:
|
|
600
|
+
"""
|
|
601
|
+
Process a container tuple by applying handlers to each element in-place.
|
|
602
|
+
|
|
603
|
+
This function recursively processes tuples from options/arguments with
|
|
604
|
+
`multiple=True` or `nargs>1`, applying appropriate handlers to each
|
|
605
|
+
leaf element based on its type and preserving the tuple structure.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
child (ChildNode):
|
|
609
|
+
The child node to dispatch handlers from.
|
|
610
|
+
value (tuple[Any, ...]):
|
|
611
|
+
The container tuple to process.
|
|
612
|
+
context (Context):
|
|
613
|
+
Processing context.
|
|
614
|
+
path (list[int] | None):
|
|
615
|
+
Current path for error reporting. Defaults to empty list.
|
|
616
|
+
|
|
617
|
+
Returns:
|
|
618
|
+
tuple[Any, ...]:
|
|
619
|
+
New tuple with same structure but processed elements.
|
|
620
|
+
|
|
621
|
+
Raises:
|
|
622
|
+
ValueError:
|
|
623
|
+
If validation fails, with path information added.
|
|
624
|
+
TypeError:
|
|
625
|
+
If type mismatch occurs, with path information added.
|
|
626
|
+
UnhandledTypeError:
|
|
627
|
+
If no handler exists for an element's type.
|
|
628
|
+
"""
|
|
629
|
+
if path is None:
|
|
630
|
+
path = []
|
|
631
|
+
|
|
632
|
+
results: list[Any] = []
|
|
633
|
+
|
|
634
|
+
for i, item in enumerate(value):
|
|
635
|
+
current_path = path + [i]
|
|
636
|
+
|
|
637
|
+
try:
|
|
638
|
+
if isinstance(item, tuple):
|
|
639
|
+
result = _process_container_tuple(
|
|
640
|
+
child,
|
|
641
|
+
item, # type: ignore
|
|
642
|
+
context,
|
|
643
|
+
current_path,
|
|
644
|
+
)
|
|
645
|
+
else:
|
|
646
|
+
if handler_name := _determine_handler(child, item, context):
|
|
647
|
+
if _should_call_handler(child, handler_name, item):
|
|
648
|
+
handler = getattr(child, handler_name)
|
|
649
|
+
result = handler(
|
|
650
|
+
item,
|
|
651
|
+
context,
|
|
652
|
+
*child.process_args,
|
|
653
|
+
**child.process_kwargs,
|
|
654
|
+
)
|
|
655
|
+
else:
|
|
656
|
+
result = item
|
|
657
|
+
elif _is_handler_implemented(child, "handle_all"):
|
|
658
|
+
if _should_call_handler(child, "handle_all", item):
|
|
659
|
+
result = child.handle_all(
|
|
660
|
+
item,
|
|
661
|
+
context,
|
|
662
|
+
*child.process_args,
|
|
663
|
+
**child.process_kwargs,
|
|
664
|
+
)
|
|
665
|
+
else:
|
|
666
|
+
result = item
|
|
667
|
+
else:
|
|
668
|
+
raise UnhandledTypeError(
|
|
669
|
+
child_name=child.name,
|
|
670
|
+
value_type=type(item).__name__, # type: ignore
|
|
671
|
+
implemented_handlers=_get_implemented_handlers(child),
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
results.append(result)
|
|
675
|
+
|
|
676
|
+
except (ValueError, TypeError) as e:
|
|
677
|
+
path_str = "".join(f"[{idx}]" for idx in current_path)
|
|
678
|
+
error_msg = str(e)
|
|
679
|
+
if path_str and " at index " not in error_msg:
|
|
680
|
+
raise type(e)(f"{error_msg} at index {path_str}") from e
|
|
681
|
+
raise
|
|
682
|
+
|
|
683
|
+
return tuple(results)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
async def _process_container_tuple_async(
|
|
687
|
+
child: "ChildNode",
|
|
688
|
+
value: tuple[Any, ...],
|
|
689
|
+
context: "Context",
|
|
690
|
+
path: list[int] | None = None,
|
|
691
|
+
) -> tuple[Any, ...]:
|
|
692
|
+
"""
|
|
693
|
+
Async version of _process_container_tuple for async handler support.
|
|
694
|
+
|
|
695
|
+
Process a container tuple by applying handlers to each element in-place.
|
|
696
|
+
|
|
697
|
+
This function recursively processes tuples from options/arguments with
|
|
698
|
+
`multiple=True` or `nargs>1`, applying appropriate handlers to each
|
|
699
|
+
leaf element based on its type and preserving the tuple structure.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
child (ChildNode):
|
|
703
|
+
The child node to dispatch handlers from.
|
|
704
|
+
value (tuple[Any, ...]):
|
|
705
|
+
The container tuple to process.
|
|
706
|
+
context (Context):
|
|
707
|
+
Processing context.
|
|
708
|
+
path (list[int] | None):
|
|
709
|
+
Current path for error reporting. Defaults to empty list.
|
|
710
|
+
|
|
711
|
+
Returns:
|
|
712
|
+
tuple[Any, ...]:
|
|
713
|
+
New tuple with same structure but processed elements.
|
|
714
|
+
|
|
715
|
+
Raises:
|
|
716
|
+
ValueError:
|
|
717
|
+
If validation fails, with path information added.
|
|
718
|
+
TypeError:
|
|
719
|
+
If type mismatch occurs, with path information added.
|
|
720
|
+
UnhandledTypeError:
|
|
721
|
+
If no handler exists for an element's type.
|
|
722
|
+
"""
|
|
723
|
+
if path is None:
|
|
724
|
+
path = []
|
|
725
|
+
|
|
726
|
+
results: list[Any] = []
|
|
727
|
+
|
|
728
|
+
for i, item in enumerate(value):
|
|
729
|
+
current_path = path + [i]
|
|
730
|
+
|
|
731
|
+
try:
|
|
732
|
+
if isinstance(item, tuple):
|
|
733
|
+
result = await _process_container_tuple_async(
|
|
734
|
+
child,
|
|
735
|
+
item, # type: ignore
|
|
736
|
+
context,
|
|
737
|
+
current_path,
|
|
738
|
+
)
|
|
739
|
+
else:
|
|
740
|
+
if handler_name := _determine_handler(child, item, context):
|
|
741
|
+
if _should_call_handler(child, handler_name, item):
|
|
742
|
+
handler = getattr(child, handler_name)
|
|
743
|
+
if asyncio.iscoroutinefunction(handler):
|
|
744
|
+
result = await handler(
|
|
745
|
+
item,
|
|
746
|
+
context,
|
|
747
|
+
*child.process_args,
|
|
748
|
+
**child.process_kwargs,
|
|
749
|
+
)
|
|
750
|
+
else:
|
|
751
|
+
result = handler(
|
|
752
|
+
item,
|
|
753
|
+
context,
|
|
754
|
+
*child.process_args,
|
|
755
|
+
**child.process_kwargs,
|
|
756
|
+
)
|
|
757
|
+
else:
|
|
758
|
+
result = item
|
|
759
|
+
elif _is_handler_implemented(child, "handle_all"):
|
|
760
|
+
if _should_call_handler(child, "handle_all", item):
|
|
761
|
+
handler = child.handle_all
|
|
762
|
+
if asyncio.iscoroutinefunction(handler):
|
|
763
|
+
result = await handler(
|
|
764
|
+
item,
|
|
765
|
+
context,
|
|
766
|
+
*child.process_args,
|
|
767
|
+
**child.process_kwargs,
|
|
768
|
+
)
|
|
769
|
+
else:
|
|
770
|
+
result = handler(
|
|
771
|
+
item,
|
|
772
|
+
context,
|
|
773
|
+
*child.process_args,
|
|
774
|
+
**child.process_kwargs,
|
|
775
|
+
)
|
|
776
|
+
else:
|
|
777
|
+
result = item
|
|
778
|
+
else:
|
|
779
|
+
raise UnhandledTypeError(
|
|
780
|
+
child_name=child.name,
|
|
781
|
+
value_type=type(item).__name__, # type: ignore
|
|
782
|
+
implemented_handlers=_get_implemented_handlers(child),
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
results.append(result)
|
|
786
|
+
|
|
787
|
+
except (ValueError, TypeError) as e:
|
|
788
|
+
path_str = "".join(f"[{idx}]" for idx in current_path)
|
|
789
|
+
error_msg = str(e)
|
|
790
|
+
if path_str and " at index " not in error_msg:
|
|
791
|
+
raise type(e)(f"{error_msg} at index {path_str}") from e
|
|
792
|
+
raise
|
|
793
|
+
|
|
794
|
+
return tuple(results)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def has_async_handlers(child: "ChildNode") -> bool:
|
|
798
|
+
"""
|
|
799
|
+
Check if a child node has any async handlers implemented.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
child (ChildNode):
|
|
803
|
+
The child node to check.
|
|
804
|
+
|
|
805
|
+
Returns:
|
|
806
|
+
bool:
|
|
807
|
+
`True` if any handler is async, `False` otherwise.
|
|
808
|
+
"""
|
|
809
|
+
for handler_name in ALL_HANDLER_NAMES:
|
|
810
|
+
if _is_handler_implemented(child, handler_name):
|
|
811
|
+
handler = getattr(child, handler_name)
|
|
812
|
+
if asyncio.iscoroutinefunction(handler):
|
|
813
|
+
return True
|
|
814
|
+
|
|
815
|
+
return False
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
async def dispatch_to_child_async(
|
|
819
|
+
child: "ChildNode",
|
|
820
|
+
value: Any,
|
|
821
|
+
context: "Context",
|
|
822
|
+
) -> Any:
|
|
823
|
+
"""
|
|
824
|
+
Async version of dispatch_to_child for async handler support.
|
|
825
|
+
|
|
826
|
+
Dispatch value to appropriate child handler with priority system.
|
|
827
|
+
|
|
828
|
+
Args:
|
|
829
|
+
child (ChildNode):
|
|
830
|
+
The child node to dispatch to.
|
|
831
|
+
value (Any):
|
|
832
|
+
The value to process.
|
|
833
|
+
context (Context):
|
|
834
|
+
Processing context with parent, siblings, tags.
|
|
835
|
+
|
|
836
|
+
Returns:
|
|
837
|
+
Any:
|
|
838
|
+
The processed value. Returns original value if the
|
|
839
|
+
handler returns `None`.
|
|
840
|
+
|
|
841
|
+
Raises:
|
|
842
|
+
UnhandledTypeError:
|
|
843
|
+
If no handler is implemented for this value type.
|
|
844
|
+
InvalidHandlerError:
|
|
845
|
+
If `handle_tag` returns a modified dictionary.
|
|
846
|
+
"""
|
|
847
|
+
if isinstance(value, tuple):
|
|
848
|
+
is_container = context.click_context.meta.get("click_extended", {}).get(
|
|
849
|
+
"is_container_tuple", False
|
|
850
|
+
)
|
|
851
|
+
if is_container:
|
|
852
|
+
return await _process_container_tuple_async(
|
|
853
|
+
child,
|
|
854
|
+
value, # type: ignore
|
|
855
|
+
context,
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
if value is None:
|
|
859
|
+
# Handle None
|
|
860
|
+
if _is_handler_implemented(child, "handle_none"):
|
|
861
|
+
try:
|
|
862
|
+
none_handler = child.handle_none
|
|
863
|
+
if asyncio.iscoroutinefunction(none_handler):
|
|
864
|
+
result = await none_handler(
|
|
865
|
+
context, *child.process_args, **child.process_kwargs
|
|
866
|
+
)
|
|
867
|
+
else:
|
|
868
|
+
result = none_handler(
|
|
869
|
+
context, *child.process_args, **child.process_kwargs
|
|
870
|
+
)
|
|
871
|
+
return value if result is None else result # type: ignore
|
|
872
|
+
except NotImplementedError:
|
|
873
|
+
pass
|
|
874
|
+
|
|
875
|
+
for handler_name in TYPE_SPECIFIC_HANDLERS:
|
|
876
|
+
if _is_handler_implemented(child, handler_name):
|
|
877
|
+
if _should_call_handler(child, handler_name, value):
|
|
878
|
+
try:
|
|
879
|
+
handler = getattr(child, handler_name)
|
|
880
|
+
if asyncio.iscoroutinefunction(handler):
|
|
881
|
+
result = await handler(
|
|
882
|
+
value,
|
|
883
|
+
context,
|
|
884
|
+
*child.process_args,
|
|
885
|
+
**child.process_kwargs,
|
|
886
|
+
)
|
|
887
|
+
else:
|
|
888
|
+
result = handler(
|
|
889
|
+
value,
|
|
890
|
+
context,
|
|
891
|
+
*child.process_args,
|
|
892
|
+
**child.process_kwargs,
|
|
893
|
+
)
|
|
894
|
+
|
|
895
|
+
if result is None:
|
|
896
|
+
return value
|
|
897
|
+
return result
|
|
898
|
+
except NotImplementedError:
|
|
899
|
+
pass
|
|
900
|
+
|
|
901
|
+
# Handle all
|
|
902
|
+
try:
|
|
903
|
+
if _should_call_handler(child, "handle_all", value):
|
|
904
|
+
all_handler = child.handle_all
|
|
905
|
+
if asyncio.iscoroutinefunction(all_handler):
|
|
906
|
+
result = await all_handler(
|
|
907
|
+
value,
|
|
908
|
+
context,
|
|
909
|
+
*child.process_args,
|
|
910
|
+
**child.process_kwargs,
|
|
911
|
+
)
|
|
912
|
+
else:
|
|
913
|
+
result = all_handler(
|
|
914
|
+
value,
|
|
915
|
+
context,
|
|
916
|
+
*child.process_args,
|
|
917
|
+
**child.process_kwargs,
|
|
918
|
+
)
|
|
919
|
+
return value if result is None else result
|
|
920
|
+
except NotImplementedError:
|
|
921
|
+
pass
|
|
922
|
+
|
|
923
|
+
return None
|
|
924
|
+
|
|
925
|
+
handler_name = _determine_handler(
|
|
926
|
+
child,
|
|
927
|
+
value,
|
|
928
|
+
context,
|
|
929
|
+
) # type: ignore[assignment]
|
|
930
|
+
|
|
931
|
+
# Handle specific
|
|
932
|
+
if handler_name:
|
|
933
|
+
try:
|
|
934
|
+
if _should_call_handler(child, handler_name, value):
|
|
935
|
+
if "click_extended" in context.click_context.meta:
|
|
936
|
+
context.click_context.meta["click_extended"][
|
|
937
|
+
"handler_method"
|
|
938
|
+
] = handler_name # type: ignore[assignment]
|
|
939
|
+
|
|
940
|
+
handler = getattr(child, handler_name)
|
|
941
|
+
hints = get_type_hints(handler)
|
|
942
|
+
|
|
943
|
+
if "value" in hints:
|
|
944
|
+
is_valid, error_msg = _validate_handler_type(
|
|
945
|
+
handler_name, value, hints["value"]
|
|
946
|
+
)
|
|
947
|
+
if not is_valid:
|
|
948
|
+
raise ProcessError(
|
|
949
|
+
f"Type mismatch in {handler_name}: " f"{error_msg}"
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
if asyncio.iscoroutinefunction(handler):
|
|
953
|
+
result = await handler(
|
|
954
|
+
value,
|
|
955
|
+
context,
|
|
956
|
+
*child.process_args,
|
|
957
|
+
**child.process_kwargs,
|
|
958
|
+
)
|
|
959
|
+
else:
|
|
960
|
+
result = handler(
|
|
961
|
+
value,
|
|
962
|
+
context,
|
|
963
|
+
*child.process_args,
|
|
964
|
+
**child.process_kwargs,
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
if handler_name == "handle_tag" and result is not None:
|
|
968
|
+
message = (
|
|
969
|
+
"Method handle_tag() is validation-only and "
|
|
970
|
+
"does not support transformations."
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
tip = (
|
|
974
|
+
"Remove the return statement to make it "
|
|
975
|
+
"validation-only or move the "
|
|
976
|
+
"transformation logic to the parent node."
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
raise InvalidHandlerError(message=message, tip=tip)
|
|
980
|
+
|
|
981
|
+
return value if result is None else result # type: ignore
|
|
982
|
+
except NotImplementedError:
|
|
983
|
+
pass
|
|
984
|
+
|
|
985
|
+
try:
|
|
986
|
+
if _should_call_handler(child, "handle_all", value):
|
|
987
|
+
if "click_extended" in context.click_context.meta:
|
|
988
|
+
context.click_context.meta["click_extended"][
|
|
989
|
+
"handler_method"
|
|
990
|
+
] = "handle_all"
|
|
991
|
+
|
|
992
|
+
handler = child.handle_all
|
|
993
|
+
if asyncio.iscoroutinefunction(handler):
|
|
994
|
+
result = await handler(
|
|
995
|
+
value, context, *child.process_args, **child.process_kwargs
|
|
996
|
+
)
|
|
997
|
+
else:
|
|
998
|
+
result = handler(
|
|
999
|
+
value, context, *child.process_args, **child.process_kwargs
|
|
1000
|
+
)
|
|
1001
|
+
return value if result is None else result # type: ignore
|
|
1002
|
+
except NotImplementedError:
|
|
1003
|
+
pass
|
|
1004
|
+
|
|
1005
|
+
raise UnhandledTypeError(
|
|
1006
|
+
child_name=child.name,
|
|
1007
|
+
value_type=type(value).__name__, # type: ignore
|
|
1008
|
+
implemented_handlers=_get_implemented_handlers(child),
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
__all__ = [
|
|
1013
|
+
"dispatch_to_child",
|
|
1014
|
+
"dispatch_to_child_async",
|
|
1015
|
+
"has_async_handlers",
|
|
1016
|
+
]
|