click-extended 0.4.0__tar.gz → 1.0.1__tar.gz
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-0.4.0/click_extended.egg-info → click_extended-1.0.1}/PKG-INFO +100 -29
- {click_extended-0.4.0 → click_extended-1.0.1}/README.md +92 -28
- click_extended-1.0.1/click_extended/__init__.py +21 -0
- click_extended-1.0.1/click_extended/classes.py +25 -0
- click_extended-1.0.1/click_extended/core/__init__.py +10 -0
- click_extended-1.0.1/click_extended/core/decorators/__init__.py +21 -0
- click_extended-1.0.1/click_extended/core/decorators/argument.py +227 -0
- click_extended-1.0.1/click_extended/core/decorators/command.py +93 -0
- click_extended-1.0.1/click_extended/core/decorators/env.py +155 -0
- click_extended-1.0.1/click_extended/core/decorators/group.py +96 -0
- click_extended-1.0.1/click_extended/core/decorators/option.py +347 -0
- click_extended-1.0.1/click_extended/core/decorators/prompt.py +69 -0
- click_extended-1.0.1/click_extended/core/decorators/selection.py +155 -0
- click_extended-1.0.1/click_extended/core/decorators/tag.py +109 -0
- click_extended-1.0.1/click_extended/core/nodes/__init__.py +21 -0
- click_extended-1.0.1/click_extended/core/nodes/_root_node.py +1012 -0
- click_extended-1.0.1/click_extended/core/nodes/argument_node.py +165 -0
- click_extended-1.0.1/click_extended/core/nodes/child_node.py +555 -0
- click_extended-1.0.1/click_extended/core/nodes/child_validation_node.py +100 -0
- click_extended-1.0.1/click_extended/core/nodes/node.py +55 -0
- click_extended-1.0.1/click_extended/core/nodes/option_node.py +205 -0
- click_extended-1.0.1/click_extended/core/nodes/parent_node.py +220 -0
- click_extended-1.0.1/click_extended/core/nodes/validation_node.py +124 -0
- click_extended-1.0.1/click_extended/core/other/__init__.py +7 -0
- click_extended-1.0.1/click_extended/core/other/_click_command.py +60 -0
- click_extended-1.0.1/click_extended/core/other/_click_group.py +246 -0
- click_extended-1.0.1/click_extended/core/other/_tree.py +491 -0
- click_extended-1.0.1/click_extended/core/other/context.py +496 -0
- click_extended-1.0.1/click_extended/decorators/__init__.py +29 -0
- click_extended-1.0.1/click_extended/decorators/check/__init__.py +57 -0
- click_extended-1.0.1/click_extended/decorators/check/conflicts.py +149 -0
- click_extended-1.0.1/click_extended/decorators/check/contains.py +69 -0
- click_extended-1.0.1/click_extended/decorators/check/dependencies.py +115 -0
- click_extended-1.0.1/click_extended/decorators/check/divisible_by.py +48 -0
- click_extended-1.0.1/click_extended/decorators/check/ends_with.py +85 -0
- click_extended-1.0.1/click_extended/decorators/check/exclusive.py +75 -0
- click_extended-1.0.1/click_extended/decorators/check/falsy.py +37 -0
- click_extended-1.0.1/click_extended/decorators/check/is_email.py +43 -0
- click_extended-1.0.1/click_extended/decorators/check/is_hex_color.py +41 -0
- click_extended-1.0.1/click_extended/decorators/check/is_hostname.py +47 -0
- click_extended-1.0.1/click_extended/decorators/check/is_ipv4.py +46 -0
- click_extended-1.0.1/click_extended/decorators/check/is_ipv6.py +46 -0
- click_extended-1.0.1/click_extended/decorators/check/is_json.py +40 -0
- click_extended-1.0.1/click_extended/decorators/check/is_mac_address.py +40 -0
- click_extended-1.0.1/click_extended/decorators/check/is_negative.py +37 -0
- click_extended-1.0.1/click_extended/decorators/check/is_non_zero.py +37 -0
- click_extended-1.0.1/click_extended/decorators/check/is_port.py +39 -0
- click_extended-1.0.1/click_extended/decorators/check/is_positive.py +37 -0
- click_extended-1.0.1/click_extended/decorators/check/is_url.py +75 -0
- click_extended-1.0.1/click_extended/decorators/check/is_uuid.py +40 -0
- click_extended-1.0.1/click_extended/decorators/check/length.py +68 -0
- click_extended-1.0.1/click_extended/decorators/check/not_empty.py +49 -0
- click_extended-1.0.1/click_extended/decorators/check/regex.py +47 -0
- click_extended-1.0.1/click_extended/decorators/check/requires.py +190 -0
- click_extended-1.0.1/click_extended/decorators/check/starts_with.py +87 -0
- click_extended-1.0.1/click_extended/decorators/check/truthy.py +37 -0
- click_extended-1.0.1/click_extended/decorators/compare/__init__.py +15 -0
- click_extended-1.0.1/click_extended/decorators/compare/at_least.py +57 -0
- click_extended-1.0.1/click_extended/decorators/compare/at_most.py +57 -0
- click_extended-1.0.1/click_extended/decorators/compare/between.py +119 -0
- click_extended-1.0.1/click_extended/decorators/compare/greater_than.py +183 -0
- click_extended-1.0.1/click_extended/decorators/compare/less_than.py +183 -0
- click_extended-1.0.1/click_extended/decorators/convert/__init__.py +31 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_angle.py +94 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_area.py +123 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_bits.py +211 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_distance.py +154 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_energy.py +155 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_power.py +128 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_pressure.py +131 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_speed.py +122 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_temperature.py +89 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_time.py +108 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_volume.py +218 -0
- click_extended-1.0.1/click_extended/decorators/convert/convert_weight.py +158 -0
- click_extended-1.0.1/click_extended/decorators/load/__init__.py +13 -0
- click_extended-1.0.1/click_extended/decorators/load/load_csv.py +117 -0
- click_extended-1.0.1/click_extended/decorators/load/load_json.py +61 -0
- click_extended-1.0.1/click_extended/decorators/load/load_toml.py +47 -0
- click_extended-1.0.1/click_extended/decorators/load/load_yaml.py +72 -0
- click_extended-1.0.1/click_extended/decorators/math/__init__.py +37 -0
- click_extended-1.0.1/click_extended/decorators/math/absolute.py +35 -0
- click_extended-1.0.1/click_extended/decorators/math/add.py +48 -0
- click_extended-1.0.1/click_extended/decorators/math/ceil.py +36 -0
- click_extended-1.0.1/click_extended/decorators/math/clamp.py +51 -0
- click_extended-1.0.1/click_extended/decorators/math/divide.py +42 -0
- click_extended-1.0.1/click_extended/decorators/math/floor.py +36 -0
- click_extended-1.0.1/click_extended/decorators/math/maximum.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/minimum.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/modulo.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/multiply.py +51 -0
- click_extended-1.0.1/click_extended/decorators/math/normalize.py +76 -0
- click_extended-1.0.1/click_extended/decorators/math/power.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/rounded.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/sqrt.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/subtract.py +39 -0
- click_extended-1.0.1/click_extended/decorators/math/to_percent.py +63 -0
- click_extended-1.0.1/click_extended/decorators/misc/__init__.py +17 -0
- click_extended-1.0.1/click_extended/decorators/misc/choice.py +139 -0
- click_extended-1.0.1/click_extended/decorators/misc/confirm_if.py +147 -0
- click_extended-1.0.1/click_extended/decorators/misc/default.py +95 -0
- click_extended-1.0.1/click_extended/decorators/misc/deprecated.py +131 -0
- click_extended-1.0.1/click_extended/decorators/misc/experimental.py +79 -0
- click_extended-1.0.1/click_extended/decorators/misc/now.py +42 -0
- click_extended-1.0.1/click_extended/decorators/random/__init__.py +21 -0
- click_extended-1.0.1/click_extended/decorators/random/random_bool.py +49 -0
- click_extended-1.0.1/click_extended/decorators/random/random_choice.py +63 -0
- click_extended-1.0.1/click_extended/decorators/random/random_datetime.py +140 -0
- click_extended-1.0.1/click_extended/decorators/random/random_float.py +62 -0
- click_extended-1.0.1/click_extended/decorators/random/random_integer.py +56 -0
- click_extended-1.0.1/click_extended/decorators/random/random_prime.py +196 -0
- click_extended-1.0.1/click_extended/decorators/random/random_string.py +77 -0
- click_extended-1.0.1/click_extended/decorators/random/random_uuid.py +119 -0
- click_extended-1.0.1/click_extended/decorators/transform/__init__.py +71 -0
- click_extended-1.0.1/click_extended/decorators/transform/add_prefix.py +58 -0
- click_extended-1.0.1/click_extended/decorators/transform/add_suffix.py +58 -0
- click_extended-1.0.1/click_extended/decorators/transform/apply.py +35 -0
- click_extended-1.0.1/click_extended/decorators/transform/basename.py +44 -0
- click_extended-1.0.1/click_extended/decorators/transform/dirname.py +44 -0
- click_extended-1.0.1/click_extended/decorators/transform/expand_vars.py +36 -0
- click_extended-1.0.1/click_extended/decorators/transform/remove_prefix.py +57 -0
- click_extended-1.0.1/click_extended/decorators/transform/remove_suffix.py +57 -0
- click_extended-1.0.1/click_extended/decorators/transform/replace.py +46 -0
- click_extended-1.0.1/click_extended/decorators/transform/slugify.py +45 -0
- click_extended-1.0.1/click_extended/decorators/transform/split.py +43 -0
- click_extended-1.0.1/click_extended/decorators/transform/strip.py +148 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_case.py +216 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_date.py +75 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_datetime.py +83 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_path.py +274 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_time.py +77 -0
- click_extended-1.0.1/click_extended/decorators/transform/to_timestamp.py +114 -0
- click_extended-1.0.1/click_extended/decorators/transform/truncate.py +47 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/click_extended/types.py +1 -1
- click_extended-1.0.1/click_extended/utils/__init__.py +13 -0
- click_extended-1.0.1/click_extended/utils/casing.py +169 -0
- click_extended-1.0.1/click_extended/utils/checks.py +48 -0
- click_extended-1.0.1/click_extended/utils/dispatch.py +1016 -0
- click_extended-1.0.1/click_extended/utils/format.py +101 -0
- click_extended-1.0.1/click_extended/utils/humanize.py +209 -0
- click_extended-1.0.1/click_extended/utils/naming.py +238 -0
- click_extended-1.0.1/click_extended/utils/process.py +294 -0
- click_extended-1.0.1/click_extended/utils/selection.py +267 -0
- click_extended-1.0.1/click_extended/utils/time.py +46 -0
- {click_extended-0.4.0 → click_extended-1.0.1/click_extended.egg-info}/PKG-INFO +100 -29
- click_extended-1.0.1/click_extended.egg-info/SOURCES.txt +153 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/requires.txt +8 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/pyproject.toml +13 -4
- {click_extended-0.4.0 → click_extended-1.0.1}/tests/test_lifecycle.py +7 -10
- click_extended-0.4.0/click_extended/__init__.py +0 -17
- click_extended-0.4.0/click_extended/classes.py +0 -21
- click_extended-0.4.0/click_extended.egg-info/SOURCES.txt +0 -24
- click_extended-0.4.0/tests/test_argument_node.py +0 -1059
- click_extended-0.4.0/tests/test_child_node.py +0 -2633
- click_extended-0.4.0/tests/test_context.py +0 -867
- click_extended-0.4.0/tests/test_errors.py +0 -478
- click_extended-0.4.0/tests/test_node.py +0 -112
- click_extended-0.4.0/tests/test_option_node.py +0 -1346
- click_extended-0.4.0/tests/test_parent_node.py +0 -947
- click_extended-0.4.0/tests/test_root_node.py +0 -2240
- click_extended-0.4.0/tests/test_tag.py +0 -766
- click_extended-0.4.0/tests/test_tree.py +0 -1481
- {click_extended-0.4.0 → click_extended-1.0.1}/AUTHORS.md +0 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/LICENSE +0 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/click_extended/errors.py +0 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/dependency_links.txt +0 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/top_level.txt +0 -0
- {click_extended-0.4.0 → click_extended-1.0.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: click_extended
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: An extension to Click with additional features like automatic async support, aliasing and a modular decorator system.
|
|
5
5
|
Author-email: Marcus Fredriksson <marcus@marcusfredriksson.com>
|
|
6
6
|
License: MIT License
|
|
@@ -37,6 +37,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
37
37
|
Classifier: Programming Language :: Python :: 3.11
|
|
38
38
|
Classifier: Programming Language :: Python :: 3.12
|
|
39
39
|
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
40
41
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
41
42
|
Classifier: Topic :: System :: Shells
|
|
42
43
|
Classifier: Topic :: Utilities
|
|
@@ -47,6 +48,10 @@ License-File: LICENSE
|
|
|
47
48
|
License-File: AUTHORS.md
|
|
48
49
|
Requires-Dist: click>=8.3.0
|
|
49
50
|
Requires-Dist: python-dotenv>=1.2.1
|
|
51
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
52
|
+
Requires-Dist: email-validator>=2.3.0
|
|
53
|
+
Requires-Dist: python-slugify>=8.0.4
|
|
54
|
+
Requires-Dist: tomli>=2.0.0; python_version < "3.11"
|
|
50
55
|
Provides-Extra: build
|
|
51
56
|
Requires-Dist: build; extra == "build"
|
|
52
57
|
Requires-Dist: twine; extra == "build"
|
|
@@ -59,6 +64,8 @@ Requires-Dist: pylint>=3.0.0; extra == "dev"
|
|
|
59
64
|
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
60
65
|
Requires-Dist: black>=25.9.0; extra == "dev"
|
|
61
66
|
Requires-Dist: pre-commit>=4.3.0; extra == "dev"
|
|
67
|
+
Requires-Dist: types-PyYAML>=6.0.12.20250915; extra == "dev"
|
|
68
|
+
Requires-Dist: tomli>=2.0.0; extra == "dev"
|
|
62
69
|
Dynamic: license-file
|
|
63
70
|
|
|
64
71
|

|
|
@@ -88,6 +95,7 @@ An extension of the [Click](https://github.com/pallets/click) library with addit
|
|
|
88
95
|
- **Environment Variables**: Built-in support for loading and using environment variables as a data source.
|
|
89
96
|
- **Full Type Support**: Built with type-hinting from the ground up, meaning everything is fully typed.
|
|
90
97
|
- **Improved Errors**: Improved error output like tips, debugging, and more.
|
|
98
|
+
- **Short Flag Concatenation**: Automatically support concatenating short hand flags where `-r -f` is the same as `-rf`.
|
|
91
99
|
|
|
92
100
|
## Installation
|
|
93
101
|
|
|
@@ -116,14 +124,18 @@ def my_function(value: str, count: int):
|
|
|
116
124
|
|
|
117
125
|
if __name__ == "__main__":
|
|
118
126
|
my_function()
|
|
127
|
+
```
|
|
119
128
|
|
|
120
|
-
|
|
121
|
-
|
|
129
|
+
```bash
|
|
130
|
+
$ python cli.py "Hello world"
|
|
131
|
+
Hello world
|
|
132
|
+
```
|
|
122
133
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
```bash
|
|
135
|
+
$ python cli.py "Hello world" --count 3
|
|
136
|
+
Hello world
|
|
137
|
+
Hello world
|
|
138
|
+
Hello world
|
|
127
139
|
```
|
|
128
140
|
|
|
129
141
|
### Basic Command Line Interface
|
|
@@ -146,16 +158,20 @@ def my_function(value: str, count: int):
|
|
|
146
158
|
|
|
147
159
|
if __name__ == "__main__":
|
|
148
160
|
my_group()
|
|
161
|
+
```
|
|
149
162
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
163
|
+
```bash
|
|
164
|
+
$ python cli.py my_function "Hello world"
|
|
165
|
+
Running initialization code...
|
|
166
|
+
Hello world
|
|
167
|
+
```
|
|
153
168
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
169
|
+
```bash
|
|
170
|
+
$ python cli.py my_function "Hello world" --count 3
|
|
171
|
+
Running initialization code...
|
|
172
|
+
Hello world
|
|
173
|
+
Hello world
|
|
174
|
+
Hello world
|
|
159
175
|
```
|
|
160
176
|
|
|
161
177
|
### Using Environment Variables
|
|
@@ -181,21 +197,72 @@ def my_function_2(api_key: str):
|
|
|
181
197
|
|
|
182
198
|
if __name__ == "__main__":
|
|
183
199
|
my_group()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
$ python cli.py my_function_1
|
|
204
|
+
The API key is: None
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
$ API_KEY=api-key python cli.py my_function_1
|
|
209
|
+
The API key is: api-key
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
$ python cli.py my_function_2
|
|
214
|
+
ProcessError (my_function_2): Required environment variable 'API_KEY' is not set.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
$ API_KEY=api-key python cli.py my_function_2
|
|
219
|
+
The API key is: api-key
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Load CSV Data
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
import pandas as pd
|
|
226
|
+
from click_extended import command, argument
|
|
227
|
+
from click_extended.decorators import to_path, load_csv
|
|
228
|
+
|
|
229
|
+
@command()
|
|
230
|
+
@argument("file", param="data")
|
|
231
|
+
@to_path(extensions=["csv"], exists=True)
|
|
232
|
+
@load_csv()
|
|
233
|
+
def my_command(data: dict[str, Any], *args: Any, **kwargs: Any) -> None:
|
|
234
|
+
df = pd.DataFrame(data)
|
|
235
|
+
print(df.head())
|
|
236
|
+
```
|
|
184
237
|
|
|
185
|
-
|
|
186
|
-
# The API key is: None
|
|
238
|
+
_Note: `pandas` is not installed in this library and must be installed manually due to size._
|
|
187
239
|
|
|
188
|
-
|
|
189
|
-
# The API key is: api-key
|
|
240
|
+
### Pre-Built Children
|
|
190
241
|
|
|
191
|
-
|
|
192
|
-
# ProcessError (my_function_2): Required environment variable 'API_KEY' is not set.
|
|
242
|
+
This library includes a vast number of pre-built children, everything from checking values to transforming values.
|
|
193
243
|
|
|
194
|
-
|
|
195
|
-
|
|
244
|
+
```python
|
|
245
|
+
from click_extended import command, argument, option
|
|
246
|
+
from click_extended.decorators import to_snake_case, strip, is_email, minimum, dependencies
|
|
247
|
+
|
|
248
|
+
@command()
|
|
249
|
+
@dependencies("username", "email", "password")
|
|
250
|
+
@argument("username")
|
|
251
|
+
@to_snake_case()
|
|
252
|
+
@strip()
|
|
253
|
+
@option("email")
|
|
254
|
+
@is_email()
|
|
255
|
+
@option("password")
|
|
256
|
+
@minimum(8)
|
|
257
|
+
def create_account(username: str, email: str, password: str) -> None:
|
|
258
|
+
print("Username:", username)
|
|
259
|
+
print("Email:", email)
|
|
260
|
+
print("Password:", password)
|
|
196
261
|
```
|
|
197
262
|
|
|
198
|
-
### Custom
|
|
263
|
+
### Custom Nodes
|
|
264
|
+
|
|
265
|
+
If the library does not include a decorator you need, you can easily create your own. Read more about creating your own [children](./docs/core/CHILD_NODE.md), [validators](./docs/core/VALIDATION_NODE.md), [child validators](./docs/core/CHILD_VALIDATION_NODE.md) or [parents](./docs/core/PARENT_NODE.md).
|
|
199
266
|
|
|
200
267
|
```python
|
|
201
268
|
from typing import Any
|
|
@@ -205,7 +272,7 @@ from click_extended.classes import ChildNode
|
|
|
205
272
|
from click_extended.types import Context, Decorator
|
|
206
273
|
|
|
207
274
|
class MyCustomChild(ChildNode):
|
|
208
|
-
def
|
|
275
|
+
def handle_string(
|
|
209
276
|
self,
|
|
210
277
|
value: str,
|
|
211
278
|
context: Context,
|
|
@@ -236,12 +303,16 @@ def my_function(value: str):
|
|
|
236
303
|
|
|
237
304
|
if __name__ == "__main__":
|
|
238
305
|
my_group()
|
|
306
|
+
```
|
|
239
307
|
|
|
240
|
-
|
|
241
|
-
|
|
308
|
+
```bash
|
|
309
|
+
$ python cli.py my_function valid
|
|
310
|
+
The value 'VALID' should be uppercase.
|
|
311
|
+
```
|
|
242
312
|
|
|
243
|
-
|
|
244
|
-
|
|
313
|
+
```bash
|
|
314
|
+
$ python cli.py my_function invalid
|
|
315
|
+
ValueError (my_function): "The value 'invalid' is not valid"
|
|
245
316
|
```
|
|
246
317
|
|
|
247
318
|
## Documentation
|
|
@@ -25,6 +25,7 @@ An extension of the [Click](https://github.com/pallets/click) library with addit
|
|
|
25
25
|
- **Environment Variables**: Built-in support for loading and using environment variables as a data source.
|
|
26
26
|
- **Full Type Support**: Built with type-hinting from the ground up, meaning everything is fully typed.
|
|
27
27
|
- **Improved Errors**: Improved error output like tips, debugging, and more.
|
|
28
|
+
- **Short Flag Concatenation**: Automatically support concatenating short hand flags where `-r -f` is the same as `-rf`.
|
|
28
29
|
|
|
29
30
|
## Installation
|
|
30
31
|
|
|
@@ -53,14 +54,18 @@ def my_function(value: str, count: int):
|
|
|
53
54
|
|
|
54
55
|
if __name__ == "__main__":
|
|
55
56
|
my_function()
|
|
57
|
+
```
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
```bash
|
|
60
|
+
$ python cli.py "Hello world"
|
|
61
|
+
Hello world
|
|
62
|
+
```
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
```bash
|
|
65
|
+
$ python cli.py "Hello world" --count 3
|
|
66
|
+
Hello world
|
|
67
|
+
Hello world
|
|
68
|
+
Hello world
|
|
64
69
|
```
|
|
65
70
|
|
|
66
71
|
### Basic Command Line Interface
|
|
@@ -83,16 +88,20 @@ def my_function(value: str, count: int):
|
|
|
83
88
|
|
|
84
89
|
if __name__ == "__main__":
|
|
85
90
|
my_group()
|
|
91
|
+
```
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
```bash
|
|
94
|
+
$ python cli.py my_function "Hello world"
|
|
95
|
+
Running initialization code...
|
|
96
|
+
Hello world
|
|
97
|
+
```
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
```bash
|
|
100
|
+
$ python cli.py my_function "Hello world" --count 3
|
|
101
|
+
Running initialization code...
|
|
102
|
+
Hello world
|
|
103
|
+
Hello world
|
|
104
|
+
Hello world
|
|
96
105
|
```
|
|
97
106
|
|
|
98
107
|
### Using Environment Variables
|
|
@@ -118,21 +127,72 @@ def my_function_2(api_key: str):
|
|
|
118
127
|
|
|
119
128
|
if __name__ == "__main__":
|
|
120
129
|
my_group()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
$ python cli.py my_function_1
|
|
134
|
+
The API key is: None
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
$ API_KEY=api-key python cli.py my_function_1
|
|
139
|
+
The API key is: api-key
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
$ python cli.py my_function_2
|
|
144
|
+
ProcessError (my_function_2): Required environment variable 'API_KEY' is not set.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
$ API_KEY=api-key python cli.py my_function_2
|
|
149
|
+
The API key is: api-key
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Load CSV Data
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import pandas as pd
|
|
156
|
+
from click_extended import command, argument
|
|
157
|
+
from click_extended.decorators import to_path, load_csv
|
|
158
|
+
|
|
159
|
+
@command()
|
|
160
|
+
@argument("file", param="data")
|
|
161
|
+
@to_path(extensions=["csv"], exists=True)
|
|
162
|
+
@load_csv()
|
|
163
|
+
def my_command(data: dict[str, Any], *args: Any, **kwargs: Any) -> None:
|
|
164
|
+
df = pd.DataFrame(data)
|
|
165
|
+
print(df.head())
|
|
166
|
+
```
|
|
121
167
|
|
|
122
|
-
|
|
123
|
-
# The API key is: None
|
|
168
|
+
_Note: `pandas` is not installed in this library and must be installed manually due to size._
|
|
124
169
|
|
|
125
|
-
|
|
126
|
-
# The API key is: api-key
|
|
170
|
+
### Pre-Built Children
|
|
127
171
|
|
|
128
|
-
|
|
129
|
-
# ProcessError (my_function_2): Required environment variable 'API_KEY' is not set.
|
|
172
|
+
This library includes a vast number of pre-built children, everything from checking values to transforming values.
|
|
130
173
|
|
|
131
|
-
|
|
132
|
-
|
|
174
|
+
```python
|
|
175
|
+
from click_extended import command, argument, option
|
|
176
|
+
from click_extended.decorators import to_snake_case, strip, is_email, minimum, dependencies
|
|
177
|
+
|
|
178
|
+
@command()
|
|
179
|
+
@dependencies("username", "email", "password")
|
|
180
|
+
@argument("username")
|
|
181
|
+
@to_snake_case()
|
|
182
|
+
@strip()
|
|
183
|
+
@option("email")
|
|
184
|
+
@is_email()
|
|
185
|
+
@option("password")
|
|
186
|
+
@minimum(8)
|
|
187
|
+
def create_account(username: str, email: str, password: str) -> None:
|
|
188
|
+
print("Username:", username)
|
|
189
|
+
print("Email:", email)
|
|
190
|
+
print("Password:", password)
|
|
133
191
|
```
|
|
134
192
|
|
|
135
|
-
### Custom
|
|
193
|
+
### Custom Nodes
|
|
194
|
+
|
|
195
|
+
If the library does not include a decorator you need, you can easily create your own. Read more about creating your own [children](./docs/core/CHILD_NODE.md), [validators](./docs/core/VALIDATION_NODE.md), [child validators](./docs/core/CHILD_VALIDATION_NODE.md) or [parents](./docs/core/PARENT_NODE.md).
|
|
136
196
|
|
|
137
197
|
```python
|
|
138
198
|
from typing import Any
|
|
@@ -142,7 +202,7 @@ from click_extended.classes import ChildNode
|
|
|
142
202
|
from click_extended.types import Context, Decorator
|
|
143
203
|
|
|
144
204
|
class MyCustomChild(ChildNode):
|
|
145
|
-
def
|
|
205
|
+
def handle_string(
|
|
146
206
|
self,
|
|
147
207
|
value: str,
|
|
148
208
|
context: Context,
|
|
@@ -173,12 +233,16 @@ def my_function(value: str):
|
|
|
173
233
|
|
|
174
234
|
if __name__ == "__main__":
|
|
175
235
|
my_group()
|
|
236
|
+
```
|
|
176
237
|
|
|
177
|
-
|
|
178
|
-
|
|
238
|
+
```bash
|
|
239
|
+
$ python cli.py my_function valid
|
|
240
|
+
The value 'VALID' should be uppercase.
|
|
241
|
+
```
|
|
179
242
|
|
|
180
|
-
|
|
181
|
-
|
|
243
|
+
```bash
|
|
244
|
+
$ python cli.py my_function invalid
|
|
245
|
+
ValueError (my_function): "The value 'invalid' is not valid"
|
|
182
246
|
```
|
|
183
247
|
|
|
184
248
|
## Documentation
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Initialization file for the 'click_extended' module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.core.decorators.argument import argument
|
|
4
|
+
from click_extended.core.decorators.command import command
|
|
5
|
+
from click_extended.core.decorators.env import env
|
|
6
|
+
from click_extended.core.decorators.group import group
|
|
7
|
+
from click_extended.core.decorators.option import option
|
|
8
|
+
from click_extended.core.decorators.prompt import prompt
|
|
9
|
+
from click_extended.core.decorators.selection import selection
|
|
10
|
+
from click_extended.core.decorators.tag import tag
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"argument",
|
|
14
|
+
"command",
|
|
15
|
+
"env",
|
|
16
|
+
"group",
|
|
17
|
+
"option",
|
|
18
|
+
"prompt",
|
|
19
|
+
"selection",
|
|
20
|
+
"tag",
|
|
21
|
+
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Classes used in `click_extended`."""
|
|
2
|
+
|
|
3
|
+
from click_extended.core.decorators.command import Command
|
|
4
|
+
from click_extended.core.decorators.group import Group
|
|
5
|
+
from click_extended.core.decorators.tag import Tag
|
|
6
|
+
from click_extended.core.nodes.argument_node import ArgumentNode
|
|
7
|
+
from click_extended.core.nodes.child_node import ChildNode
|
|
8
|
+
from click_extended.core.nodes.child_validation_node import ChildValidationNode
|
|
9
|
+
from click_extended.core.nodes.node import Node
|
|
10
|
+
from click_extended.core.nodes.option_node import OptionNode
|
|
11
|
+
from click_extended.core.nodes.parent_node import ParentNode
|
|
12
|
+
from click_extended.core.nodes.validation_node import ValidationNode
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Node",
|
|
16
|
+
"ChildNode",
|
|
17
|
+
"ChildValidationNode",
|
|
18
|
+
"ParentNode",
|
|
19
|
+
"ArgumentNode",
|
|
20
|
+
"OptionNode",
|
|
21
|
+
"Command",
|
|
22
|
+
"Group",
|
|
23
|
+
"Tag",
|
|
24
|
+
"ValidationNode",
|
|
25
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Initialization file for the 'click_extended.core' module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.core.decorators import *
|
|
4
|
+
from click_extended.core.decorators import __all__ as decorators_all
|
|
5
|
+
from click_extended.core.nodes import *
|
|
6
|
+
from click_extended.core.nodes import __all__ as nodes_all
|
|
7
|
+
from click_extended.core.other import *
|
|
8
|
+
from click_extended.core.other import __all__ as other_all
|
|
9
|
+
|
|
10
|
+
__all__ = [*decorators_all, *nodes_all, *other_all] # type: ignore
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Initialization file for the `click_extended.core.decorators` module."""
|
|
2
|
+
|
|
3
|
+
from click_extended.core.decorators.argument import Argument
|
|
4
|
+
from click_extended.core.decorators.command import Command
|
|
5
|
+
from click_extended.core.decorators.env import Env
|
|
6
|
+
from click_extended.core.decorators.group import Group
|
|
7
|
+
from click_extended.core.decorators.option import Option
|
|
8
|
+
from click_extended.core.decorators.prompt import Prompt
|
|
9
|
+
from click_extended.core.decorators.selection import Selection
|
|
10
|
+
from click_extended.core.decorators.tag import Tag
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Argument",
|
|
14
|
+
"Command",
|
|
15
|
+
"Env",
|
|
16
|
+
"Group",
|
|
17
|
+
"Option",
|
|
18
|
+
"Prompt",
|
|
19
|
+
"Selection",
|
|
20
|
+
"Tag",
|
|
21
|
+
]
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Class to support arguments in the command line interface."""
|
|
2
|
+
|
|
3
|
+
# pylint: disable=too-many-arguments
|
|
4
|
+
# pylint: disable=too-many-positional-arguments
|
|
5
|
+
# pylint: disable=redefined-builtin
|
|
6
|
+
|
|
7
|
+
from builtins import type as builtins_type
|
|
8
|
+
from typing import Any, Type, cast
|
|
9
|
+
|
|
10
|
+
from click_extended.core.nodes.argument_node import ArgumentNode
|
|
11
|
+
from click_extended.core.other.context import Context
|
|
12
|
+
from click_extended.types import Decorator
|
|
13
|
+
from click_extended.utils.casing import Casing
|
|
14
|
+
from click_extended.utils.humanize import humanize_type
|
|
15
|
+
from click_extended.utils.naming import validate_name
|
|
16
|
+
|
|
17
|
+
_MISSING = object()
|
|
18
|
+
SUPPORTED_TYPES = (str, int, float, bool)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Argument(ArgumentNode):
|
|
22
|
+
"""`ArgumentNode` that represents a Click argument."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
name: str,
|
|
27
|
+
param: str | None = None,
|
|
28
|
+
nargs: int = 1,
|
|
29
|
+
type: Type[Any] | Any = None,
|
|
30
|
+
help: str | None = None,
|
|
31
|
+
required: bool = True,
|
|
32
|
+
default: Any = _MISSING,
|
|
33
|
+
tags: str | list[str] | None = None,
|
|
34
|
+
**kwargs: Any,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize a new `Argument` instance.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
name (str):
|
|
41
|
+
The argument name in snake_case.
|
|
42
|
+
Examples: "filename", "input_file"
|
|
43
|
+
param (str, optional):
|
|
44
|
+
Custom parameter name for the function.
|
|
45
|
+
If not provided, uses the name directly.
|
|
46
|
+
nargs (int):
|
|
47
|
+
Number of arguments to accept. Use `-1` for unlimited.
|
|
48
|
+
Defaults to `1`.
|
|
49
|
+
type (Any, optional):
|
|
50
|
+
The type to convert the value to (`int`, `str`, `float`, etc.).
|
|
51
|
+
help (str, optional):
|
|
52
|
+
Help text for this argument.
|
|
53
|
+
required (bool):
|
|
54
|
+
Whether this argument is required. Defaults to `True` unless
|
|
55
|
+
`default` is provided, which makes it optional automatically.
|
|
56
|
+
default (Any):
|
|
57
|
+
Default value if not provided. When set, automatically makes
|
|
58
|
+
the argument optional (`required=False`). Defaults to `None`.
|
|
59
|
+
tags (str | list[str], optional):
|
|
60
|
+
Tag(s) to associate with this argument for grouping.
|
|
61
|
+
**kwargs (Any):
|
|
62
|
+
Additional keyword arguments.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If both `default` is provided and `required=True` is
|
|
66
|
+
explicitly set (detected via kwargs inspection in decorator).
|
|
67
|
+
"""
|
|
68
|
+
validate_name(name, "argument name")
|
|
69
|
+
|
|
70
|
+
param_name = param if param is not None else name
|
|
71
|
+
|
|
72
|
+
validate_name(param_name, "parameter name")
|
|
73
|
+
|
|
74
|
+
if default is not _MISSING and required is True:
|
|
75
|
+
required = False
|
|
76
|
+
|
|
77
|
+
if default is _MISSING:
|
|
78
|
+
default = None
|
|
79
|
+
|
|
80
|
+
if type is None:
|
|
81
|
+
if default is not None:
|
|
82
|
+
type = cast(Type[Any], builtins_type(default)) # type: ignore
|
|
83
|
+
else:
|
|
84
|
+
type = str
|
|
85
|
+
|
|
86
|
+
if type not in SUPPORTED_TYPES:
|
|
87
|
+
types = humanize_type(
|
|
88
|
+
type.__name__ if hasattr(type, "__name__") else type
|
|
89
|
+
)
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Argument '{name}' has unsupported type '{types}'. "
|
|
92
|
+
"Only basic primitives are supported: str, int, float, bool. "
|
|
93
|
+
"For complex types, use child decorators (e.g., @to_path, "
|
|
94
|
+
"@to_datetime, ..)."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
super().__init__(
|
|
98
|
+
name=name,
|
|
99
|
+
param=param if param is not None else name,
|
|
100
|
+
nargs=nargs,
|
|
101
|
+
type=type,
|
|
102
|
+
help=help,
|
|
103
|
+
required=required,
|
|
104
|
+
default=default,
|
|
105
|
+
tags=tags,
|
|
106
|
+
)
|
|
107
|
+
self.extra_kwargs = kwargs
|
|
108
|
+
|
|
109
|
+
def get_display_name(self) -> str:
|
|
110
|
+
"""
|
|
111
|
+
Get a formatted display name for error messages.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
str:
|
|
115
|
+
The argument name in SCREAMING_SNAKE_CASE.
|
|
116
|
+
"""
|
|
117
|
+
return Casing.to_screaming_snake_case(self.name)
|
|
118
|
+
|
|
119
|
+
def load(
|
|
120
|
+
self,
|
|
121
|
+
value: str | int | float | bool | None,
|
|
122
|
+
context: Context,
|
|
123
|
+
*args: Any,
|
|
124
|
+
**kwargs: Any,
|
|
125
|
+
) -> Any:
|
|
126
|
+
"""
|
|
127
|
+
Load and return the CLI argument value.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
value (str | int | float | bool | None):
|
|
131
|
+
The parsed CLI argument value from Click.
|
|
132
|
+
context (Context):
|
|
133
|
+
The current context instance.
|
|
134
|
+
*args (Any):
|
|
135
|
+
Optional positional arguments.
|
|
136
|
+
**kwargs (Any):
|
|
137
|
+
Optional keyword arguments.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Any:
|
|
141
|
+
The argument value to inject into the function.
|
|
142
|
+
"""
|
|
143
|
+
return value
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def argument(
|
|
147
|
+
name: str,
|
|
148
|
+
param: str | None = None,
|
|
149
|
+
nargs: int = 1,
|
|
150
|
+
type: Type[str | int | float | bool] | None = None,
|
|
151
|
+
help: str | None = None,
|
|
152
|
+
required: bool = True,
|
|
153
|
+
default: Any = _MISSING,
|
|
154
|
+
tags: str | list[str] | None = None,
|
|
155
|
+
**kwargs: Any,
|
|
156
|
+
) -> Decorator:
|
|
157
|
+
"""
|
|
158
|
+
A `ParentNode` decorator to create a Click argument with value injection.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
name (str):
|
|
162
|
+
The argument name in snake_case.
|
|
163
|
+
Examples: "filename", "input_file"
|
|
164
|
+
param (str, optional):
|
|
165
|
+
Custom parameter name for the function.
|
|
166
|
+
If not provided, uses the name directly.
|
|
167
|
+
nargs (int):
|
|
168
|
+
Number of arguments to accept. Use `-1` for unlimited.
|
|
169
|
+
Defaults to `1`.
|
|
170
|
+
type (Type[str | int | float | bool] | None, optional):
|
|
171
|
+
The type to convert the value to.
|
|
172
|
+
help (str, optional):
|
|
173
|
+
Help text for this argument.
|
|
174
|
+
required (bool):
|
|
175
|
+
Whether this argument is required. Defaults to `True` unless
|
|
176
|
+
`default` is provided, which automatically makes it optional.
|
|
177
|
+
default (Any):
|
|
178
|
+
Default value if not provided. When set, automatically makes
|
|
179
|
+
the argument optional (`required=False`). Defaults to `None`.
|
|
180
|
+
tags (str | list[str], optional):
|
|
181
|
+
Tag(s) to associate with this argument for grouping.
|
|
182
|
+
**kwargs (Any):
|
|
183
|
+
Additional Click argument parameters.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Decorator:
|
|
187
|
+
A decorator function that registers the argument parent node.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
@argument("filename")
|
|
193
|
+
def my_func(filename):
|
|
194
|
+
print(f"File: {filename}")
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
@argument("files", nargs=-1, help="Files to process")
|
|
199
|
+
def my_func(files):
|
|
200
|
+
for file in files:
|
|
201
|
+
print(f"Processing: {file}")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
@argument("port", type=int, default=8080)
|
|
206
|
+
def my_func(port):
|
|
207
|
+
print(f"Port: {port}")
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# Custom parameter name
|
|
212
|
+
@argument("input_file", param="infile")
|
|
213
|
+
def my_func(infile): # param: infile, CLI: INPUT_FILE
|
|
214
|
+
print(f"Input: {infile}")
|
|
215
|
+
```
|
|
216
|
+
"""
|
|
217
|
+
return Argument.as_decorator(
|
|
218
|
+
name=name,
|
|
219
|
+
param=param,
|
|
220
|
+
nargs=nargs,
|
|
221
|
+
type=type,
|
|
222
|
+
help=help,
|
|
223
|
+
required=required,
|
|
224
|
+
default=default,
|
|
225
|
+
tags=tags,
|
|
226
|
+
**kwargs,
|
|
227
|
+
)
|