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.
- click_extended/__init__.py +10 -6
- click_extended/classes.py +12 -8
- 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/types.py +1 -1
- 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-0.4.0.dist-info → click_extended-1.0.1.dist-info}/METADATA +100 -29
- click_extended-1.0.1.dist-info/RECORD +149 -0
- click_extended-0.4.0.dist-info/RECORD +0 -10
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/WHEEL +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/AUTHORS.md +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {click_extended-0.4.0.dist-info → click_extended-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Convert between various weight 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 = 28
|
|
11
|
+
|
|
12
|
+
MASS_UNITS = {
|
|
13
|
+
"ug": Decimal("1e-9"),
|
|
14
|
+
"mg": Decimal("1e-6"),
|
|
15
|
+
"g": Decimal("1e-3"),
|
|
16
|
+
"kg": Decimal("1"),
|
|
17
|
+
"t": Decimal("1000"),
|
|
18
|
+
"lb": Decimal("0.45359237"),
|
|
19
|
+
"oz": Decimal("0.028349523125"),
|
|
20
|
+
"st": Decimal("6.35029318"),
|
|
21
|
+
"ct": Decimal("0.0002"),
|
|
22
|
+
"amu": Decimal("1.66053906660e-27"),
|
|
23
|
+
"slg": Decimal("14.593903"),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
FORCE_UNITS = {
|
|
27
|
+
"n": Decimal("1"),
|
|
28
|
+
"kn": Decimal("1000"),
|
|
29
|
+
"lbf": Decimal("4.4482216152605"),
|
|
30
|
+
"ozf": Decimal("0.27801385095378125"),
|
|
31
|
+
"kgf": Decimal("9.80665"),
|
|
32
|
+
"dyn": Decimal("1e-5"),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ConvertWeight(ChildNode):
|
|
37
|
+
"""Convert between various weight units."""
|
|
38
|
+
|
|
39
|
+
def handle_numeric(
|
|
40
|
+
self,
|
|
41
|
+
value: int | float,
|
|
42
|
+
context: Context,
|
|
43
|
+
*args: Any,
|
|
44
|
+
**kwargs: Any,
|
|
45
|
+
) -> float:
|
|
46
|
+
from_unit = kwargs["from_unit"]
|
|
47
|
+
to_unit = kwargs["to_unit"]
|
|
48
|
+
gravity = Decimal(str(kwargs["gravity"]))
|
|
49
|
+
val = Decimal(str(value))
|
|
50
|
+
|
|
51
|
+
if from_unit in MASS_UNITS:
|
|
52
|
+
base_value = val * MASS_UNITS[from_unit]
|
|
53
|
+
is_mass = True
|
|
54
|
+
elif from_unit in FORCE_UNITS:
|
|
55
|
+
base_value = val * FORCE_UNITS[from_unit]
|
|
56
|
+
is_mass = False
|
|
57
|
+
else:
|
|
58
|
+
raise ValueError(f"Unknown unit '{from_unit}'")
|
|
59
|
+
|
|
60
|
+
if is_mass and to_unit in FORCE_UNITS:
|
|
61
|
+
base_value = base_value * gravity
|
|
62
|
+
elif not is_mass and to_unit in MASS_UNITS:
|
|
63
|
+
base_value = base_value / gravity
|
|
64
|
+
|
|
65
|
+
if to_unit in MASS_UNITS:
|
|
66
|
+
result = base_value / MASS_UNITS[to_unit]
|
|
67
|
+
elif to_unit in FORCE_UNITS:
|
|
68
|
+
result = base_value / FORCE_UNITS[to_unit]
|
|
69
|
+
else:
|
|
70
|
+
raise ValueError(f"Unknown unit '{to_unit}'")
|
|
71
|
+
|
|
72
|
+
return float(result)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def convert_weight(
|
|
76
|
+
from_unit: Literal[
|
|
77
|
+
"ug",
|
|
78
|
+
"mg",
|
|
79
|
+
"g",
|
|
80
|
+
"kg",
|
|
81
|
+
"t",
|
|
82
|
+
"lb",
|
|
83
|
+
"oz",
|
|
84
|
+
"st",
|
|
85
|
+
"n",
|
|
86
|
+
"kn",
|
|
87
|
+
"ct",
|
|
88
|
+
"amu",
|
|
89
|
+
"lbf",
|
|
90
|
+
"ozf",
|
|
91
|
+
"kgf",
|
|
92
|
+
"dyn",
|
|
93
|
+
"slg",
|
|
94
|
+
],
|
|
95
|
+
to_unit: Literal[
|
|
96
|
+
"ug",
|
|
97
|
+
"mg",
|
|
98
|
+
"g",
|
|
99
|
+
"kg",
|
|
100
|
+
"t",
|
|
101
|
+
"lb",
|
|
102
|
+
"oz",
|
|
103
|
+
"st",
|
|
104
|
+
"n",
|
|
105
|
+
"kn",
|
|
106
|
+
"ct",
|
|
107
|
+
"amu",
|
|
108
|
+
"lbf",
|
|
109
|
+
"ozf",
|
|
110
|
+
"kgf",
|
|
111
|
+
"dyn",
|
|
112
|
+
"slg",
|
|
113
|
+
],
|
|
114
|
+
gravity: float = 9.80665,
|
|
115
|
+
) -> Decorator:
|
|
116
|
+
"""
|
|
117
|
+
Convert between various weight units.
|
|
118
|
+
|
|
119
|
+
Type: `ChildNode`
|
|
120
|
+
|
|
121
|
+
Supports: `int`, `float`
|
|
122
|
+
|
|
123
|
+
Units:
|
|
124
|
+
- **ug**: Micrograms (mass)
|
|
125
|
+
- **mg**: Milligrams (mass)
|
|
126
|
+
- **g**: Grams (mass)
|
|
127
|
+
- **kg**: Kilograms (mass)
|
|
128
|
+
- **t**: Metric tonne (mass)
|
|
129
|
+
- **lb**: Pound (mass)
|
|
130
|
+
- **oz**: Ounce (mass)
|
|
131
|
+
- **st**: Stone (mass)
|
|
132
|
+
- **n**: Newton (force)
|
|
133
|
+
- **kn**: Kilonewton (force)
|
|
134
|
+
- **ct**: Carat (mass)
|
|
135
|
+
- **amu**: Atomic mass unit (mass)
|
|
136
|
+
- **lbf**: Pound-force (force)
|
|
137
|
+
- **ozf**: Ounce-force (force)
|
|
138
|
+
- **kgf**: Kilogram-force (force)
|
|
139
|
+
- **dyn**: Dyne (force)
|
|
140
|
+
- **slg**: Slug (mass)
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
from_unit (str):
|
|
144
|
+
The unit to convert from.
|
|
145
|
+
to_unit (str):
|
|
146
|
+
The unit to convert to.
|
|
147
|
+
gravity (float, optional):
|
|
148
|
+
The gravity constant, used for force units. Defaults to `9.80665`.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Decorator:
|
|
152
|
+
The decorated function.
|
|
153
|
+
"""
|
|
154
|
+
return ConvertWeight.as_decorator(
|
|
155
|
+
from_unit=from_unit,
|
|
156
|
+
to_unit=to_unit,
|
|
157
|
+
gravity=gravity,
|
|
158
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Initialization file for the `click_extended.decorators.load` module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.decorators.load.load_csv import load_csv
|
|
4
|
+
from click_extended.decorators.load.load_json import load_json
|
|
5
|
+
from click_extended.decorators.load.load_toml import load_toml
|
|
6
|
+
from click_extended.decorators.load.load_yaml import load_yaml
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"load_csv",
|
|
10
|
+
"load_json",
|
|
11
|
+
"load_toml",
|
|
12
|
+
"load_yaml",
|
|
13
|
+
]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Child decorator to load the contents of a CSV file."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-many-locals
|
|
4
|
+
# pylint: disable=too-many-arguments
|
|
5
|
+
# pylint: disable=too-many-positional-arguments
|
|
6
|
+
|
|
7
|
+
import csv
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
12
|
+
from click_extended.core.other.context import Context
|
|
13
|
+
from click_extended.types import Decorator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LoadCsv(ChildNode):
|
|
17
|
+
"""Child decorator to load the contents of a CSV file."""
|
|
18
|
+
|
|
19
|
+
def handle_path(
|
|
20
|
+
self, value: Path, context: Context, *args: Any, **kwargs: Any
|
|
21
|
+
) -> list[dict[str, str]] | list[list[str]]:
|
|
22
|
+
dialect = kwargs["dialect"]
|
|
23
|
+
delimiter = kwargs["delimiter"]
|
|
24
|
+
has_header = kwargs["has_header"]
|
|
25
|
+
as_dict = kwargs["as_dict"]
|
|
26
|
+
encoding = kwargs["encoding"]
|
|
27
|
+
skip_empty = kwargs["skip_empty"]
|
|
28
|
+
|
|
29
|
+
if value.is_dir():
|
|
30
|
+
raise IsADirectoryError(
|
|
31
|
+
f"Path '{value.absolute()}' is a directory, but must be a file."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
with value.open("r", encoding=encoding, newline="") as f:
|
|
35
|
+
reader_kwargs: dict[str, Any] = {}
|
|
36
|
+
if dialect:
|
|
37
|
+
reader_kwargs["dialect"] = dialect
|
|
38
|
+
if delimiter:
|
|
39
|
+
reader_kwargs["delimiter"] = delimiter
|
|
40
|
+
|
|
41
|
+
if as_dict:
|
|
42
|
+
reader = csv.DictReader(f, **reader_kwargs)
|
|
43
|
+
rows: list[dict[str, str]] = []
|
|
44
|
+
for row_dict in reader:
|
|
45
|
+
if skip_empty and not any(row_dict.values()):
|
|
46
|
+
continue
|
|
47
|
+
rows.append(row_dict)
|
|
48
|
+
return rows
|
|
49
|
+
|
|
50
|
+
reader_list = csv.reader(f, **reader_kwargs)
|
|
51
|
+
rows_list: list[list[str]] = []
|
|
52
|
+
|
|
53
|
+
if has_header:
|
|
54
|
+
next(reader_list, None) # Skip header row
|
|
55
|
+
|
|
56
|
+
for row_list in reader_list:
|
|
57
|
+
if skip_empty and not any(row_list):
|
|
58
|
+
continue
|
|
59
|
+
rows_list.append(row_list)
|
|
60
|
+
return rows_list
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def load_csv(
|
|
64
|
+
dialect: Literal["excel", "excel-tab", "unix"] | None = None,
|
|
65
|
+
delimiter: str | None = None,
|
|
66
|
+
has_header: bool = True,
|
|
67
|
+
as_dict: bool = True,
|
|
68
|
+
encoding: str = "utf-8",
|
|
69
|
+
skip_empty: bool = True,
|
|
70
|
+
) -> Decorator:
|
|
71
|
+
"""
|
|
72
|
+
Load the contents of a CSV file.
|
|
73
|
+
|
|
74
|
+
Type: `ChildNode`
|
|
75
|
+
|
|
76
|
+
Supports: `pathlib.Path`
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
dialect (Literal["excel", "excel-tab", "unix"] | None, optional):
|
|
80
|
+
CSV dialect to use:
|
|
81
|
+
- `"excel"`: Excel-generated CSV files (comma-delimited)
|
|
82
|
+
- `"excel-tab"`: Excel-generated tab-delimited files
|
|
83
|
+
- `"unix"`: Unix-style CSV files (quote all fields)
|
|
84
|
+
If not specified, the reader will use default settings.
|
|
85
|
+
Defaults to `None`.
|
|
86
|
+
delimiter (str, optional):
|
|
87
|
+
Character used to separate fields. Common values are ',' and '\\t'.
|
|
88
|
+
If not specified, defaults to comma for most dialects.
|
|
89
|
+
Defaults to `None`.
|
|
90
|
+
has_header (bool, optional):
|
|
91
|
+
Whether the CSV file has a header row. Only used when
|
|
92
|
+
`as_dict=False`. When `as_dict=True`, the first row is
|
|
93
|
+
always treated as headers. Defaults to `True`.
|
|
94
|
+
as_dict (bool, optional):
|
|
95
|
+
Whether to return rows as dictionaries (using header as keys)
|
|
96
|
+
or as lists. When `True`, uses `csv.DictReader`. When `False`,
|
|
97
|
+
uses `csv.reader`.
|
|
98
|
+
Defaults to `True`.
|
|
99
|
+
encoding (str, optional):
|
|
100
|
+
The encoding to use when reading the file.
|
|
101
|
+
Defaults to `"utf-8"`.
|
|
102
|
+
skip_empty (bool, optional):
|
|
103
|
+
Whether to skip empty rows in the CSV file.
|
|
104
|
+
Defaults to `True`.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Decorator:
|
|
108
|
+
The decorated function.
|
|
109
|
+
"""
|
|
110
|
+
return LoadCsv.as_decorator(
|
|
111
|
+
dialect=dialect,
|
|
112
|
+
delimiter=delimiter,
|
|
113
|
+
has_header=has_header,
|
|
114
|
+
as_dict=as_dict,
|
|
115
|
+
encoding=encoding,
|
|
116
|
+
skip_empty=skip_empty,
|
|
117
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Child decorator to load contents from a JSON file."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
9
|
+
from click_extended.core.other.context import Context
|
|
10
|
+
from click_extended.types import Decorator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoadJson(ChildNode):
|
|
14
|
+
"""Child decorator to load contents from a JSON file."""
|
|
15
|
+
|
|
16
|
+
def handle_path(
|
|
17
|
+
self, value: Path, context: Context, *args: Any, **kwargs: Any
|
|
18
|
+
) -> Any:
|
|
19
|
+
encoding = kwargs["encoding"]
|
|
20
|
+
strict = kwargs["strict"]
|
|
21
|
+
|
|
22
|
+
if value.is_dir():
|
|
23
|
+
raise IsADirectoryError(
|
|
24
|
+
f"Path '{value.absolute()}' is a directory, but must be a file."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
with value.open("r", encoding=encoding) as f:
|
|
28
|
+
if strict:
|
|
29
|
+
return json.load(f, parse_float=Decimal)
|
|
30
|
+
return json.load(f)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def load_json(
|
|
34
|
+
encoding: str = "utf-8",
|
|
35
|
+
strict: bool = True,
|
|
36
|
+
) -> Decorator:
|
|
37
|
+
"""
|
|
38
|
+
Load a JSON file from a `pathlib.Path` object.
|
|
39
|
+
|
|
40
|
+
Type: `ChildNode`
|
|
41
|
+
|
|
42
|
+
Supports: `pathlib.Path`
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
encoding (str, optional):
|
|
46
|
+
The encoding to use when reading the file.
|
|
47
|
+
Defaults to `"utf-8"`.
|
|
48
|
+
strict (bool, optional):
|
|
49
|
+
Whether to use strict parsing for numerical values.
|
|
50
|
+
When `True`, floats are parsed as `Decimal` for precision.
|
|
51
|
+
When `False`, floats are parsed as standard Python `float`.
|
|
52
|
+
Defaults to `True`.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Decorator:
|
|
56
|
+
The decorated function.
|
|
57
|
+
"""
|
|
58
|
+
return LoadJson.as_decorator(
|
|
59
|
+
encoding=encoding,
|
|
60
|
+
strict=strict,
|
|
61
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Child decorator to load the contents of a TOML file."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=wrong-import-position
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
if sys.version_info >= (3, 11):
|
|
10
|
+
import tomllib
|
|
11
|
+
else:
|
|
12
|
+
import tomli as tomllib # type: ignore[import-not-found]
|
|
13
|
+
|
|
14
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
15
|
+
from click_extended.core.other.context import Context
|
|
16
|
+
from click_extended.types import Decorator
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LoadToml(ChildNode):
|
|
20
|
+
"""Child decorator to load the contents of a TOML file."""
|
|
21
|
+
|
|
22
|
+
def handle_path(
|
|
23
|
+
self, value: Path, context: Context, *args: Any, **kwargs: Any
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
if value.is_dir():
|
|
26
|
+
raise IsADirectoryError(
|
|
27
|
+
f"Path '{value.absolute()}' is a directory, but must be a file."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
with value.open("rb") as f:
|
|
31
|
+
result: dict[str, Any] = tomllib.load(f)
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def load_toml() -> Decorator:
|
|
36
|
+
"""
|
|
37
|
+
Load the contents of a TOML file from a `pathlib.Path` object.
|
|
38
|
+
|
|
39
|
+
Type: `ChildNode`
|
|
40
|
+
|
|
41
|
+
Supports: `pathlib.Path`
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Decorator:
|
|
45
|
+
The decorated function.
|
|
46
|
+
"""
|
|
47
|
+
return LoadToml.as_decorator()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Child decorator to load the contents of a YAML file."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Literal
|
|
5
|
+
|
|
6
|
+
from yaml import FullLoader, SafeLoader, UnsafeLoader, load
|
|
7
|
+
|
|
8
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
9
|
+
from click_extended.core.other.context import Context
|
|
10
|
+
from click_extended.types import Decorator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LoadYaml(ChildNode):
|
|
14
|
+
"""Child decorator to load the contents of a YAML file."""
|
|
15
|
+
|
|
16
|
+
def handle_path(
|
|
17
|
+
self, value: Path, context: Context, *args: Any, **kwargs: Any
|
|
18
|
+
) -> Any:
|
|
19
|
+
if value.is_dir():
|
|
20
|
+
raise IsADirectoryError(
|
|
21
|
+
f"Path '{value.absolute()}' is a directory, but must be a file."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
loader_name = kwargs["loader"]
|
|
25
|
+
loader: type[SafeLoader] | type[FullLoader] | type[UnsafeLoader]
|
|
26
|
+
|
|
27
|
+
if loader_name == "safe":
|
|
28
|
+
loader = SafeLoader
|
|
29
|
+
elif loader_name == "unsafe":
|
|
30
|
+
loader = UnsafeLoader
|
|
31
|
+
else:
|
|
32
|
+
loader = FullLoader
|
|
33
|
+
|
|
34
|
+
with value.open("r", encoding=kwargs["encoding"]) as f:
|
|
35
|
+
return load(f, Loader=loader)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_yaml(
|
|
39
|
+
encoding: str = "utf-8",
|
|
40
|
+
loader: Literal["safe", "unsafe", "full"] = "safe",
|
|
41
|
+
) -> Decorator:
|
|
42
|
+
"""
|
|
43
|
+
Load the contents of a YAML file.
|
|
44
|
+
|
|
45
|
+
Type: `ChildNode`
|
|
46
|
+
|
|
47
|
+
Supports: `pathlib.Path`
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
encoding (str, optional):
|
|
51
|
+
The encoding to use when reading the file.
|
|
52
|
+
Defaults to `"utf-8"`.
|
|
53
|
+
loader (Literal["safe", "unsafe", "full"], optional):
|
|
54
|
+
The YAML loader to use:
|
|
55
|
+
- `"safe"`: SafeLoader - Only constructs simple Python objects
|
|
56
|
+
(strings, lists, dicts, numbers, dates). Recommended for
|
|
57
|
+
untrusted input.
|
|
58
|
+
- `"unsafe"`: UnsafeLoader - Can construct arbitrary Python
|
|
59
|
+
objects. Use only with trusted YAML files.
|
|
60
|
+
- `"full"`: FullLoader - Constructs simple Python objects and
|
|
61
|
+
some additional types. Safer than unsafe but less restrictive
|
|
62
|
+
than safe.
|
|
63
|
+
Defaults to `"safe"`.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Decorator:
|
|
67
|
+
The decorated function.
|
|
68
|
+
"""
|
|
69
|
+
return LoadYaml.as_decorator(
|
|
70
|
+
encoding=encoding,
|
|
71
|
+
loader=loader,
|
|
72
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Initialization file for the `click_extended.decorators.math` module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.decorators.math.absolute import absolute
|
|
4
|
+
from click_extended.decorators.math.add import add
|
|
5
|
+
from click_extended.decorators.math.ceil import ceil
|
|
6
|
+
from click_extended.decorators.math.clamp import clamp
|
|
7
|
+
from click_extended.decorators.math.divide import divide
|
|
8
|
+
from click_extended.decorators.math.floor import floor
|
|
9
|
+
from click_extended.decorators.math.maximum import maximum
|
|
10
|
+
from click_extended.decorators.math.minimum import minimum
|
|
11
|
+
from click_extended.decorators.math.modulo import modulo
|
|
12
|
+
from click_extended.decorators.math.multiply import multiply
|
|
13
|
+
from click_extended.decorators.math.normalize import normalize
|
|
14
|
+
from click_extended.decorators.math.power import power
|
|
15
|
+
from click_extended.decorators.math.rounded import rounded
|
|
16
|
+
from click_extended.decorators.math.sqrt import sqrt
|
|
17
|
+
from click_extended.decorators.math.subtract import subtract
|
|
18
|
+
from click_extended.decorators.math.to_percent import to_percent
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"absolute",
|
|
22
|
+
"add",
|
|
23
|
+
"ceil",
|
|
24
|
+
"clamp",
|
|
25
|
+
"divide",
|
|
26
|
+
"floor",
|
|
27
|
+
"maximum",
|
|
28
|
+
"minimum",
|
|
29
|
+
"modulo",
|
|
30
|
+
"multiply",
|
|
31
|
+
"normalize",
|
|
32
|
+
"power",
|
|
33
|
+
"rounded",
|
|
34
|
+
"sqrt",
|
|
35
|
+
"subtract",
|
|
36
|
+
"to_percent",
|
|
37
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Return the absolute value of the input."""
|
|
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 Absolute(ChildNode):
|
|
11
|
+
"""Return the absolute value of the input."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
return abs(value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def absolute() -> Decorator:
|
|
24
|
+
"""
|
|
25
|
+
Return the absolute value of the input.
|
|
26
|
+
|
|
27
|
+
Type: `ChildNode`
|
|
28
|
+
|
|
29
|
+
Supports: `int`, `float`
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Decorator:
|
|
33
|
+
The decorated function.
|
|
34
|
+
"""
|
|
35
|
+
return Absolute.as_decorator()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Add a value to the input."""
|
|
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 Add(ChildNode):
|
|
11
|
+
"""Add a value to the input."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
return value + kwargs["n"]
|
|
21
|
+
|
|
22
|
+
def handle_str(
|
|
23
|
+
self,
|
|
24
|
+
value: str,
|
|
25
|
+
context: Context,
|
|
26
|
+
*args: Any,
|
|
27
|
+
**kwargs: Any,
|
|
28
|
+
) -> Any:
|
|
29
|
+
return value + str(kwargs["n"])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def add(n: int | float | str) -> Decorator:
|
|
33
|
+
"""
|
|
34
|
+
Add a value to the input.
|
|
35
|
+
|
|
36
|
+
Type: `ChildNode`
|
|
37
|
+
|
|
38
|
+
Supports: `int`, `float`, `str`
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
n (int | float | str):
|
|
42
|
+
The value to add.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Decorator:
|
|
46
|
+
The decorated function.
|
|
47
|
+
"""
|
|
48
|
+
return Add.as_decorator(n=n)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Calculate the ceiling of the input."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
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 Ceil(ChildNode):
|
|
12
|
+
"""Calculate the ceiling of the input."""
|
|
13
|
+
|
|
14
|
+
def handle_numeric(
|
|
15
|
+
self,
|
|
16
|
+
value: int | float,
|
|
17
|
+
context: Context,
|
|
18
|
+
*args: Any,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> Any:
|
|
21
|
+
return math.ceil(value)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ceil() -> Decorator:
|
|
25
|
+
"""
|
|
26
|
+
Calculate the ceiling of the input.
|
|
27
|
+
|
|
28
|
+
Type: `ChildNode`
|
|
29
|
+
|
|
30
|
+
Supports: `int`, `float`
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
Decorator:
|
|
34
|
+
The decorated function.
|
|
35
|
+
"""
|
|
36
|
+
return Ceil.as_decorator()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Clamp the input value between a minimum and maximum."""
|
|
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 Clamp(ChildNode):
|
|
11
|
+
"""Clamp the input value between a minimum and maximum."""
|
|
12
|
+
|
|
13
|
+
def handle_numeric(
|
|
14
|
+
self,
|
|
15
|
+
value: int | float,
|
|
16
|
+
context: Context,
|
|
17
|
+
*args: Any,
|
|
18
|
+
**kwargs: Any,
|
|
19
|
+
) -> Any:
|
|
20
|
+
min_val = kwargs.get("min_val")
|
|
21
|
+
max_val = kwargs.get("max_val")
|
|
22
|
+
|
|
23
|
+
if min_val is not None and value < min_val:
|
|
24
|
+
return min_val
|
|
25
|
+
if max_val is not None and value > max_val:
|
|
26
|
+
return max_val
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def clamp(
|
|
31
|
+
min_val: int | float | None = None,
|
|
32
|
+
max_val: int | float | None = None,
|
|
33
|
+
) -> Decorator:
|
|
34
|
+
"""
|
|
35
|
+
Clamp the input value between a minimum and maximum.
|
|
36
|
+
|
|
37
|
+
Type: `ChildNode`
|
|
38
|
+
|
|
39
|
+
Supports: `int`, `float`
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
min_val (int | float, optional):
|
|
43
|
+
The minimum value.
|
|
44
|
+
max_val (int | float, optional):
|
|
45
|
+
The maximum value.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Decorator:
|
|
49
|
+
The decorated function.
|
|
50
|
+
"""
|
|
51
|
+
return Clamp.as_decorator(min_val=min_val, max_val=max_val)
|