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,183 @@
|
|
|
1
|
+
"""Child decorator to validate that a value is more than a threshold."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
|
+
from decimal import Decimal
|
|
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
|
+
|
|
11
|
+
|
|
12
|
+
class GreaterThan(ChildNode):
|
|
13
|
+
"""Child decorator to validate that a value is more than a threshold."""
|
|
14
|
+
|
|
15
|
+
def handle_numeric(
|
|
16
|
+
self,
|
|
17
|
+
value: int | float | Decimal,
|
|
18
|
+
context: Context,
|
|
19
|
+
*args: Any,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> int | float | Decimal:
|
|
22
|
+
threshold = kwargs["threshold"]
|
|
23
|
+
inclusive = kwargs["inclusive"]
|
|
24
|
+
|
|
25
|
+
if inclusive:
|
|
26
|
+
if value < threshold:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Value must be at least {threshold}, got {value}"
|
|
29
|
+
)
|
|
30
|
+
else:
|
|
31
|
+
if value <= threshold:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Value must be more than {threshold}, got {value}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
def handle_datetime(
|
|
39
|
+
self,
|
|
40
|
+
value: datetime,
|
|
41
|
+
context: Context,
|
|
42
|
+
*args: Any,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> datetime:
|
|
45
|
+
threshold = kwargs["threshold"]
|
|
46
|
+
inclusive = kwargs["inclusive"]
|
|
47
|
+
|
|
48
|
+
if inclusive:
|
|
49
|
+
if value < threshold:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Value must be at least {threshold}, got {value}"
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
if value <= threshold:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Value must be more than {threshold}, got {value}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
def handle_date(
|
|
62
|
+
self,
|
|
63
|
+
value: date,
|
|
64
|
+
context: Context,
|
|
65
|
+
*args: Any,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> date:
|
|
68
|
+
threshold = kwargs["threshold"]
|
|
69
|
+
inclusive = kwargs["inclusive"]
|
|
70
|
+
|
|
71
|
+
if inclusive:
|
|
72
|
+
if value < threshold:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Value must be at least {threshold}, got {value}"
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
if value <= threshold:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Value must be more than {threshold}, got {value}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
def handle_time(
|
|
85
|
+
self,
|
|
86
|
+
value: time,
|
|
87
|
+
context: Context,
|
|
88
|
+
*args: Any,
|
|
89
|
+
**kwargs: Any,
|
|
90
|
+
) -> time:
|
|
91
|
+
threshold = kwargs["threshold"]
|
|
92
|
+
inclusive = kwargs["inclusive"]
|
|
93
|
+
|
|
94
|
+
if inclusive:
|
|
95
|
+
if value < threshold:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Value must be at least {threshold}, got {value}"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
if value <= threshold:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Value must be more than {threshold}, got {value}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def greater_than(
|
|
109
|
+
threshold: int | float | Decimal | datetime | date | time,
|
|
110
|
+
inclusive: bool = False,
|
|
111
|
+
) -> Decorator:
|
|
112
|
+
"""
|
|
113
|
+
Validate that a value is more than a threshold.
|
|
114
|
+
|
|
115
|
+
Type: `ChildNode`
|
|
116
|
+
|
|
117
|
+
Supports: `int`, `float`, `Decimal`, `datetime`, `date`, `time`
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
threshold (int | float | Decimal | datetime | date | time):
|
|
121
|
+
The threshold value to compare against.
|
|
122
|
+
inclusive (bool):
|
|
123
|
+
If `True`, allows values equal to threshold (>=).
|
|
124
|
+
If `False`, requires values strictly greater (>).
|
|
125
|
+
Defaults to `False`.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError:
|
|
129
|
+
If value is not more than (or at least) the threshold.
|
|
130
|
+
TypeError:
|
|
131
|
+
If value type cannot be compared with threshold.
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
Basic numeric validation:
|
|
135
|
+
|
|
136
|
+
>>> @command()
|
|
137
|
+
>>> @option("age", type=int)
|
|
138
|
+
>>> @greater_than(18)
|
|
139
|
+
>>> def cmd(age):
|
|
140
|
+
... click.echo(f"Age: {age}")
|
|
141
|
+
|
|
142
|
+
>>> # Valid: 19, 20, 100
|
|
143
|
+
>>> # Invalid: 18, 17, 0, -5
|
|
144
|
+
|
|
145
|
+
With inclusive parameter:
|
|
146
|
+
|
|
147
|
+
>>> @command()
|
|
148
|
+
>>> @option("score", type=float)
|
|
149
|
+
>>> @greater_than(0.0, inclusive=True)
|
|
150
|
+
>>> def cmd(score):
|
|
151
|
+
... click.echo(f"Score: {score}")
|
|
152
|
+
|
|
153
|
+
>>> # Valid: 0.0, 0.1, 100.0
|
|
154
|
+
>>> # Invalid: -0.1, -1.0
|
|
155
|
+
|
|
156
|
+
Datetime validation:
|
|
157
|
+
|
|
158
|
+
>>> @command()
|
|
159
|
+
>>> @option("start_date")
|
|
160
|
+
>>> @to_date()
|
|
161
|
+
>>> @greater_than(date(2024, 1, 1), inclusive=True)
|
|
162
|
+
>>> def cmd(start_date):
|
|
163
|
+
... click.echo(f"Start: {start_date}")
|
|
164
|
+
|
|
165
|
+
>>> # Valid: 2024-01-01, 2024-12-31, 2025-01-01
|
|
166
|
+
>>> # Invalid: 2023-12-31, 2023-01-01
|
|
167
|
+
|
|
168
|
+
Multiple values (automatic handling):
|
|
169
|
+
|
|
170
|
+
>>> @command()
|
|
171
|
+
>>> @option("numbers", type=int, multiple=True)
|
|
172
|
+
>>> @greater_than(0)
|
|
173
|
+
>>> def cmd(numbers):
|
|
174
|
+
... click.echo(f"Numbers: {numbers}")
|
|
175
|
+
|
|
176
|
+
>>> # Valid: (1, 2, 3), (5, 10, 15)
|
|
177
|
+
>>> # Invalid: (1, 0, 3), (-1, 2, 3)
|
|
178
|
+
>>> # Note: Validation applies to each number individually
|
|
179
|
+
"""
|
|
180
|
+
return GreaterThan.as_decorator(
|
|
181
|
+
threshold=threshold,
|
|
182
|
+
inclusive=inclusive,
|
|
183
|
+
)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""Child decorator to validate that a value is less than a threshold."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
|
+
from decimal import Decimal
|
|
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
|
+
|
|
11
|
+
|
|
12
|
+
class LessThan(ChildNode):
|
|
13
|
+
"""Child decorator to validate that a value is less than a threshold."""
|
|
14
|
+
|
|
15
|
+
def handle_numeric(
|
|
16
|
+
self,
|
|
17
|
+
value: int | float | Decimal,
|
|
18
|
+
context: Context,
|
|
19
|
+
*args: Any,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> int | float | Decimal:
|
|
22
|
+
threshold = kwargs["threshold"]
|
|
23
|
+
inclusive = kwargs["inclusive"]
|
|
24
|
+
|
|
25
|
+
if inclusive:
|
|
26
|
+
if value > threshold:
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Value must be at most {threshold}, got {value}"
|
|
29
|
+
)
|
|
30
|
+
else:
|
|
31
|
+
if value >= threshold:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
f"Value must be less than {threshold}, got {value}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return value
|
|
37
|
+
|
|
38
|
+
def handle_datetime(
|
|
39
|
+
self,
|
|
40
|
+
value: datetime,
|
|
41
|
+
context: Context,
|
|
42
|
+
*args: Any,
|
|
43
|
+
**kwargs: Any,
|
|
44
|
+
) -> datetime:
|
|
45
|
+
threshold = kwargs["threshold"]
|
|
46
|
+
inclusive = kwargs["inclusive"]
|
|
47
|
+
|
|
48
|
+
if inclusive:
|
|
49
|
+
if value > threshold:
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"Value must be at most {threshold}, got {value}"
|
|
52
|
+
)
|
|
53
|
+
else:
|
|
54
|
+
if value >= threshold:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
f"Value must be less than {threshold}, got {value}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return value
|
|
60
|
+
|
|
61
|
+
def handle_date(
|
|
62
|
+
self,
|
|
63
|
+
value: date,
|
|
64
|
+
context: Context,
|
|
65
|
+
*args: Any,
|
|
66
|
+
**kwargs: Any,
|
|
67
|
+
) -> date:
|
|
68
|
+
threshold = kwargs["threshold"]
|
|
69
|
+
inclusive = kwargs["inclusive"]
|
|
70
|
+
|
|
71
|
+
if inclusive:
|
|
72
|
+
if value > threshold:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"Value must be at most {threshold}, got {value}"
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
if value >= threshold:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Value must be less than {threshold}, got {value}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return value
|
|
83
|
+
|
|
84
|
+
def handle_time(
|
|
85
|
+
self,
|
|
86
|
+
value: time,
|
|
87
|
+
context: Context,
|
|
88
|
+
*args: Any,
|
|
89
|
+
**kwargs: Any,
|
|
90
|
+
) -> time:
|
|
91
|
+
threshold = kwargs["threshold"]
|
|
92
|
+
inclusive = kwargs["inclusive"]
|
|
93
|
+
|
|
94
|
+
if inclusive:
|
|
95
|
+
if value > threshold:
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f"Value must be at most {threshold}, got {value}"
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
if value >= threshold:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"Value must be less than {threshold}, got {value}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return value
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def less_than(
|
|
109
|
+
threshold: int | float | Decimal | datetime | date | time,
|
|
110
|
+
inclusive: bool = False,
|
|
111
|
+
) -> Decorator:
|
|
112
|
+
"""
|
|
113
|
+
Validate that a value is less than a threshold.
|
|
114
|
+
|
|
115
|
+
Type: `ChildNode`
|
|
116
|
+
|
|
117
|
+
Supports: `int`, `float`, `Decimal`, `datetime`, `date`, `time`
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
threshold (int | float | Decimal | datetime | date | time):
|
|
121
|
+
The threshold value to compare against.
|
|
122
|
+
inclusive (bool):
|
|
123
|
+
If `True`, allows values equal to threshold (<=).
|
|
124
|
+
If `False`, requires values strictly less (<).
|
|
125
|
+
Defaults to `False`.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
ValueError:
|
|
129
|
+
If value is not less than (or at most) the threshold.
|
|
130
|
+
TypeError:
|
|
131
|
+
If value type cannot be compared with threshold.
|
|
132
|
+
|
|
133
|
+
Examples:
|
|
134
|
+
Basic numeric validation:
|
|
135
|
+
|
|
136
|
+
>>> @command()
|
|
137
|
+
>>> @option("age", type=int)
|
|
138
|
+
>>> @less_than(100)
|
|
139
|
+
>>> def cmd(age):
|
|
140
|
+
... click.echo(f"Age: {age}")
|
|
141
|
+
|
|
142
|
+
>>> # Valid: 99, 50, 0, -5
|
|
143
|
+
>>> # Invalid: 100, 101, 200
|
|
144
|
+
|
|
145
|
+
With inclusive parameter:
|
|
146
|
+
|
|
147
|
+
>>> @command()
|
|
148
|
+
>>> @option("percentage", type=float)
|
|
149
|
+
>>> @less_than(100.0, inclusive=True)
|
|
150
|
+
>>> def cmd(percentage):
|
|
151
|
+
... click.echo(f"Percentage: {percentage}")
|
|
152
|
+
|
|
153
|
+
>>> # Valid: 100.0, 99.9, 50.0, 0.0
|
|
154
|
+
>>> # Invalid: 100.1, 150.0
|
|
155
|
+
|
|
156
|
+
Datetime validation:
|
|
157
|
+
|
|
158
|
+
>>> @command()
|
|
159
|
+
>>> @option("deadline")
|
|
160
|
+
>>> @to_date()
|
|
161
|
+
>>> @less_than(date(2025, 12, 31), inclusive=True)
|
|
162
|
+
>>> def cmd(deadline):
|
|
163
|
+
... click.echo(f"Deadline: {deadline}")
|
|
164
|
+
|
|
165
|
+
>>> # Valid: 2025-12-31, 2025-01-01, 2024-12-31
|
|
166
|
+
>>> # Invalid: 2026-01-01, 2026-12-31
|
|
167
|
+
|
|
168
|
+
Multiple values (automatic handling):
|
|
169
|
+
|
|
170
|
+
>>> @command()
|
|
171
|
+
>>> @option("scores", type=int, multiple=True)
|
|
172
|
+
>>> @less_than(100, inclusive=True)
|
|
173
|
+
>>> def cmd(scores):
|
|
174
|
+
... click.echo(f"Scores: {scores}")
|
|
175
|
+
|
|
176
|
+
>>> # Valid: (90, 85, 100), (50, 60, 70)
|
|
177
|
+
>>> # Invalid: (90, 101, 85), (100, 100, 105)
|
|
178
|
+
>>> # Note: Validation applies to each score individually
|
|
179
|
+
"""
|
|
180
|
+
return LessThan.as_decorator(
|
|
181
|
+
threshold=threshold,
|
|
182
|
+
inclusive=inclusive,
|
|
183
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Initialization file for the `click_extended.decorators.convert` module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.decorators.convert.convert_angle import convert_angle
|
|
4
|
+
from click_extended.decorators.convert.convert_area import convert_area
|
|
5
|
+
from click_extended.decorators.convert.convert_bits import convert_bits
|
|
6
|
+
from click_extended.decorators.convert.convert_distance import convert_distance
|
|
7
|
+
from click_extended.decorators.convert.convert_energy import convert_energy
|
|
8
|
+
from click_extended.decorators.convert.convert_power import convert_power
|
|
9
|
+
from click_extended.decorators.convert.convert_pressure import convert_pressure
|
|
10
|
+
from click_extended.decorators.convert.convert_speed import convert_speed
|
|
11
|
+
from click_extended.decorators.convert.convert_temperature import (
|
|
12
|
+
convert_temperature,
|
|
13
|
+
)
|
|
14
|
+
from click_extended.decorators.convert.convert_time import convert_time
|
|
15
|
+
from click_extended.decorators.convert.convert_volume import convert_volume
|
|
16
|
+
from click_extended.decorators.convert.convert_weight import convert_weight
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"convert_angle",
|
|
20
|
+
"convert_area",
|
|
21
|
+
"convert_bits",
|
|
22
|
+
"convert_distance",
|
|
23
|
+
"convert_energy",
|
|
24
|
+
"convert_power",
|
|
25
|
+
"convert_pressure",
|
|
26
|
+
"convert_time",
|
|
27
|
+
"convert_speed",
|
|
28
|
+
"convert_temperature",
|
|
29
|
+
"convert_volume",
|
|
30
|
+
"convert_weight",
|
|
31
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Convert between different angle units."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from decimal import Decimal, getcontext
|
|
5
|
+
from typing import Any, Literal
|
|
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
|
+
|
|
11
|
+
getcontext().prec = 35
|
|
12
|
+
|
|
13
|
+
PI = Decimal(math.pi)
|
|
14
|
+
|
|
15
|
+
UNITS = {
|
|
16
|
+
"deg": Decimal("1"),
|
|
17
|
+
"rad": Decimal("180") / PI,
|
|
18
|
+
"grad": Decimal("0.9"),
|
|
19
|
+
"turn": Decimal("360"),
|
|
20
|
+
"rev": Decimal("360"),
|
|
21
|
+
"arcmin": Decimal("1") / Decimal("60"),
|
|
22
|
+
"arcsec": Decimal("1") / Decimal("3600"),
|
|
23
|
+
"mil": Decimal("0.05625"),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ConvertAngle(ChildNode):
|
|
28
|
+
"""Convert between different angle units."""
|
|
29
|
+
|
|
30
|
+
def handle_numeric(
|
|
31
|
+
self,
|
|
32
|
+
value: int | float,
|
|
33
|
+
context: Context,
|
|
34
|
+
*args: Any,
|
|
35
|
+
**kwargs: Any,
|
|
36
|
+
) -> float:
|
|
37
|
+
from_unit = kwargs["from_unit"]
|
|
38
|
+
to_unit = kwargs["to_unit"]
|
|
39
|
+
val = Decimal(str(value))
|
|
40
|
+
|
|
41
|
+
if from_unit not in UNITS:
|
|
42
|
+
raise ValueError(f"Unknown unit '{from_unit}'")
|
|
43
|
+
if to_unit not in UNITS:
|
|
44
|
+
raise ValueError(f"Unknown unit '{to_unit}'")
|
|
45
|
+
|
|
46
|
+
degrees = val * UNITS[from_unit]
|
|
47
|
+
|
|
48
|
+
return float(degrees / UNITS[to_unit])
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def convert_angle(
|
|
52
|
+
from_unit: Literal[
|
|
53
|
+
"deg",
|
|
54
|
+
"rad",
|
|
55
|
+
"grad",
|
|
56
|
+
"turn",
|
|
57
|
+
"arcmin",
|
|
58
|
+
"arcsec",
|
|
59
|
+
"rev",
|
|
60
|
+
"mil",
|
|
61
|
+
],
|
|
62
|
+
to_unit: Literal[
|
|
63
|
+
"deg",
|
|
64
|
+
"rad",
|
|
65
|
+
"grad",
|
|
66
|
+
"turn",
|
|
67
|
+
"arcmin",
|
|
68
|
+
"arcsec",
|
|
69
|
+
"rev",
|
|
70
|
+
"mil",
|
|
71
|
+
],
|
|
72
|
+
) -> Decorator:
|
|
73
|
+
"""
|
|
74
|
+
Convert between different angle units.
|
|
75
|
+
|
|
76
|
+
Type: `ChildNode`
|
|
77
|
+
|
|
78
|
+
Supports: `int`, `float`
|
|
79
|
+
|
|
80
|
+
Units:
|
|
81
|
+
- **deg**: Degree
|
|
82
|
+
- **rad**: Radian
|
|
83
|
+
- **grad**: Gradian
|
|
84
|
+
- **turn**: Full turn
|
|
85
|
+
- **arcmin**: Arcminute
|
|
86
|
+
- **arcsec**: Arcsecond
|
|
87
|
+
- **rev**: Revolution
|
|
88
|
+
- **mil**: NATO angular mil
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Decorator:
|
|
92
|
+
The decorated function.
|
|
93
|
+
"""
|
|
94
|
+
return ConvertAngle.as_decorator(from_unit=from_unit, to_unit=to_unit)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Convert between different area units."""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal, getcontext
|
|
4
|
+
from typing import Any, Literal
|
|
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
|
+
getcontext().prec = 35
|
|
11
|
+
|
|
12
|
+
UNITS = {
|
|
13
|
+
"mm2": Decimal("1e-6"),
|
|
14
|
+
"cm2": Decimal("1e-4"),
|
|
15
|
+
"dm2": Decimal("1e-2"),
|
|
16
|
+
"m2": Decimal("1"),
|
|
17
|
+
"a": Decimal("100"),
|
|
18
|
+
"ha": Decimal("10000"),
|
|
19
|
+
"km2": Decimal("1e6"),
|
|
20
|
+
"in2": Decimal("0.00064516"),
|
|
21
|
+
"ft2": Decimal("0.09290304"),
|
|
22
|
+
"yd2": Decimal("0.83612736"),
|
|
23
|
+
"mi2": Decimal("2589988.110336"),
|
|
24
|
+
"acre": Decimal("4046.8564224"),
|
|
25
|
+
"rood": Decimal("1011.7141056"),
|
|
26
|
+
"perch": Decimal("25.29285264"),
|
|
27
|
+
"ang2": Decimal("1e-20"),
|
|
28
|
+
"tunnland": Decimal("4936.4"),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ConvertArea(ChildNode):
|
|
33
|
+
"""Convert between different area units."""
|
|
34
|
+
|
|
35
|
+
def handle_numeric(
|
|
36
|
+
self,
|
|
37
|
+
value: int | float,
|
|
38
|
+
context: Context,
|
|
39
|
+
*args: Any,
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> float:
|
|
42
|
+
from_unit = kwargs["from_unit"]
|
|
43
|
+
to_unit = kwargs["to_unit"]
|
|
44
|
+
val = Decimal(str(value))
|
|
45
|
+
|
|
46
|
+
if from_unit not in UNITS:
|
|
47
|
+
raise ValueError(f"Unknown unit '{from_unit}'")
|
|
48
|
+
if to_unit not in UNITS:
|
|
49
|
+
raise ValueError(f"Unknown unit '{to_unit}'")
|
|
50
|
+
|
|
51
|
+
m2 = val * UNITS[from_unit]
|
|
52
|
+
|
|
53
|
+
return float(m2 / UNITS[to_unit])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def convert_area(
|
|
57
|
+
from_unit: Literal[
|
|
58
|
+
"mm2",
|
|
59
|
+
"cm2",
|
|
60
|
+
"dm2",
|
|
61
|
+
"m2",
|
|
62
|
+
"a",
|
|
63
|
+
"ha",
|
|
64
|
+
"km2",
|
|
65
|
+
"in2",
|
|
66
|
+
"ft2",
|
|
67
|
+
"yd2",
|
|
68
|
+
"mi2",
|
|
69
|
+
"acre",
|
|
70
|
+
"rood",
|
|
71
|
+
"perch",
|
|
72
|
+
"ang2",
|
|
73
|
+
"tunnland",
|
|
74
|
+
],
|
|
75
|
+
to_unit: Literal[
|
|
76
|
+
"mm2",
|
|
77
|
+
"cm2",
|
|
78
|
+
"dm2",
|
|
79
|
+
"m2",
|
|
80
|
+
"a",
|
|
81
|
+
"ha",
|
|
82
|
+
"km2",
|
|
83
|
+
"in2",
|
|
84
|
+
"ft2",
|
|
85
|
+
"yd2",
|
|
86
|
+
"mi2",
|
|
87
|
+
"acre",
|
|
88
|
+
"rood",
|
|
89
|
+
"perch",
|
|
90
|
+
"ang2",
|
|
91
|
+
"tunnland",
|
|
92
|
+
],
|
|
93
|
+
) -> Decorator:
|
|
94
|
+
"""
|
|
95
|
+
Convert between different area units.
|
|
96
|
+
|
|
97
|
+
Type: `ChildNode`
|
|
98
|
+
|
|
99
|
+
Supports: `int`, `float`.
|
|
100
|
+
|
|
101
|
+
Units:
|
|
102
|
+
- **mm2**: Square millimeter
|
|
103
|
+
- **cm2**: Square centimeter
|
|
104
|
+
- **dm2**: Square decimeter
|
|
105
|
+
- **m2**: Square meter
|
|
106
|
+
- **a**: Are (100 m2)
|
|
107
|
+
- **ha**: Hectare / hektar (10,000 m2)
|
|
108
|
+
- **km2**: Square kilometer
|
|
109
|
+
- **in2**: Square inch
|
|
110
|
+
- **ft2**: Square foot
|
|
111
|
+
- **yd2**: Square yard
|
|
112
|
+
- **mi2**: Square mile
|
|
113
|
+
- **acre**: Acre (43,560 ft2)
|
|
114
|
+
- **rood**: Rood (1/4 acre)
|
|
115
|
+
- **perch**: Perch (1/160 acre)
|
|
116
|
+
- **ang2**: Square ångström
|
|
117
|
+
- **tunnland**: Historical Swedish area unit (~4,937 m2)
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Decorator:
|
|
121
|
+
The decorated function.
|
|
122
|
+
"""
|
|
123
|
+
return ConvertArea.as_decorator(from_unit=from_unit, to_unit=to_unit)
|