click-extended 1.0.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.
- 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/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.1.dist-info}/METADATA +2 -1
- click_extended-1.0.1.dist-info/RECORD +149 -0
- click_extended-1.0.0.dist-info/RECORD +0 -10
- {click_extended-1.0.0.dist-info → click_extended-1.0.1.dist-info}/WHEEL +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.1.dist-info}/licenses/AUTHORS.md +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {click_extended-1.0.0.dist-info → click_extended-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Check if a value is a valid hostname."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
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
|
+
HOSTNAME_PATTERN = re.compile(r"(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IsHostname(ChildNode):
|
|
14
|
+
"""Check if a value is a valid hostname."""
|
|
15
|
+
|
|
16
|
+
def handle_str(
|
|
17
|
+
self,
|
|
18
|
+
value: str,
|
|
19
|
+
context: Context,
|
|
20
|
+
*args: Any,
|
|
21
|
+
**kwargs: Any,
|
|
22
|
+
) -> Any:
|
|
23
|
+
if len(value) > 255:
|
|
24
|
+
raise ValueError(f"Value '{value}' is too long to be a hostname.")
|
|
25
|
+
|
|
26
|
+
if value[-1] == ".":
|
|
27
|
+
value = value[:-1]
|
|
28
|
+
|
|
29
|
+
if not all(HOSTNAME_PATTERN.match(x) for x in value.split(".")):
|
|
30
|
+
raise ValueError(f"Value '{value}' is not a valid hostname.")
|
|
31
|
+
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_hostname() -> Decorator:
|
|
36
|
+
"""
|
|
37
|
+
Check if a value is a valid hostname.
|
|
38
|
+
|
|
39
|
+
Type: `ChildNode`
|
|
40
|
+
|
|
41
|
+
Supports: `str`
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Decorator:
|
|
45
|
+
The decorated function.
|
|
46
|
+
"""
|
|
47
|
+
return IsHostname.as_decorator()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Check if a value is a valid IPv4 address."""
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
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 IsIpv4(ChildNode):
|
|
12
|
+
"""Check if a value is a valid IPv4 address."""
|
|
13
|
+
|
|
14
|
+
def handle_str(
|
|
15
|
+
self,
|
|
16
|
+
value: str,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
try:
|
|
22
|
+
ip = ipaddress.ip_address(value)
|
|
23
|
+
if not isinstance(ip, ipaddress.IPv4Address):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Value '{value}' is not a valid IPv4 address."
|
|
26
|
+
)
|
|
27
|
+
except ValueError as e:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Value '{value}' is not a valid IPv4 address."
|
|
30
|
+
) from e
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_ipv4() -> Decorator:
|
|
35
|
+
"""
|
|
36
|
+
Check if a value is a valid IPv4 address.
|
|
37
|
+
|
|
38
|
+
Type: `ChildNode`
|
|
39
|
+
|
|
40
|
+
Supports: `str`
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Decorator:
|
|
44
|
+
The decorated function.
|
|
45
|
+
"""
|
|
46
|
+
return IsIpv4.as_decorator()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Check if a value is a valid IPv6 address."""
|
|
2
|
+
|
|
3
|
+
import ipaddress
|
|
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 IsIpv6(ChildNode):
|
|
12
|
+
"""Check if a value is a valid IPv6 address."""
|
|
13
|
+
|
|
14
|
+
def handle_str(
|
|
15
|
+
self,
|
|
16
|
+
value: str,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
try:
|
|
22
|
+
ip = ipaddress.ip_address(value)
|
|
23
|
+
if not isinstance(ip, ipaddress.IPv6Address):
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Value '{value}' is not a valid IPv6 address."
|
|
26
|
+
)
|
|
27
|
+
except ValueError as e:
|
|
28
|
+
raise ValueError(
|
|
29
|
+
f"Value '{value}' is not a valid IPv6 address."
|
|
30
|
+
) from e
|
|
31
|
+
return value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_ipv6() -> Decorator:
|
|
35
|
+
"""
|
|
36
|
+
Check if a value is a valid IPv6 address.
|
|
37
|
+
|
|
38
|
+
Type: `ChildNode`
|
|
39
|
+
|
|
40
|
+
Supports: `str`
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Decorator:
|
|
44
|
+
The decorated function.
|
|
45
|
+
"""
|
|
46
|
+
return IsIpv6.as_decorator()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Check if a value is valid JSON."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
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 IsJson(ChildNode):
|
|
12
|
+
"""Check if a value is valid JSON."""
|
|
13
|
+
|
|
14
|
+
def handle_str(
|
|
15
|
+
self,
|
|
16
|
+
value: str,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
try:
|
|
22
|
+
json.loads(value)
|
|
23
|
+
except json.JSONDecodeError as e:
|
|
24
|
+
raise ValueError(f"Value '{value}' is not valid JSON.") from e
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_json() -> Decorator:
|
|
29
|
+
"""
|
|
30
|
+
Check if a value is valid JSON.
|
|
31
|
+
|
|
32
|
+
Type: `ChildNode`
|
|
33
|
+
|
|
34
|
+
Supports: `str`
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Decorator:
|
|
38
|
+
The decorated function.
|
|
39
|
+
"""
|
|
40
|
+
return IsJson.as_decorator()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Check if a value is a valid MAC address."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
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
|
+
MAC_PATTERN = r"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})|([0-9a-fA-F]{4}\.[0-9a-fA-F]{4}\.[0-9a-fA-F]{4})$" # pylint: disable=line-too-long
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IsMacAddress(ChildNode):
|
|
14
|
+
"""Check if a value is a valid MAC address."""
|
|
15
|
+
|
|
16
|
+
def handle_str(
|
|
17
|
+
self,
|
|
18
|
+
value: str,
|
|
19
|
+
context: Context,
|
|
20
|
+
*args: Any,
|
|
21
|
+
**kwargs: Any,
|
|
22
|
+
) -> Any:
|
|
23
|
+
if not re.fullmatch(MAC_PATTERN, value):
|
|
24
|
+
raise ValueError(f"Value '{value}' is not a valid MAC address.")
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_mac_address() -> Decorator:
|
|
29
|
+
"""
|
|
30
|
+
Check if a value is a valid MAC address.
|
|
31
|
+
|
|
32
|
+
Type: `ChildNode`
|
|
33
|
+
|
|
34
|
+
Supports: `str`
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Decorator:
|
|
38
|
+
The decorated function.
|
|
39
|
+
"""
|
|
40
|
+
return IsMacAddress.as_decorator()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Check if a value is negative."""
|
|
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 IsNegative(ChildNode):
|
|
11
|
+
"""Check if a value is negative."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
if value >= 0:
|
|
21
|
+
raise ValueError(f"Value '{value}' is not negative.")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_negative() -> Decorator:
|
|
26
|
+
"""
|
|
27
|
+
Check if a value is negative.
|
|
28
|
+
|
|
29
|
+
Type: `ChildNode`
|
|
30
|
+
|
|
31
|
+
Supports: `int`, `float`
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Decorator:
|
|
35
|
+
The decorated function.
|
|
36
|
+
"""
|
|
37
|
+
return IsNegative.as_decorator()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Check if a value is not zero."""
|
|
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 IsNonZero(ChildNode):
|
|
11
|
+
"""Check if a value is not zero."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
if value == 0:
|
|
21
|
+
raise ValueError(f"Value '{value}' is zero.")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_non_zero() -> Decorator:
|
|
26
|
+
"""
|
|
27
|
+
Check if a value is not zero.
|
|
28
|
+
|
|
29
|
+
Type: `ChildNode`
|
|
30
|
+
|
|
31
|
+
Supports: `int`, `float`
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Decorator:
|
|
35
|
+
The decorated function.
|
|
36
|
+
"""
|
|
37
|
+
return IsNonZero.as_decorator()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Check if a value is a valid network port."""
|
|
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 IsPort(ChildNode):
|
|
11
|
+
"""Check if a value is a valid network port."""
|
|
12
|
+
|
|
13
|
+
def handle_int(
|
|
14
|
+
self,
|
|
15
|
+
value: int,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
if not 1 <= value <= 65535:
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"Value '{value}' is not a valid port number (1-65535)."
|
|
23
|
+
)
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def is_port() -> Decorator:
|
|
28
|
+
"""
|
|
29
|
+
Check if a value is a valid network port.
|
|
30
|
+
|
|
31
|
+
Type: `ChildNode`
|
|
32
|
+
|
|
33
|
+
Supports: `int`
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Decorator:
|
|
37
|
+
The decorated function.
|
|
38
|
+
"""
|
|
39
|
+
return IsPort.as_decorator()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Check if a value is positive."""
|
|
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 IsPositive(ChildNode):
|
|
11
|
+
"""Check if a value is positive."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
if value <= 0:
|
|
21
|
+
raise ValueError(f"Value '{value}' is not positive.")
|
|
22
|
+
return value
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_positive() -> Decorator:
|
|
26
|
+
"""
|
|
27
|
+
Check if a value is positive.
|
|
28
|
+
|
|
29
|
+
Type: `ChildNode`
|
|
30
|
+
|
|
31
|
+
Supports: `int`, `float`
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Decorator:
|
|
35
|
+
The decorated function.
|
|
36
|
+
"""
|
|
37
|
+
return IsPositive.as_decorator()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Check if a value is a valid URL."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import urlparse
|
|
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
|
+
from click_extended.utils.humanize import humanize_iterable
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class IsUrl(ChildNode):
|
|
13
|
+
"""Check if a value is a valid URL."""
|
|
14
|
+
|
|
15
|
+
def handle_str(
|
|
16
|
+
self,
|
|
17
|
+
value: str,
|
|
18
|
+
context: Context,
|
|
19
|
+
*args: Any,
|
|
20
|
+
**kwargs: Any,
|
|
21
|
+
) -> Any:
|
|
22
|
+
schemes = kwargs.get("schemes")
|
|
23
|
+
require_tld = kwargs.get("require_tld", True)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
result = urlparse(value)
|
|
27
|
+
if not all([result.scheme, result.netloc]):
|
|
28
|
+
raise ValueError(f"Value '{value}' is not a valid URL.")
|
|
29
|
+
|
|
30
|
+
if schemes and result.scheme not in schemes:
|
|
31
|
+
humanized_schemes = humanize_iterable(
|
|
32
|
+
schemes,
|
|
33
|
+
wrap="'",
|
|
34
|
+
prefix_singular="scheme is",
|
|
35
|
+
prefix_plural="shemes are",
|
|
36
|
+
)
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"URL scheme '{result.scheme}' is not allowed. "
|
|
39
|
+
f"Allowed {humanized_schemes}"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
if require_tld and "." not in result.netloc:
|
|
43
|
+
raise ValueError(f"URL '{value}' does not have a valid TLD.")
|
|
44
|
+
|
|
45
|
+
except ValueError as e:
|
|
46
|
+
raise e
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise ValueError(f"Value '{value}' is not a valid URL.") from e
|
|
49
|
+
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_url(
|
|
54
|
+
schemes: list[str] | None = None,
|
|
55
|
+
require_tld: bool = True,
|
|
56
|
+
) -> Decorator:
|
|
57
|
+
"""
|
|
58
|
+
Check if a value is a valid URL.
|
|
59
|
+
|
|
60
|
+
Type: `ChildNode`
|
|
61
|
+
|
|
62
|
+
Supports: `str`
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
schemes (list[str] | None, optional):
|
|
66
|
+
List of allowed URL schemes (e.g., ["http", "https"]).
|
|
67
|
+
Defaults to `None` (all schemes allowed).
|
|
68
|
+
require_tld (bool, optional):
|
|
69
|
+
Whether to require a Top-Level Domain (TLD). Defaults to `True`.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Decorator:
|
|
73
|
+
The decorated function.
|
|
74
|
+
"""
|
|
75
|
+
return IsUrl.as_decorator(schemes=schemes, require_tld=require_tld)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Check if a value is a valid UUID."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
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 IsUuid(ChildNode):
|
|
12
|
+
"""Check if a value is a valid UUID."""
|
|
13
|
+
|
|
14
|
+
def handle_str(
|
|
15
|
+
self,
|
|
16
|
+
value: str,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
try:
|
|
22
|
+
uuid.UUID(value)
|
|
23
|
+
except ValueError as e:
|
|
24
|
+
raise ValueError(f"Value '{value}' is not a valid UUID.") from e
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_uuid() -> Decorator:
|
|
29
|
+
"""
|
|
30
|
+
Check if a value is a valid UUID.
|
|
31
|
+
|
|
32
|
+
Type: `ChildNode`
|
|
33
|
+
|
|
34
|
+
Supports: `str`
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Decorator:
|
|
38
|
+
The decorated function.
|
|
39
|
+
"""
|
|
40
|
+
return IsUuid.as_decorator()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Child decorator to check if a string is within length bounds."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=redefined-builtin
|
|
4
|
+
|
|
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 Length(ChildNode):
|
|
13
|
+
"""Child decorator to check if a string is within length bounds."""
|
|
14
|
+
|
|
15
|
+
def handle_str(
|
|
16
|
+
self, value: str, context: Context, *args: Any, **kwargs: Any
|
|
17
|
+
) -> Any:
|
|
18
|
+
min_len: int | None = kwargs["min"]
|
|
19
|
+
max_len: int | None = kwargs["max"]
|
|
20
|
+
|
|
21
|
+
if min_len is not None and len(value) < min_len:
|
|
22
|
+
s = "" if min_len == 1 else "s"
|
|
23
|
+
raise ValueError(
|
|
24
|
+
f"Value is too short, must be at least {min_len} character{s}."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if max_len is not None and len(value) > max_len:
|
|
28
|
+
s = "" if max_len == 1 else "s"
|
|
29
|
+
raise ValueError(
|
|
30
|
+
f"Value is too long, must be at most {max_len} character{s}."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
return value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def length(min: int | None = None, max: int | None = None) -> Decorator:
|
|
37
|
+
"""
|
|
38
|
+
Check if a string is within length bounds.
|
|
39
|
+
|
|
40
|
+
Type: `ChildNode`
|
|
41
|
+
|
|
42
|
+
Supports: `str`
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
min (int | None):
|
|
46
|
+
Minimum length or `None` for no minimum.
|
|
47
|
+
max (int | None):
|
|
48
|
+
Maximum length or `None` for no maximum.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Decorator:
|
|
52
|
+
The decorated function.
|
|
53
|
+
|
|
54
|
+
Raises:
|
|
55
|
+
ValueError:
|
|
56
|
+
If neither min nor max is specified.
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
```python
|
|
60
|
+
@length(min=5, max=20) # Between 5-20 characters
|
|
61
|
+
@length(min=5) # At least 5 characters
|
|
62
|
+
@length(max=20) # At most 20 characters
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
if min is None and max is None:
|
|
66
|
+
raise ValueError("At least one of 'min' or 'max' must be specified")
|
|
67
|
+
|
|
68
|
+
return Length.as_decorator(min=min, max=max)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Child decorator to check if a string is not empty."""
|
|
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 NotEmpty(ChildNode):
|
|
11
|
+
"""Child decorator to check if a string is not empty."""
|
|
12
|
+
|
|
13
|
+
def handle_str(
|
|
14
|
+
self, value: str, context: Context, *args: Any, **kwargs: Any
|
|
15
|
+
) -> Any:
|
|
16
|
+
if not value or value.isspace():
|
|
17
|
+
raise ValueError(
|
|
18
|
+
"Value cannot be empty or contain only whitespace."
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return value
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def not_empty() -> Decorator:
|
|
25
|
+
"""
|
|
26
|
+
Check if a string is not empty.
|
|
27
|
+
|
|
28
|
+
Type: `ChildNode`
|
|
29
|
+
|
|
30
|
+
Supports: `str`
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError:
|
|
34
|
+
If the value is empty or contains only whitespace.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Decorator:
|
|
38
|
+
The decorator function.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
```python
|
|
42
|
+
@command()
|
|
43
|
+
@option("name")
|
|
44
|
+
@not_empty()
|
|
45
|
+
def greet(name: str) -> None:
|
|
46
|
+
click.echo(f"Hello, {name}!")
|
|
47
|
+
```
|
|
48
|
+
"""
|
|
49
|
+
return NotEmpty.as_decorator()
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Check if a value matches a regex pattern."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
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 Regex(ChildNode):
|
|
12
|
+
"""Check if a value matches a regex pattern."""
|
|
13
|
+
|
|
14
|
+
def handle_str(
|
|
15
|
+
self,
|
|
16
|
+
value: str,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
patterns = kwargs["patterns"]
|
|
22
|
+
for pattern in patterns:
|
|
23
|
+
if re.fullmatch(pattern, value):
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Value '{value}' does not match any of the patterns: {patterns}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def regex(*patterns: str) -> Decorator:
|
|
32
|
+
"""
|
|
33
|
+
Check if a value matches a regex pattern.
|
|
34
|
+
|
|
35
|
+
Type: `ChildNode`
|
|
36
|
+
|
|
37
|
+
Supports: `str`
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
*patterns (str):
|
|
41
|
+
The regex patterns to check against.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Decorator:
|
|
45
|
+
The decorated function.
|
|
46
|
+
"""
|
|
47
|
+
return Regex.as_decorator(patterns=patterns)
|