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.
Files changed (148) hide show
  1. click_extended/__init__.py +2 -0
  2. click_extended/core/__init__.py +10 -0
  3. click_extended/core/decorators/__init__.py +21 -0
  4. click_extended/core/decorators/argument.py +227 -0
  5. click_extended/core/decorators/command.py +93 -0
  6. click_extended/core/decorators/context.py +56 -0
  7. click_extended/core/decorators/env.py +155 -0
  8. click_extended/core/decorators/group.py +96 -0
  9. click_extended/core/decorators/option.py +347 -0
  10. click_extended/core/decorators/prompt.py +69 -0
  11. click_extended/core/decorators/selection.py +155 -0
  12. click_extended/core/decorators/tag.py +109 -0
  13. click_extended/core/nodes/__init__.py +21 -0
  14. click_extended/core/nodes/_root_node.py +1012 -0
  15. click_extended/core/nodes/argument_node.py +165 -0
  16. click_extended/core/nodes/child_node.py +555 -0
  17. click_extended/core/nodes/child_validation_node.py +100 -0
  18. click_extended/core/nodes/node.py +55 -0
  19. click_extended/core/nodes/option_node.py +205 -0
  20. click_extended/core/nodes/parent_node.py +220 -0
  21. click_extended/core/nodes/validation_node.py +124 -0
  22. click_extended/core/other/__init__.py +7 -0
  23. click_extended/core/other/_click_command.py +60 -0
  24. click_extended/core/other/_click_group.py +246 -0
  25. click_extended/core/other/_tree.py +491 -0
  26. click_extended/core/other/context.py +496 -0
  27. click_extended/decorators/__init__.py +29 -0
  28. click_extended/decorators/check/__init__.py +57 -0
  29. click_extended/decorators/check/conflicts.py +149 -0
  30. click_extended/decorators/check/contains.py +69 -0
  31. click_extended/decorators/check/dependencies.py +115 -0
  32. click_extended/decorators/check/divisible_by.py +48 -0
  33. click_extended/decorators/check/ends_with.py +85 -0
  34. click_extended/decorators/check/exclusive.py +75 -0
  35. click_extended/decorators/check/falsy.py +37 -0
  36. click_extended/decorators/check/is_email.py +43 -0
  37. click_extended/decorators/check/is_hex_color.py +41 -0
  38. click_extended/decorators/check/is_hostname.py +47 -0
  39. click_extended/decorators/check/is_ipv4.py +46 -0
  40. click_extended/decorators/check/is_ipv6.py +46 -0
  41. click_extended/decorators/check/is_json.py +40 -0
  42. click_extended/decorators/check/is_mac_address.py +40 -0
  43. click_extended/decorators/check/is_negative.py +37 -0
  44. click_extended/decorators/check/is_non_zero.py +37 -0
  45. click_extended/decorators/check/is_port.py +39 -0
  46. click_extended/decorators/check/is_positive.py +37 -0
  47. click_extended/decorators/check/is_url.py +75 -0
  48. click_extended/decorators/check/is_uuid.py +40 -0
  49. click_extended/decorators/check/length.py +68 -0
  50. click_extended/decorators/check/not_empty.py +49 -0
  51. click_extended/decorators/check/regex.py +47 -0
  52. click_extended/decorators/check/requires.py +190 -0
  53. click_extended/decorators/check/starts_with.py +87 -0
  54. click_extended/decorators/check/truthy.py +37 -0
  55. click_extended/decorators/compare/__init__.py +15 -0
  56. click_extended/decorators/compare/at_least.py +57 -0
  57. click_extended/decorators/compare/at_most.py +57 -0
  58. click_extended/decorators/compare/between.py +119 -0
  59. click_extended/decorators/compare/greater_than.py +183 -0
  60. click_extended/decorators/compare/less_than.py +183 -0
  61. click_extended/decorators/convert/__init__.py +31 -0
  62. click_extended/decorators/convert/convert_angle.py +94 -0
  63. click_extended/decorators/convert/convert_area.py +123 -0
  64. click_extended/decorators/convert/convert_bits.py +211 -0
  65. click_extended/decorators/convert/convert_distance.py +154 -0
  66. click_extended/decorators/convert/convert_energy.py +155 -0
  67. click_extended/decorators/convert/convert_power.py +128 -0
  68. click_extended/decorators/convert/convert_pressure.py +131 -0
  69. click_extended/decorators/convert/convert_speed.py +122 -0
  70. click_extended/decorators/convert/convert_temperature.py +89 -0
  71. click_extended/decorators/convert/convert_time.py +108 -0
  72. click_extended/decorators/convert/convert_volume.py +218 -0
  73. click_extended/decorators/convert/convert_weight.py +158 -0
  74. click_extended/decorators/load/__init__.py +13 -0
  75. click_extended/decorators/load/load_csv.py +117 -0
  76. click_extended/decorators/load/load_json.py +61 -0
  77. click_extended/decorators/load/load_toml.py +47 -0
  78. click_extended/decorators/load/load_yaml.py +72 -0
  79. click_extended/decorators/math/__init__.py +37 -0
  80. click_extended/decorators/math/absolute.py +35 -0
  81. click_extended/decorators/math/add.py +48 -0
  82. click_extended/decorators/math/ceil.py +36 -0
  83. click_extended/decorators/math/clamp.py +51 -0
  84. click_extended/decorators/math/divide.py +42 -0
  85. click_extended/decorators/math/floor.py +36 -0
  86. click_extended/decorators/math/maximum.py +39 -0
  87. click_extended/decorators/math/minimum.py +39 -0
  88. click_extended/decorators/math/modulo.py +39 -0
  89. click_extended/decorators/math/multiply.py +51 -0
  90. click_extended/decorators/math/normalize.py +76 -0
  91. click_extended/decorators/math/power.py +39 -0
  92. click_extended/decorators/math/rounded.py +39 -0
  93. click_extended/decorators/math/sqrt.py +39 -0
  94. click_extended/decorators/math/subtract.py +39 -0
  95. click_extended/decorators/math/to_percent.py +63 -0
  96. click_extended/decorators/misc/__init__.py +17 -0
  97. click_extended/decorators/misc/choice.py +139 -0
  98. click_extended/decorators/misc/confirm_if.py +147 -0
  99. click_extended/decorators/misc/default.py +95 -0
  100. click_extended/decorators/misc/deprecated.py +131 -0
  101. click_extended/decorators/misc/experimental.py +79 -0
  102. click_extended/decorators/misc/now.py +42 -0
  103. click_extended/decorators/random/__init__.py +21 -0
  104. click_extended/decorators/random/random_bool.py +49 -0
  105. click_extended/decorators/random/random_choice.py +63 -0
  106. click_extended/decorators/random/random_datetime.py +140 -0
  107. click_extended/decorators/random/random_float.py +62 -0
  108. click_extended/decorators/random/random_integer.py +56 -0
  109. click_extended/decorators/random/random_prime.py +196 -0
  110. click_extended/decorators/random/random_string.py +77 -0
  111. click_extended/decorators/random/random_uuid.py +119 -0
  112. click_extended/decorators/transform/__init__.py +71 -0
  113. click_extended/decorators/transform/add_prefix.py +58 -0
  114. click_extended/decorators/transform/add_suffix.py +58 -0
  115. click_extended/decorators/transform/apply.py +35 -0
  116. click_extended/decorators/transform/basename.py +44 -0
  117. click_extended/decorators/transform/dirname.py +44 -0
  118. click_extended/decorators/transform/expand_vars.py +36 -0
  119. click_extended/decorators/transform/remove_prefix.py +57 -0
  120. click_extended/decorators/transform/remove_suffix.py +57 -0
  121. click_extended/decorators/transform/replace.py +46 -0
  122. click_extended/decorators/transform/slugify.py +45 -0
  123. click_extended/decorators/transform/split.py +43 -0
  124. click_extended/decorators/transform/strip.py +148 -0
  125. click_extended/decorators/transform/to_case.py +216 -0
  126. click_extended/decorators/transform/to_date.py +75 -0
  127. click_extended/decorators/transform/to_datetime.py +83 -0
  128. click_extended/decorators/transform/to_path.py +274 -0
  129. click_extended/decorators/transform/to_time.py +77 -0
  130. click_extended/decorators/transform/to_timestamp.py +114 -0
  131. click_extended/decorators/transform/truncate.py +47 -0
  132. click_extended/utils/__init__.py +13 -0
  133. click_extended/utils/casing.py +169 -0
  134. click_extended/utils/checks.py +48 -0
  135. click_extended/utils/dispatch.py +1016 -0
  136. click_extended/utils/format.py +101 -0
  137. click_extended/utils/humanize.py +209 -0
  138. click_extended/utils/naming.py +238 -0
  139. click_extended/utils/process.py +294 -0
  140. click_extended/utils/selection.py +267 -0
  141. click_extended/utils/time.py +46 -0
  142. {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/METADATA +2 -1
  143. click_extended-1.0.2.dist-info/RECORD +150 -0
  144. click_extended-1.0.0.dist-info/RECORD +0 -10
  145. {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/WHEEL +0 -0
  146. {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/licenses/AUTHORS.md +0 -0
  147. {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/licenses/LICENSE +0 -0
  148. {click_extended-1.0.0.dist-info → click_extended-1.0.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,165 @@
1
+ """ArgumentNode abstract base class for CLI argument nodes."""
2
+
3
+ # pylint: disable=too-many-arguments
4
+ # pylint: disable=too-many-positional-arguments
5
+ # pylint: disable=redefined-builtin
6
+ # pylint: disable=arguments-differ
7
+ # pylint: disable=line-too-long
8
+
9
+ from abc import ABC, abstractmethod
10
+ from typing import TYPE_CHECKING, Any, Callable, ParamSpec, Type, TypeVar
11
+
12
+ from click_extended.core.nodes.parent_node import ParentNode
13
+
14
+ if TYPE_CHECKING:
15
+ from click_extended.core.other.context import Context
16
+
17
+ P = ParamSpec("P")
18
+ T = TypeVar("T")
19
+
20
+
21
+ class ArgumentNode(ParentNode, ABC):
22
+ """
23
+ Abstract base class for nodes that receive CLI argument values.
24
+
25
+ ArgumentNode extends ParentNode to handle command-line arguments.
26
+ The key difference is that the `load()` method receives a `value`
27
+ parameter containing the parsed CLI argument value.
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ name: str,
33
+ param: str | None = None,
34
+ nargs: int = 1,
35
+ type: Type[Any] | None = None,
36
+ help: str | None = None,
37
+ required: bool = True,
38
+ default: Any = None,
39
+ tags: str | list[str] | None = None,
40
+ **kwargs: Any,
41
+ ):
42
+ """
43
+ Initialize a new `ArgumentNode` instance.
44
+
45
+ Args:
46
+ name (str):
47
+ The argument name (parameter name for injection).
48
+ param (str, optional):
49
+ Custom parameter name for the function.
50
+ If not provided, uses `name`.
51
+ nargs (int):
52
+ Number of arguments to accept. Use `-1` for unlimited.
53
+ Defaults to `1`.
54
+ type (Type[Any], optional):
55
+ The type to convert the value to (`int`, `str`, `float`, `bool`).
56
+ help (str, optional):
57
+ Help text for this argument.
58
+ required (bool):
59
+ Whether this argument is required. Defaults to `True`.
60
+ default (Any):
61
+ Default value if not provided. Defaults to `None`.
62
+ tags (str | list[str], optional):
63
+ Tag(s) to associate with this argument for grouping.
64
+ **kwargs (Any):
65
+ Additional keyword arguments passed to parent class.
66
+ """
67
+ super().__init__(
68
+ name=param if param is not None else name,
69
+ param=param,
70
+ help=help,
71
+ required=required,
72
+ default=default,
73
+ tags=tags,
74
+ **kwargs,
75
+ )
76
+ self.nargs = nargs
77
+ self.type = type
78
+
79
+ @abstractmethod
80
+ def load(
81
+ self,
82
+ value: str | int | float | bool | None,
83
+ context: "Context",
84
+ *args: Any,
85
+ **kwargs: Any,
86
+ ) -> Any:
87
+ """
88
+ Load and process the CLI argument value.
89
+
90
+ This method is called with the parsed CLI argument value and should
91
+ return the processed value to be injected into the function.
92
+
93
+ Args:
94
+ value (str | int | float | bool | None):
95
+ The parsed CLI argument value from Click.
96
+ context (Context):
97
+ The current context instance.
98
+ *args (Any):
99
+ Optional positional arguments.
100
+ **kwargs (Any):
101
+ Optional keyword arguments.
102
+
103
+ Returns:
104
+ Any:
105
+ The processed value to inject into the function.
106
+ """
107
+ raise NotImplementedError
108
+
109
+ @classmethod
110
+ def as_decorator(
111
+ cls,
112
+ *,
113
+ name: str,
114
+ param: str | None = None,
115
+ nargs: int = 1,
116
+ type: Type[Any] | None = None,
117
+ help: str | None = None,
118
+ required: bool = True,
119
+ default: Any = None,
120
+ tags: str | list[str] | None = None,
121
+ **kwargs: Any,
122
+ ) -> Callable[[Callable[P, T]], Callable[P, T]]:
123
+ """
124
+ Return a decorator representation of the argument node.
125
+
126
+ Args:
127
+ name (str):
128
+ The argument name (parameter name for injection).
129
+ param (str, optional):
130
+ Custom parameter name for the function.
131
+ If not provided, uses `name`.
132
+ nargs (int):
133
+ Number of arguments to accept. Use `-1` for unlimited.
134
+ Defaults to `1`.
135
+ type (Type[Any], optional):
136
+ The type to convert the value to (`int`, `str`, `float`, `bool`).
137
+ help (str, optional):
138
+ Help text for this argument.
139
+ required (bool):
140
+ Whether this argument is required. Defaults to `True`.
141
+ default (Any):
142
+ Default value if not provided. Defaults to `None`.
143
+ tags (str | list[str], optional):
144
+ Tag(s) to associate with this argument for grouping.
145
+ **kwargs (Any):
146
+ Additional keyword arguments passed to __init__ and load().
147
+
148
+ Returns:
149
+ Callable:
150
+ A decorator function that registers the argument node.
151
+ """
152
+ return super().as_decorator(
153
+ name=name,
154
+ param=param,
155
+ nargs=nargs,
156
+ type=type,
157
+ help=help,
158
+ required=required,
159
+ default=default,
160
+ tags=tags,
161
+ **kwargs,
162
+ )
163
+
164
+
165
+ __all__ = ["ArgumentNode"]
@@ -0,0 +1,555 @@
1
+ """ChildNode class for handler-based value processing."""
2
+
3
+ # pylint: disable=too-many-public-methods
4
+
5
+ from abc import ABC
6
+ from datetime import date, datetime, time
7
+ from decimal import Decimal
8
+ from pathlib import Path
9
+ from typing import TYPE_CHECKING, Any, Callable
10
+ from uuid import UUID
11
+
12
+ from click_extended.core.nodes.node import Node
13
+ from click_extended.core.other._tree import Tree
14
+ from click_extended.core.other.context import Context
15
+ from click_extended.utils.casing import Casing
16
+
17
+ if TYPE_CHECKING:
18
+ from click_extended.types import Decorator
19
+
20
+
21
+ class ChildNode(Node, ABC):
22
+ """
23
+ Base class for child nodes with type-specific handlers. Child nodes
24
+ process values through handler methods based on value type.
25
+
26
+ Handlers operate on a priority system with specificity deciding the order.
27
+ For example, `handle_flat_tuple` takes priority over `handle_tuple` which in
28
+ turn takes priority over `handle_all`.
29
+
30
+ All handlers are optional. Override only the handlers you need.
31
+ Returning None (or no return) passes the value through unchanged and makes
32
+ that type validation-only.
33
+
34
+ If a value is passed to the child in which it has not implemented a
35
+ relevant handler, an `UnhandledTypeError` exception is raised.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ name: str,
41
+ process_args: tuple[Any, ...] | None = None,
42
+ process_kwargs: dict[str, Any] | None = None,
43
+ **kwargs: Any,
44
+ ) -> None:
45
+ """
46
+ Initialize a new `ChildNode` instance.
47
+
48
+ Args:
49
+ name (str):
50
+ The name of the node.
51
+ process_args (tuple):
52
+ Positional arguments to pass to handler methods.
53
+ process_kwargs (dict[str, Any]):
54
+ Keyword arguments to pass to handler methods.
55
+ **kwargs (Any):
56
+ Additional keyword arguments for multiple inheritance.
57
+ """
58
+ children = kwargs.pop("children", None)
59
+ super().__init__(name=name, children=children, **kwargs)
60
+ self.process_args = process_args or ()
61
+ self.process_kwargs = process_kwargs or {}
62
+
63
+ def handle_none(self, context: "Context", *args: Any, **kwargs: Any) -> Any:
64
+ """
65
+ Handle None values explicitly.
66
+
67
+ Called when value is `None` before any other handler.
68
+ If not implemented, `None` values are auto-skipped.
69
+
70
+ Args:
71
+ context (Context):
72
+ Information about the current context.
73
+ *args (Any):
74
+ Additional positional arguments from decorator.
75
+ **kwargs (Any):
76
+ Additional keyword arguments from decorator.
77
+
78
+ Returns:
79
+ Any:
80
+ The processed value, or `None` to pass through unchanged.
81
+ """
82
+ raise NotImplementedError
83
+
84
+ def handle_all(
85
+ self, value: Any, context: "Context", *args: Any, **kwargs: Any
86
+ ) -> Any:
87
+ """
88
+ Handle all value types. Called as fallback if no
89
+ specific handler is implemented.
90
+
91
+ Also catches `None` values if `handle_none` is not implemented.
92
+
93
+ Args:
94
+ value (Any):
95
+ The value to process.
96
+ context (Context):
97
+ Information about the current context.
98
+ *args (Any):
99
+ Additional positional arguments from decorator.
100
+ **kwargs (Any):
101
+ Additional keyword arguments from decorator.
102
+
103
+ Returns:
104
+ Any:
105
+ Processed value, or None to pass through unchanged.
106
+ """
107
+ raise NotImplementedError
108
+
109
+ def handle_str(
110
+ self, value: str, context: "Context", *args: Any, **kwargs: Any
111
+ ) -> Any:
112
+ """
113
+ Handle string values.
114
+
115
+ Args:
116
+ value (str):
117
+ The string value to process.
118
+ context (Context):
119
+ Information about the current context.
120
+ *args (Any):
121
+ Additional positional arguments from decorator.
122
+ **kwargs (Any):
123
+ Additional keyword arguments from decorator.
124
+
125
+ Returns:
126
+ Any:
127
+ Processed value, or `None` to pass through unchanged.
128
+ """
129
+ raise NotImplementedError
130
+
131
+ def handle_int(
132
+ self, value: int, context: "Context", *args: Any, **kwargs: Any
133
+ ) -> Any:
134
+ """
135
+ Handle integer values.
136
+
137
+ Args:
138
+ value (int):
139
+ The integer value to process.
140
+ context (Context):
141
+ Information about the current context.
142
+ *args (Any):
143
+ Additional positional arguments from decorator.
144
+ **kwargs (Any):
145
+ Additional keyword arguments from decorator.
146
+
147
+ Returns:
148
+ Any:
149
+ Processed value, or `None` to pass through unchanged.
150
+ """
151
+ raise NotImplementedError
152
+
153
+ def handle_float(
154
+ self, value: float, context: "Context", *args: Any, **kwargs: Any
155
+ ) -> Any:
156
+ """
157
+ Handle float values.
158
+
159
+ Args:
160
+ value (float):
161
+ The float value to process.
162
+ context (Context):
163
+ Information about the current context.
164
+ *args (Any):
165
+ Additional positional arguments from decorator.
166
+ **kwargs (Any):
167
+ Additional keyword arguments from decorator.
168
+
169
+ Returns:
170
+ Any:
171
+ Processed value, or `None` to pass through unchanged.
172
+ """
173
+ raise NotImplementedError
174
+
175
+ def handle_bool(
176
+ self, value: bool, context: "Context", *args: Any, **kwargs: Any
177
+ ) -> Any:
178
+ """
179
+ Handle boolean values.
180
+
181
+ Args:
182
+ value (bool):
183
+ The boolean value to process.
184
+ context (Context):
185
+ Information about the current context.
186
+ *args (Any):
187
+ Additional positional arguments from decorator.
188
+ **kwargs (Any):
189
+ Additional keyword arguments from decorator.
190
+
191
+ Returns:
192
+ Any:
193
+ Processed value, or `None` to pass through unchanged.
194
+ """
195
+ raise NotImplementedError
196
+
197
+ def handle_numeric(
198
+ self, value: int | float, context: "Context", *args: Any, **kwargs: Any
199
+ ) -> Any:
200
+ """
201
+ Handle numeric values (int or float).
202
+
203
+ This is a union handler that works with both integers and floats.
204
+ Use this when your decorator logic applies to all numeric types.
205
+
206
+ Args:
207
+ value (int | float):
208
+ The numeric value to process.
209
+ context (Context):
210
+ Information about the current context.
211
+ *args (Any):
212
+ Additional positional arguments from decorator.
213
+ **kwargs (Any):
214
+ Additional keyword arguments from decorator.
215
+
216
+ Returns:
217
+ Any:
218
+ Processed value, or `None` to pass through unchanged.
219
+ """
220
+ raise NotImplementedError
221
+
222
+ def handle_date(
223
+ self, value: date, context: "Context", *args: Any, **kwargs: Any
224
+ ) -> Any:
225
+ """
226
+ Handle date objects (datetime.date).
227
+
228
+ Args:
229
+ value (date):
230
+ The date object to process.
231
+ context (Context):
232
+ Information about the current context.
233
+ *args (Any):
234
+ Additional positional arguments from decorator.
235
+ **kwargs (Any):
236
+ Additional keyword arguments from decorator.
237
+
238
+ Returns:
239
+ Any:
240
+ Processed value, or `None` to pass through unchanged.
241
+ """
242
+ raise NotImplementedError
243
+
244
+ def handle_time(
245
+ self, value: time, context: "Context", *args: Any, **kwargs: Any
246
+ ) -> Any:
247
+ """
248
+ Handle time objects (datetime.time).
249
+
250
+ Args:
251
+ value (time):
252
+ The time object to process.
253
+ context (Context):
254
+ Information about the current context.
255
+ *args (Any):
256
+ Additional positional arguments from decorator.
257
+ **kwargs (Any):
258
+ Additional keyword arguments from decorator.
259
+
260
+ Returns:
261
+ Any:
262
+ Processed value, or `None` to pass through unchanged.
263
+ """
264
+ raise NotImplementedError
265
+
266
+ def handle_tuple(
267
+ self,
268
+ value: tuple[Any, ...],
269
+ context: "Context",
270
+ *args: Any,
271
+ **kwargs: Any,
272
+ ) -> Any:
273
+ """
274
+ Handle any tuple structure (fallback).
275
+
276
+ Called when:
277
+
278
+ - `handle_flat_tuple` not implemented and the tuple is flat
279
+ - `handle_nested_tuple` not implemented and the tuple is nested
280
+ - Tuple has mixed primitive and complex types
281
+
282
+ Use this when you want to handle all tuple types with the same logic,
283
+ or when you need to handle mixed-type tuples.
284
+
285
+ Examples:
286
+
287
+ - `(1, 2, 3)` if `handle_flat_tuple` not implemented
288
+ - `((1, 2),)` if `handle_nested_tuple` not implemented
289
+ - `(1, [2, 3], "hello")`: mixed types (only option)
290
+
291
+ Args:
292
+ value (tuple[Any, ...]):
293
+ The tuple to process.
294
+ context (Context):
295
+ Information about the current context.
296
+ *args (Any):
297
+ Additional positional arguments from decorator.
298
+ **kwargs (Any):
299
+ Additional keyword arguments from decorator.
300
+
301
+ Returns:
302
+ Any:
303
+ Processed value, or `None` to pass through unchanged.
304
+ """
305
+ raise NotImplementedError
306
+
307
+ def handle_list(
308
+ self,
309
+ value: list[Any],
310
+ context: "Context",
311
+ *args: Any,
312
+ **kwargs: Any,
313
+ ) -> Any:
314
+ """
315
+ Handle list values.
316
+
317
+ Args:
318
+ value (list[Any]):
319
+ The list to process.
320
+ context (Context):
321
+ Information about the current context.
322
+ *args (Any):
323
+ Additional positional arguments from decorator.
324
+ **kwargs (Any):
325
+ Additional keyword arguments from decorator.
326
+
327
+ Returns:
328
+ Any:
329
+ Processed value, or `None` to pass through unchanged.
330
+ """
331
+ raise NotImplementedError
332
+
333
+ def handle_dict(
334
+ self,
335
+ value: dict[Any, Any],
336
+ context: "Context",
337
+ *args: Any,
338
+ **kwargs: Any,
339
+ ) -> Any:
340
+ """
341
+ Handle dictionary values.
342
+
343
+ Args:
344
+ value (dict[Any, Any]):
345
+ The dictionary to process.
346
+ context (Context):
347
+ Information about the current context.
348
+ *args (Any):
349
+ Additional positional arguments from decorator.
350
+ **kwargs (Any):
351
+ Additional keyword arguments from decorator.
352
+
353
+ Returns:
354
+ Any:
355
+ Processed value, or `None` to pass through unchanged.
356
+ """
357
+ raise NotImplementedError
358
+
359
+ def handle_tag(
360
+ self,
361
+ value: dict[str, Any],
362
+ context: "Context",
363
+ *args: Any,
364
+ **kwargs: Any,
365
+ ) -> Any:
366
+ """
367
+ Handle values from a `Tag` parent.
368
+
369
+ Called when the parent node is a `Tag`. The value is a dictionary
370
+ mapping parameter names to their values from all parent nodes
371
+ (`Option`, `Argument`, `Env`, etc.) that reference this tag.
372
+
373
+ Can be implemented as sync or async. This handler is validation-only
374
+ and must not return a value (or return None).
375
+
376
+ Args:
377
+ value (dict[str, Any]):
378
+ Dictionary mapping parameter names to values from
379
+ all parent nodes referencing this tag. Keys are parent
380
+ node names (e.g., "username"), values are `None` if not
381
+ provided by the user.
382
+ context (Context):
383
+ Information about the current context.
384
+ *args (Any):
385
+ Additional positional arguments from decorator.
386
+ **kwargs (Any):
387
+ Additional keyword arguments from decorator.
388
+
389
+ Returns:
390
+ None:
391
+ Must return None (validation-only).
392
+ """
393
+ raise NotImplementedError
394
+
395
+ def handle_datetime(
396
+ self,
397
+ value: datetime,
398
+ context: "Context",
399
+ *args: Any,
400
+ **kwargs: Any,
401
+ ) -> Any:
402
+ """
403
+ Handle datetime objects (datetime.datetime).
404
+
405
+ Args:
406
+ value (datetime):
407
+ The datetime object to process.
408
+ context (Context):
409
+ Information about the current context.
410
+ *args (Any):
411
+ Additional positional arguments from decorator.
412
+ **kwargs (Any):
413
+ Additional keyword arguments from decorator.
414
+
415
+ Returns:
416
+ Any:
417
+ Processed value, or `None` to pass through unchanged.
418
+ """
419
+ raise NotImplementedError
420
+
421
+ def handle_uuid(
422
+ self, value: UUID, context: "Context", *args: Any, **kwargs: Any
423
+ ) -> Any:
424
+ """
425
+ Handle UUID objects.
426
+
427
+ Args:
428
+ value (UUID):
429
+ The UUID object to process.
430
+ context (Context):
431
+ Information about the current context.
432
+ *args (Any):
433
+ Additional positional arguments from decorator.
434
+ **kwargs (Any):
435
+ Additional keyword arguments from decorator.
436
+
437
+ Returns:
438
+ Any:
439
+ Processed value, or `None` to pass through unchanged.
440
+ """
441
+ raise NotImplementedError
442
+
443
+ def handle_path(
444
+ self, value: Path, context: "Context", *args: Any, **kwargs: Any
445
+ ) -> Any:
446
+ """
447
+ Handle Path objects.
448
+
449
+ Args:
450
+ value (Path):
451
+ The Path object to process.
452
+ context (Context):
453
+ Information about the current context.
454
+ *args (Any):
455
+ Additional positional arguments from decorator.
456
+ **kwargs (Any):
457
+ Additional keyword arguments from decorator.
458
+
459
+ Returns:
460
+ Any:
461
+ Processed value, or `None` to pass through unchanged.
462
+ """
463
+ raise NotImplementedError
464
+
465
+ def handle_bytes(
466
+ self, value: bytes, context: "Context", *args: Any, **kwargs: Any
467
+ ) -> Any:
468
+ """
469
+ Handle bytes objects.
470
+
471
+ Args:
472
+ value (bytes):
473
+ The bytes object to process.
474
+ context (Context):
475
+ Information about the current context.
476
+ *args (Any):
477
+ Additional positional arguments from decorator.
478
+ **kwargs (Any):
479
+ Additional keyword arguments from decorator.
480
+
481
+ Returns:
482
+ Any:
483
+ Processed value, or `None` to pass through unchanged.
484
+ """
485
+ raise NotImplementedError
486
+
487
+ def handle_decimal(
488
+ self, value: Decimal, context: "Context", *args: Any, **kwargs: Any
489
+ ) -> Any:
490
+ """
491
+ Handle Decimal objects.
492
+
493
+ Args:
494
+ value (Decimal):
495
+ The Decimal object to process.
496
+ context (Context):
497
+ Information about the current context.
498
+ *args (Any):
499
+ Additional positional arguments from decorator.
500
+ **kwargs (Any):
501
+ Additional keyword arguments from decorator.
502
+
503
+ Returns:
504
+ Any:
505
+ Processed value, or `None` to pass through unchanged.
506
+ """
507
+ raise NotImplementedError
508
+
509
+ def get(self, _name: str) -> None:
510
+ """
511
+ The ChildNode has no children, thus this method returns None.
512
+
513
+ Args:
514
+ _name (str):
515
+ The name of the child.
516
+
517
+ Returns:
518
+ None:
519
+ Always returns None as the ChildNode has no children.
520
+ """
521
+ return None
522
+
523
+ def __getitem__(self, name: str | int) -> "Node":
524
+ raise KeyError("A ChildNode instance has no children.")
525
+
526
+ @classmethod
527
+ def as_decorator(cls, *args: Any, **kwargs: Any) -> "Decorator":
528
+ """
529
+ Return a decorator representation of the child node.
530
+
531
+ The provided `args` and `kwargs` are stored and later passed to handler
532
+ methods when called by the parent processing.
533
+
534
+ Args:
535
+ *args (Any):
536
+ Positional arguments to pass to handler methods.
537
+ **kwargs (Any):
538
+ Keyword arguments to pass to handler methods.
539
+
540
+ Returns:
541
+ Decorator:
542
+ A decorator function that registers the child node.
543
+ """
544
+
545
+ def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
546
+ """The actual decorator that wraps the function."""
547
+ name = Casing.to_snake_case(cls.__name__)
548
+ instance = cls(name=name, process_args=args, process_kwargs=kwargs)
549
+ Tree.queue_child(instance)
550
+ return func
551
+
552
+ return decorator
553
+
554
+
555
+ __all__ = ["ChildNode"]