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.
Files changed (168) hide show
  1. {click_extended-0.4.0/click_extended.egg-info → click_extended-1.0.1}/PKG-INFO +100 -29
  2. {click_extended-0.4.0 → click_extended-1.0.1}/README.md +92 -28
  3. click_extended-1.0.1/click_extended/__init__.py +21 -0
  4. click_extended-1.0.1/click_extended/classes.py +25 -0
  5. click_extended-1.0.1/click_extended/core/__init__.py +10 -0
  6. click_extended-1.0.1/click_extended/core/decorators/__init__.py +21 -0
  7. click_extended-1.0.1/click_extended/core/decorators/argument.py +227 -0
  8. click_extended-1.0.1/click_extended/core/decorators/command.py +93 -0
  9. click_extended-1.0.1/click_extended/core/decorators/env.py +155 -0
  10. click_extended-1.0.1/click_extended/core/decorators/group.py +96 -0
  11. click_extended-1.0.1/click_extended/core/decorators/option.py +347 -0
  12. click_extended-1.0.1/click_extended/core/decorators/prompt.py +69 -0
  13. click_extended-1.0.1/click_extended/core/decorators/selection.py +155 -0
  14. click_extended-1.0.1/click_extended/core/decorators/tag.py +109 -0
  15. click_extended-1.0.1/click_extended/core/nodes/__init__.py +21 -0
  16. click_extended-1.0.1/click_extended/core/nodes/_root_node.py +1012 -0
  17. click_extended-1.0.1/click_extended/core/nodes/argument_node.py +165 -0
  18. click_extended-1.0.1/click_extended/core/nodes/child_node.py +555 -0
  19. click_extended-1.0.1/click_extended/core/nodes/child_validation_node.py +100 -0
  20. click_extended-1.0.1/click_extended/core/nodes/node.py +55 -0
  21. click_extended-1.0.1/click_extended/core/nodes/option_node.py +205 -0
  22. click_extended-1.0.1/click_extended/core/nodes/parent_node.py +220 -0
  23. click_extended-1.0.1/click_extended/core/nodes/validation_node.py +124 -0
  24. click_extended-1.0.1/click_extended/core/other/__init__.py +7 -0
  25. click_extended-1.0.1/click_extended/core/other/_click_command.py +60 -0
  26. click_extended-1.0.1/click_extended/core/other/_click_group.py +246 -0
  27. click_extended-1.0.1/click_extended/core/other/_tree.py +491 -0
  28. click_extended-1.0.1/click_extended/core/other/context.py +496 -0
  29. click_extended-1.0.1/click_extended/decorators/__init__.py +29 -0
  30. click_extended-1.0.1/click_extended/decorators/check/__init__.py +57 -0
  31. click_extended-1.0.1/click_extended/decorators/check/conflicts.py +149 -0
  32. click_extended-1.0.1/click_extended/decorators/check/contains.py +69 -0
  33. click_extended-1.0.1/click_extended/decorators/check/dependencies.py +115 -0
  34. click_extended-1.0.1/click_extended/decorators/check/divisible_by.py +48 -0
  35. click_extended-1.0.1/click_extended/decorators/check/ends_with.py +85 -0
  36. click_extended-1.0.1/click_extended/decorators/check/exclusive.py +75 -0
  37. click_extended-1.0.1/click_extended/decorators/check/falsy.py +37 -0
  38. click_extended-1.0.1/click_extended/decorators/check/is_email.py +43 -0
  39. click_extended-1.0.1/click_extended/decorators/check/is_hex_color.py +41 -0
  40. click_extended-1.0.1/click_extended/decorators/check/is_hostname.py +47 -0
  41. click_extended-1.0.1/click_extended/decorators/check/is_ipv4.py +46 -0
  42. click_extended-1.0.1/click_extended/decorators/check/is_ipv6.py +46 -0
  43. click_extended-1.0.1/click_extended/decorators/check/is_json.py +40 -0
  44. click_extended-1.0.1/click_extended/decorators/check/is_mac_address.py +40 -0
  45. click_extended-1.0.1/click_extended/decorators/check/is_negative.py +37 -0
  46. click_extended-1.0.1/click_extended/decorators/check/is_non_zero.py +37 -0
  47. click_extended-1.0.1/click_extended/decorators/check/is_port.py +39 -0
  48. click_extended-1.0.1/click_extended/decorators/check/is_positive.py +37 -0
  49. click_extended-1.0.1/click_extended/decorators/check/is_url.py +75 -0
  50. click_extended-1.0.1/click_extended/decorators/check/is_uuid.py +40 -0
  51. click_extended-1.0.1/click_extended/decorators/check/length.py +68 -0
  52. click_extended-1.0.1/click_extended/decorators/check/not_empty.py +49 -0
  53. click_extended-1.0.1/click_extended/decorators/check/regex.py +47 -0
  54. click_extended-1.0.1/click_extended/decorators/check/requires.py +190 -0
  55. click_extended-1.0.1/click_extended/decorators/check/starts_with.py +87 -0
  56. click_extended-1.0.1/click_extended/decorators/check/truthy.py +37 -0
  57. click_extended-1.0.1/click_extended/decorators/compare/__init__.py +15 -0
  58. click_extended-1.0.1/click_extended/decorators/compare/at_least.py +57 -0
  59. click_extended-1.0.1/click_extended/decorators/compare/at_most.py +57 -0
  60. click_extended-1.0.1/click_extended/decorators/compare/between.py +119 -0
  61. click_extended-1.0.1/click_extended/decorators/compare/greater_than.py +183 -0
  62. click_extended-1.0.1/click_extended/decorators/compare/less_than.py +183 -0
  63. click_extended-1.0.1/click_extended/decorators/convert/__init__.py +31 -0
  64. click_extended-1.0.1/click_extended/decorators/convert/convert_angle.py +94 -0
  65. click_extended-1.0.1/click_extended/decorators/convert/convert_area.py +123 -0
  66. click_extended-1.0.1/click_extended/decorators/convert/convert_bits.py +211 -0
  67. click_extended-1.0.1/click_extended/decorators/convert/convert_distance.py +154 -0
  68. click_extended-1.0.1/click_extended/decorators/convert/convert_energy.py +155 -0
  69. click_extended-1.0.1/click_extended/decorators/convert/convert_power.py +128 -0
  70. click_extended-1.0.1/click_extended/decorators/convert/convert_pressure.py +131 -0
  71. click_extended-1.0.1/click_extended/decorators/convert/convert_speed.py +122 -0
  72. click_extended-1.0.1/click_extended/decorators/convert/convert_temperature.py +89 -0
  73. click_extended-1.0.1/click_extended/decorators/convert/convert_time.py +108 -0
  74. click_extended-1.0.1/click_extended/decorators/convert/convert_volume.py +218 -0
  75. click_extended-1.0.1/click_extended/decorators/convert/convert_weight.py +158 -0
  76. click_extended-1.0.1/click_extended/decorators/load/__init__.py +13 -0
  77. click_extended-1.0.1/click_extended/decorators/load/load_csv.py +117 -0
  78. click_extended-1.0.1/click_extended/decorators/load/load_json.py +61 -0
  79. click_extended-1.0.1/click_extended/decorators/load/load_toml.py +47 -0
  80. click_extended-1.0.1/click_extended/decorators/load/load_yaml.py +72 -0
  81. click_extended-1.0.1/click_extended/decorators/math/__init__.py +37 -0
  82. click_extended-1.0.1/click_extended/decorators/math/absolute.py +35 -0
  83. click_extended-1.0.1/click_extended/decorators/math/add.py +48 -0
  84. click_extended-1.0.1/click_extended/decorators/math/ceil.py +36 -0
  85. click_extended-1.0.1/click_extended/decorators/math/clamp.py +51 -0
  86. click_extended-1.0.1/click_extended/decorators/math/divide.py +42 -0
  87. click_extended-1.0.1/click_extended/decorators/math/floor.py +36 -0
  88. click_extended-1.0.1/click_extended/decorators/math/maximum.py +39 -0
  89. click_extended-1.0.1/click_extended/decorators/math/minimum.py +39 -0
  90. click_extended-1.0.1/click_extended/decorators/math/modulo.py +39 -0
  91. click_extended-1.0.1/click_extended/decorators/math/multiply.py +51 -0
  92. click_extended-1.0.1/click_extended/decorators/math/normalize.py +76 -0
  93. click_extended-1.0.1/click_extended/decorators/math/power.py +39 -0
  94. click_extended-1.0.1/click_extended/decorators/math/rounded.py +39 -0
  95. click_extended-1.0.1/click_extended/decorators/math/sqrt.py +39 -0
  96. click_extended-1.0.1/click_extended/decorators/math/subtract.py +39 -0
  97. click_extended-1.0.1/click_extended/decorators/math/to_percent.py +63 -0
  98. click_extended-1.0.1/click_extended/decorators/misc/__init__.py +17 -0
  99. click_extended-1.0.1/click_extended/decorators/misc/choice.py +139 -0
  100. click_extended-1.0.1/click_extended/decorators/misc/confirm_if.py +147 -0
  101. click_extended-1.0.1/click_extended/decorators/misc/default.py +95 -0
  102. click_extended-1.0.1/click_extended/decorators/misc/deprecated.py +131 -0
  103. click_extended-1.0.1/click_extended/decorators/misc/experimental.py +79 -0
  104. click_extended-1.0.1/click_extended/decorators/misc/now.py +42 -0
  105. click_extended-1.0.1/click_extended/decorators/random/__init__.py +21 -0
  106. click_extended-1.0.1/click_extended/decorators/random/random_bool.py +49 -0
  107. click_extended-1.0.1/click_extended/decorators/random/random_choice.py +63 -0
  108. click_extended-1.0.1/click_extended/decorators/random/random_datetime.py +140 -0
  109. click_extended-1.0.1/click_extended/decorators/random/random_float.py +62 -0
  110. click_extended-1.0.1/click_extended/decorators/random/random_integer.py +56 -0
  111. click_extended-1.0.1/click_extended/decorators/random/random_prime.py +196 -0
  112. click_extended-1.0.1/click_extended/decorators/random/random_string.py +77 -0
  113. click_extended-1.0.1/click_extended/decorators/random/random_uuid.py +119 -0
  114. click_extended-1.0.1/click_extended/decorators/transform/__init__.py +71 -0
  115. click_extended-1.0.1/click_extended/decorators/transform/add_prefix.py +58 -0
  116. click_extended-1.0.1/click_extended/decorators/transform/add_suffix.py +58 -0
  117. click_extended-1.0.1/click_extended/decorators/transform/apply.py +35 -0
  118. click_extended-1.0.1/click_extended/decorators/transform/basename.py +44 -0
  119. click_extended-1.0.1/click_extended/decorators/transform/dirname.py +44 -0
  120. click_extended-1.0.1/click_extended/decorators/transform/expand_vars.py +36 -0
  121. click_extended-1.0.1/click_extended/decorators/transform/remove_prefix.py +57 -0
  122. click_extended-1.0.1/click_extended/decorators/transform/remove_suffix.py +57 -0
  123. click_extended-1.0.1/click_extended/decorators/transform/replace.py +46 -0
  124. click_extended-1.0.1/click_extended/decorators/transform/slugify.py +45 -0
  125. click_extended-1.0.1/click_extended/decorators/transform/split.py +43 -0
  126. click_extended-1.0.1/click_extended/decorators/transform/strip.py +148 -0
  127. click_extended-1.0.1/click_extended/decorators/transform/to_case.py +216 -0
  128. click_extended-1.0.1/click_extended/decorators/transform/to_date.py +75 -0
  129. click_extended-1.0.1/click_extended/decorators/transform/to_datetime.py +83 -0
  130. click_extended-1.0.1/click_extended/decorators/transform/to_path.py +274 -0
  131. click_extended-1.0.1/click_extended/decorators/transform/to_time.py +77 -0
  132. click_extended-1.0.1/click_extended/decorators/transform/to_timestamp.py +114 -0
  133. click_extended-1.0.1/click_extended/decorators/transform/truncate.py +47 -0
  134. {click_extended-0.4.0 → click_extended-1.0.1}/click_extended/types.py +1 -1
  135. click_extended-1.0.1/click_extended/utils/__init__.py +13 -0
  136. click_extended-1.0.1/click_extended/utils/casing.py +169 -0
  137. click_extended-1.0.1/click_extended/utils/checks.py +48 -0
  138. click_extended-1.0.1/click_extended/utils/dispatch.py +1016 -0
  139. click_extended-1.0.1/click_extended/utils/format.py +101 -0
  140. click_extended-1.0.1/click_extended/utils/humanize.py +209 -0
  141. click_extended-1.0.1/click_extended/utils/naming.py +238 -0
  142. click_extended-1.0.1/click_extended/utils/process.py +294 -0
  143. click_extended-1.0.1/click_extended/utils/selection.py +267 -0
  144. click_extended-1.0.1/click_extended/utils/time.py +46 -0
  145. {click_extended-0.4.0 → click_extended-1.0.1/click_extended.egg-info}/PKG-INFO +100 -29
  146. click_extended-1.0.1/click_extended.egg-info/SOURCES.txt +153 -0
  147. {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/requires.txt +8 -0
  148. {click_extended-0.4.0 → click_extended-1.0.1}/pyproject.toml +13 -4
  149. {click_extended-0.4.0 → click_extended-1.0.1}/tests/test_lifecycle.py +7 -10
  150. click_extended-0.4.0/click_extended/__init__.py +0 -17
  151. click_extended-0.4.0/click_extended/classes.py +0 -21
  152. click_extended-0.4.0/click_extended.egg-info/SOURCES.txt +0 -24
  153. click_extended-0.4.0/tests/test_argument_node.py +0 -1059
  154. click_extended-0.4.0/tests/test_child_node.py +0 -2633
  155. click_extended-0.4.0/tests/test_context.py +0 -867
  156. click_extended-0.4.0/tests/test_errors.py +0 -478
  157. click_extended-0.4.0/tests/test_node.py +0 -112
  158. click_extended-0.4.0/tests/test_option_node.py +0 -1346
  159. click_extended-0.4.0/tests/test_parent_node.py +0 -947
  160. click_extended-0.4.0/tests/test_root_node.py +0 -2240
  161. click_extended-0.4.0/tests/test_tag.py +0 -766
  162. click_extended-0.4.0/tests/test_tree.py +0 -1481
  163. {click_extended-0.4.0 → click_extended-1.0.1}/AUTHORS.md +0 -0
  164. {click_extended-0.4.0 → click_extended-1.0.1}/LICENSE +0 -0
  165. {click_extended-0.4.0 → click_extended-1.0.1}/click_extended/errors.py +0 -0
  166. {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/dependency_links.txt +0 -0
  167. {click_extended-0.4.0 → click_extended-1.0.1}/click_extended.egg-info/top_level.txt +0 -0
  168. {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.4.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
  ![Banner](./assets/click-extended-banner.png)
@@ -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
- # $ python cli.py "Hello world"
121
- # Hello world
129
+ ```bash
130
+ $ python cli.py "Hello world"
131
+ Hello world
132
+ ```
122
133
 
123
- # $ python cli.py "Hello world" --count 3
124
- # Hello world
125
- # Hello world
126
- # Hello world
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
- # $ python cli.py my_function "Hello world"
151
- # Running initialization code...
152
- # Hello world
163
+ ```bash
164
+ $ python cli.py my_function "Hello world"
165
+ Running initialization code...
166
+ Hello world
167
+ ```
153
168
 
154
- # $ python cli.py my_function "Hello world" --count 3
155
- # Running initialization code...
156
- # Hello world
157
- # Hello world
158
- # Hello world
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
- # $ python cli.py my_function_1
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
- # $ API_KEY=api-key python cli.py my_function_1
189
- # The API key is: api-key
240
+ ### Pre-Built Children
190
241
 
191
- # $ python cli.py my_function_2
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
- # $ API_KEY=api-key python cli.py my_function_2
195
- # The API key is: api-key
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 Children
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 handle_primitive(
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
- # $ python cli.py my_function valid
241
- # The value 'VALID' should be uppercase.
308
+ ```bash
309
+ $ python cli.py my_function valid
310
+ The value 'VALID' should be uppercase.
311
+ ```
242
312
 
243
- # $ python cli.py my_function invalid
244
- # ValueError (my_function): "The value 'invalid' is not valid"
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
- # $ python cli.py "Hello world"
58
- # Hello world
59
+ ```bash
60
+ $ python cli.py "Hello world"
61
+ Hello world
62
+ ```
59
63
 
60
- # $ python cli.py "Hello world" --count 3
61
- # Hello world
62
- # Hello world
63
- # Hello world
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
- # $ python cli.py my_function "Hello world"
88
- # Running initialization code...
89
- # Hello world
93
+ ```bash
94
+ $ python cli.py my_function "Hello world"
95
+ Running initialization code...
96
+ Hello world
97
+ ```
90
98
 
91
- # $ python cli.py my_function "Hello world" --count 3
92
- # Running initialization code...
93
- # Hello world
94
- # Hello world
95
- # Hello world
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
- # $ python cli.py my_function_1
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
- # $ API_KEY=api-key python cli.py my_function_1
126
- # The API key is: api-key
170
+ ### Pre-Built Children
127
171
 
128
- # $ python cli.py my_function_2
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
- # $ API_KEY=api-key python cli.py my_function_2
132
- # The API key is: api-key
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 Children
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 handle_primitive(
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
- # $ python cli.py my_function valid
178
- # The value 'VALID' should be uppercase.
238
+ ```bash
239
+ $ python cli.py my_function valid
240
+ The value 'VALID' should be uppercase.
241
+ ```
179
242
 
180
- # $ python cli.py my_function invalid
181
- # ValueError (my_function): "The value 'invalid' is not valid"
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
+ )