click-extended 0.4.0__py3-none-any.whl → 1.0.1__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 (149) hide show
  1. click_extended/__init__.py +10 -6
  2. click_extended/classes.py +12 -8
  3. click_extended/core/__init__.py +10 -0
  4. click_extended/core/decorators/__init__.py +21 -0
  5. click_extended/core/decorators/argument.py +227 -0
  6. click_extended/core/decorators/command.py +93 -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/types.py +1 -1
  133. click_extended/utils/__init__.py +13 -0
  134. click_extended/utils/casing.py +169 -0
  135. click_extended/utils/checks.py +48 -0
  136. click_extended/utils/dispatch.py +1016 -0
  137. click_extended/utils/format.py +101 -0
  138. click_extended/utils/humanize.py +209 -0
  139. click_extended/utils/naming.py +238 -0
  140. click_extended/utils/process.py +294 -0
  141. click_extended/utils/selection.py +267 -0
  142. click_extended/utils/time.py +46 -0
  143. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/METADATA +100 -29
  144. click_extended-1.0.1.dist-info/RECORD +149 -0
  145. click_extended-0.4.0.dist-info/RECORD +0 -10
  146. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/WHEEL +0 -0
  147. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/AUTHORS.md +0 -0
  148. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/LICENSE +0 -0
  149. {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,190 @@
1
+ """Requires decorator for enforcing parameter dependencies."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.decorators.tag import Tag
6
+ from click_extended.core.nodes.child_node import ChildNode
7
+ from click_extended.core.nodes.parent_node import ParentNode
8
+ from click_extended.core.other.context import Context
9
+ from click_extended.types import Decorator
10
+ from click_extended.utils.humanize import humanize_iterable
11
+
12
+
13
+ class Requires(ChildNode):
14
+ """Child node to enforce that required parameters are provided."""
15
+
16
+ def handle_all(
17
+ self, value: Any, context: Context, *args: Any, **kwargs: Any
18
+ ) -> Any:
19
+ parent = context.parent
20
+ if parent is None or not isinstance(parent, ParentNode):
21
+ return value
22
+
23
+ if not parent.was_provided:
24
+ return value
25
+
26
+ if missing := self._get_missing_requirements(context, args):
27
+ parent_display = parent.get_display_name()
28
+ missing_display = humanize_iterable(
29
+ [self._get_display_name(name, context) for name in missing],
30
+ wrap="'",
31
+ )
32
+
33
+ raise ValueError(
34
+ f"'{parent_display}' requires {missing_display} to be provided."
35
+ )
36
+
37
+ return value
38
+
39
+ def handle_tag(
40
+ self, value: dict[str, Any], context: Context, *args: Any, **kwargs: Any
41
+ ) -> None:
42
+ """Validate requirements for tags."""
43
+ tag = context.parent
44
+ if tag is None or not isinstance(tag, Tag):
45
+ return
46
+
47
+ tagged_parents = getattr(tag, "parent_nodes", [])
48
+ if not tagged_parents:
49
+ return
50
+
51
+ provided_count = sum(1 for p in tagged_parents if p.was_provided)
52
+
53
+ require_all_tagged = kwargs.get("require_all_tagged", True)
54
+ should_check = False
55
+ if require_all_tagged:
56
+ should_check = provided_count > 0
57
+ else:
58
+ should_check = provided_count == len(tagged_parents)
59
+
60
+ if not should_check:
61
+ return
62
+
63
+ if missing := self._get_missing_requirements(context, args):
64
+ tag_display = f"tag '{tag.name}'"
65
+ missing_display = humanize_iterable(
66
+ [self._get_display_name(name, context) for name in missing],
67
+ wrap="'",
68
+ )
69
+
70
+ raise ValueError(
71
+ f"'{tag_display}' requires {missing_display} to be provided."
72
+ )
73
+
74
+ def _get_missing_requirements(
75
+ self, context: Context, required_names: tuple[Any, ...]
76
+ ) -> list[str]:
77
+ missing: list[str] = []
78
+
79
+ for name in required_names:
80
+ parent = context.get_parent(name)
81
+ if parent is not None:
82
+ if not parent.was_provided:
83
+ missing.append(name)
84
+ continue
85
+
86
+ if (tag := context.get_tag(name)) is not None:
87
+ tagged_parents = getattr(tag, "parent_nodes", [])
88
+ if not any(p.was_provided for p in tagged_parents):
89
+ missing.append(name)
90
+ continue
91
+
92
+ raise ValueError(
93
+ f"Required parameter or tag '{name}' does not exist. "
94
+ f"Check that it is defined in the command."
95
+ )
96
+
97
+ return missing
98
+
99
+ def _get_display_name(self, name: str, context: Context) -> str:
100
+ parent = context.get_parent(name)
101
+ if parent is not None:
102
+ return parent.get_display_name()
103
+
104
+ tag = context.get_tag(name)
105
+ if tag is not None:
106
+ return f"tag '{tag.name}'"
107
+
108
+ return name
109
+
110
+
111
+ def requires(*names: str, require_all_tagged: bool = True) -> Decorator:
112
+ """
113
+ Enforce that specified parameters or tags are provided.
114
+
115
+ Type: `ChildNode`
116
+
117
+ Supports: `any`, `tag`
118
+
119
+ Args:
120
+ *names (str):
121
+ Names of parameters or tags that must be provided.
122
+ require_all_tagged (bool):
123
+ If a tag is references, all parents must be provided.
124
+ Defaults to `True`.
125
+
126
+ Returns:
127
+ Decorator:
128
+ The decorated function.
129
+
130
+ Raises:
131
+ ValueError:
132
+ If the parent/tag is provided but required dependencies are not met,
133
+ or if a required name doesn't exist.
134
+
135
+ Examples:
136
+ Basic requirement:
137
+ ```python
138
+ @command()
139
+ @option("--output")
140
+ @requires("input")
141
+ @option("--input")
142
+ def my_command(input: str, output: str):
143
+ # --output requires --input to be provided
144
+ pass
145
+ ```
146
+
147
+ Multiple requirements:
148
+ ```python
149
+ @command()
150
+ @option("--save")
151
+ @requires("format", "output")
152
+ @option("--format")
153
+ @option("--output")
154
+ def my_command(save: bool, format: str, output: str):
155
+ # --save requires both --format and --output
156
+ pass
157
+ ```
158
+
159
+ Tag requirements:
160
+
161
+ ```python
162
+ @command()
163
+ @tag("database")
164
+ @requires("host", "port")
165
+ @option("--database-name")
166
+ @option("--database-user")
167
+ @option("--host")
168
+ @option("--port")
169
+ def my_command(**kwargs):
170
+ pass
171
+ ```
172
+
173
+ Tag with require_all_tagged=False:
174
+
175
+ ```python
176
+ @command()
177
+ @tag("advanced")
178
+ @requires("config", require_all_tagged=False)
179
+ # Only if ALL advanced options provided, require config
180
+ @option("--verbose")
181
+ @option("--debug")
182
+ @option("--config")
183
+ def my_command(**kwargs):
184
+ pass
185
+ ```
186
+ """
187
+ return Requires.as_decorator(*names, require_all_tagged=require_all_tagged)
188
+
189
+
190
+ __all__ = ["requires", "Requires"]
@@ -0,0 +1,87 @@
1
+ """Child decorator to check if a string starts with one or more substrings."""
2
+
3
+ import fnmatch
4
+ import re
5
+ from typing import Any
6
+
7
+ from click_extended.core.nodes.child_node import ChildNode
8
+ from click_extended.core.other.context import Context
9
+ from click_extended.types import Decorator
10
+ from click_extended.utils.humanize import humanize_iterable
11
+
12
+
13
+ class StartsWith(ChildNode):
14
+ """
15
+ Child decorator to check if a string starts with one or more substrings.
16
+ """
17
+
18
+ def handle_str(
19
+ self, value: str, context: Context, *args: Any, **kwargs: Any
20
+ ) -> Any:
21
+ patterns: tuple[str | re.Pattern[str], ...] = kwargs.get("text", ())
22
+ matches: list[str | re.Pattern[str]] = []
23
+
24
+ for pattern in patterns:
25
+ if isinstance(pattern, re.Pattern):
26
+ if pattern.match(value):
27
+ matches.append(pattern)
28
+ elif isinstance(pattern, str) and any(
29
+ char in pattern for char in ["*", "?", "[", "]"]
30
+ ):
31
+ if fnmatch.fnmatch(
32
+ value,
33
+ f"{pattern}*" if not pattern.endswith("*") else pattern,
34
+ ):
35
+ matches.append(pattern)
36
+ elif isinstance(pattern, str):
37
+ if value.startswith(pattern):
38
+ matches.append(pattern)
39
+
40
+ if not matches:
41
+ pattern_strs: list[str] = [
42
+ p.pattern if isinstance(p, re.Pattern) else p for p in patterns
43
+ ]
44
+ pattern_list = humanize_iterable(
45
+ pattern_strs,
46
+ wrap="'",
47
+ sep="or",
48
+ prefix_singular="the pattern",
49
+ prefix_plural="one of the patterns",
50
+ )
51
+ raise ValueError(f"Value must start with {pattern_list}")
52
+
53
+ return value
54
+
55
+
56
+ def starts_with(*text: str | re.Pattern[str]) -> Decorator:
57
+ """
58
+ Check if a string starts with one or more substrings or patterns.
59
+
60
+ Type: `ChildNode`
61
+
62
+ Supports: `str`
63
+
64
+ Args:
65
+ *text (str | re.Pattern[str]):
66
+ Patterns to check for.
67
+
68
+ Returns:
69
+ Decorator:
70
+ The decorated function.
71
+
72
+ Examples:
73
+ ```python
74
+ # Exact prefix
75
+ @starts_with("http://", "https://")
76
+
77
+ # Glob pattern
78
+ @starts_with("user_*", "admin_*")
79
+
80
+ # Regex matching
81
+ @starts_with(re.compile(r"https?://"))
82
+
83
+ # Mixed patterns
84
+ @starts_with("www.", re.compile(r"https?://"), "ftp_*")
85
+ ```
86
+ """
87
+ return StartsWith.as_decorator(text=text)
@@ -0,0 +1,37 @@
1
+ """Check if a value is truthy."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+
9
+
10
+ class Truthy(ChildNode):
11
+ """Check if a value is truthy."""
12
+
13
+ def handle_all(
14
+ self,
15
+ value: Any,
16
+ context: Context,
17
+ *args: Any,
18
+ **kwargs: Any,
19
+ ) -> Any:
20
+ if not value:
21
+ raise ValueError(f"Value '{value}' is not truthy.")
22
+ return value
23
+
24
+
25
+ def truthy() -> Decorator:
26
+ """
27
+ Check if a value is truthy.
28
+
29
+ Type: `ChildNode`
30
+
31
+ Supports: `Any`
32
+
33
+ Returns:
34
+ Decorator:
35
+ The decorated function.
36
+ """
37
+ return Truthy.as_decorator()
@@ -0,0 +1,15 @@
1
+ """Initialization file for the `click_extended.decorators.compare` module."""
2
+
3
+ from click_extended.decorators.compare.at_least import at_least
4
+ from click_extended.decorators.compare.at_most import at_most
5
+ from click_extended.decorators.compare.between import between
6
+ from click_extended.decorators.compare.greater_than import greater_than
7
+ from click_extended.decorators.compare.less_than import less_than
8
+
9
+ __all__ = [
10
+ "at_least",
11
+ "at_most",
12
+ "between",
13
+ "greater_than",
14
+ "less_than",
15
+ ]
@@ -0,0 +1,57 @@
1
+ """A child decorator to check if at least n number or arguments are provided."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+ from click_extended.utils.humanize import humanize_iterable
9
+
10
+
11
+ class AtLeast(ChildNode):
12
+ """
13
+ A child decorator to check if at least n
14
+ number or arguments are provided.
15
+ """
16
+
17
+ def handle_tag(
18
+ self,
19
+ value: Any,
20
+ context: Context,
21
+ *args: Any,
22
+ **kwargs: Any,
23
+ ) -> None:
24
+ if context.parent is None:
25
+ raise EnvironmentError("Parent is not defined.")
26
+
27
+ parents = context.get_tagged(context.parent.name)
28
+ required = kwargs["n"]
29
+ provided = sum(p.was_provided for p in parents)
30
+
31
+ if provided < required:
32
+ humanized = humanize_iterable([p.name for p in parents], wrap="'")
33
+ word = "was" if provided == 1 else "were"
34
+ raise ValueError(
35
+ f"At least {required} of {humanized} must be provided, "
36
+ f"but only {provided} {word} given."
37
+ )
38
+
39
+
40
+ def at_least(n: int) -> Decorator:
41
+ """
42
+ Checks if at least `n` number of arguments
43
+ are provided.
44
+
45
+ Type: `ChildNode`
46
+
47
+ Supports: `Tag`
48
+
49
+ Args:
50
+ n (int):
51
+ The number of arguments required.
52
+
53
+ Returns:
54
+ Decorator:
55
+ The decorated function.
56
+ """
57
+ return AtLeast.as_decorator(n=n)
@@ -0,0 +1,57 @@
1
+ """A child decorator to check if at most n number or arguments are provided."""
2
+
3
+ from typing import Any
4
+
5
+ from click_extended.core.nodes.child_node import ChildNode
6
+ from click_extended.core.other.context import Context
7
+ from click_extended.types import Decorator
8
+ from click_extended.utils.humanize import humanize_iterable
9
+
10
+
11
+ class AtMost(ChildNode):
12
+ """
13
+ A child decorator to check if at most n
14
+ number or arguments are provided.
15
+ """
16
+
17
+ def handle_tag(
18
+ self,
19
+ value: Any,
20
+ context: Context,
21
+ *args: Any,
22
+ **kwargs: Any,
23
+ ) -> None:
24
+ if context.parent is None:
25
+ raise EnvironmentError("Parent is not defined.")
26
+
27
+ parents = context.get_tagged(context.parent.name)
28
+ maximum = kwargs["n"]
29
+ provided = sum(p.was_provided for p in parents)
30
+
31
+ if provided > maximum:
32
+ humanized = humanize_iterable([p.name for p in parents], wrap="'")
33
+ word = "was" if provided == 1 else "were"
34
+ raise ValueError(
35
+ f"At most {maximum} of {humanized} can be provided, "
36
+ f"but {provided} {word} given."
37
+ )
38
+
39
+
40
+ def at_most(n: int) -> Decorator:
41
+ """
42
+ Checks if at most `n` number of arguments
43
+ are provided.
44
+
45
+ Type: `ChildNode`
46
+
47
+ Supports: `Tag`
48
+
49
+ Args:
50
+ n (int):
51
+ The maximum number of arguments allowed.
52
+
53
+ Returns:
54
+ Decorator:
55
+ The decorated function.
56
+ """
57
+ return AtMost.as_decorator(n=n)
@@ -0,0 +1,119 @@
1
+ """Check if a value is between two bounds."""
2
+
3
+ from datetime import date, datetime, time
4
+ from typing import Any
5
+
6
+ from click_extended.core.nodes.child_node import ChildNode
7
+ from click_extended.core.other.context import Context
8
+ from click_extended.types import Decorator
9
+
10
+
11
+ class Between(ChildNode):
12
+ """Check if a value is between two bounds."""
13
+
14
+ def _check_between(
15
+ self,
16
+ value: Any,
17
+ lower: Any,
18
+ upper: Any,
19
+ inclusive: bool,
20
+ ) -> Any:
21
+ if inclusive:
22
+ if not lower <= value <= upper:
23
+ raise ValueError(
24
+ f"Value '{value}' is not between '{lower}' and '{upper}'."
25
+ )
26
+ else:
27
+ if not lower < value < upper:
28
+ raise ValueError(
29
+ f"Value '{value}' is not between '{lower}' and '{upper}'."
30
+ )
31
+ return value
32
+
33
+ def handle_numeric(
34
+ self,
35
+ value: int | float,
36
+ context: Context,
37
+ *args: Any,
38
+ **kwargs: Any,
39
+ ) -> Any:
40
+ return self._check_between(
41
+ value,
42
+ kwargs["lower"],
43
+ kwargs["upper"],
44
+ kwargs["inclusive"],
45
+ )
46
+
47
+ def handle_time(
48
+ self,
49
+ value: time,
50
+ context: Context,
51
+ *args: Any,
52
+ **kwargs: Any,
53
+ ) -> Any:
54
+ return self._check_between(
55
+ value,
56
+ kwargs["lower"],
57
+ kwargs["upper"],
58
+ kwargs["inclusive"],
59
+ )
60
+
61
+ def handle_date(
62
+ self,
63
+ value: date,
64
+ context: Context,
65
+ *args: Any,
66
+ **kwargs: Any,
67
+ ) -> Any:
68
+ return self._check_between(
69
+ value,
70
+ kwargs["lower"],
71
+ kwargs["upper"],
72
+ kwargs["inclusive"],
73
+ )
74
+
75
+ def handle_datetime(
76
+ self,
77
+ value: datetime,
78
+ context: Context,
79
+ *args: Any,
80
+ **kwargs: Any,
81
+ ) -> Any:
82
+ return self._check_between(
83
+ value,
84
+ kwargs["lower"],
85
+ kwargs["upper"],
86
+ kwargs["inclusive"],
87
+ )
88
+
89
+
90
+ def between(
91
+ lower: int | float | date | time | datetime,
92
+ upper: int | float | date | time | datetime,
93
+ inclusive: bool = True,
94
+ ) -> Decorator:
95
+ """
96
+ Check if a value is between two bounds where the bounds must be of
97
+ the same type.
98
+
99
+ Type: `ChildNode`
100
+
101
+ Supports: `int`, `float`, `date`, `time`, `datetime`
102
+
103
+ Args:
104
+ lower (int | float | date | time | datetime):
105
+ The lower bound to check.
106
+ upper (int | float | date | time | datetime):
107
+ The upper bound to check.
108
+ inclusive (bool, optional):
109
+ Whether to include the bounds or not. Defaults to `True`.
110
+
111
+ Returns:
112
+ Decorator:
113
+ The decorated function.
114
+ """
115
+ return Between.as_decorator(
116
+ lower=lower,
117
+ upper=upper,
118
+ inclusive=inclusive,
119
+ )