click-extended 0.1.1__tar.gz → 0.2.0__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 (26) hide show
  1. {click_extended-0.1.1/click_extended.egg-info → click_extended-0.2.0}/PKG-INFO +1 -1
  2. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended/errors.py +17 -6
  3. {click_extended-0.1.1 → click_extended-0.2.0/click_extended.egg-info}/PKG-INFO +1 -1
  4. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended.egg-info/SOURCES.txt +0 -1
  5. {click_extended-0.1.1 → click_extended-0.2.0}/pyproject.toml +1 -1
  6. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_child_node.py +10 -0
  7. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_option.py +19 -19
  8. click_extended-0.1.1/tests/test_transform.py +0 -333
  9. {click_extended-0.1.1 → click_extended-0.2.0}/AUTHORS.md +0 -0
  10. {click_extended-0.1.1 → click_extended-0.2.0}/LICENSE +0 -0
  11. {click_extended-0.1.1 → click_extended-0.2.0}/README.md +0 -0
  12. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended/__init__.py +0 -0
  13. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended/types.py +0 -0
  14. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended.egg-info/dependency_links.txt +0 -0
  15. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended.egg-info/requires.txt +0 -0
  16. {click_extended-0.1.1 → click_extended-0.2.0}/click_extended.egg-info/top_level.txt +0 -0
  17. {click_extended-0.1.1 → click_extended-0.2.0}/setup.cfg +0 -0
  18. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_argument.py +0 -0
  19. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_command.py +0 -0
  20. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_env.py +0 -0
  21. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_global_node.py +0 -0
  22. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_group.py +0 -0
  23. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_parent_node.py +0 -0
  24. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_root_node.py +0 -0
  25. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_tag.py +0 -0
  26. {click_extended-0.1.1 → click_extended-0.2.0}/tests/test_tree.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: click_extended
3
- Version: 0.1.1
3
+ Version: 0.2.0
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
@@ -10,6 +10,8 @@ from click import ClickException
10
10
  from click._compat import get_text_stderr
11
11
  from click.utils import echo
12
12
 
13
+ from click_extended.utils.format import format_list
14
+
13
15
 
14
16
  class ClickExtendedError(Exception):
15
17
  """Base exception for exceptions defined in the `click_extended` library."""
@@ -47,7 +49,7 @@ class ParameterError(ClickException):
47
49
  and adding parameter context information.
48
50
  """
49
51
 
50
- exit_code = 2 # Match Click's UsageError exit code
52
+ exit_code = 2
51
53
 
52
54
  def __init__(
53
55
  self,
@@ -59,9 +61,12 @@ class ParameterError(ClickException):
59
61
  Initialize a ParameterError.
60
62
 
61
63
  Args:
62
- message: The error message from the validator/transformer.
63
- param_hint: The parameter name (e.g., '--config', 'PATH').
64
- ctx: The Click context for displaying usage information.
64
+ message (str):
65
+ The error message from the validator/transformer.
66
+ param_hint (str, optional):
67
+ The parameter name (e.g., '--config', 'PATH').
68
+ ctx (click.Context, optional):
69
+ The Click context for displaying usage information.
65
70
  """
66
71
  super().__init__(message)
67
72
  self.param_hint = param_hint
@@ -235,12 +240,18 @@ class TypeMismatchError(ClickExtendedError):
235
240
  """Get type name, handling both regular types and UnionType."""
236
241
  return getattr(type_obj, "__name__", str(type_obj))
237
242
 
238
- type_names = ", ".join(get_type_name(t) for t in supported_types)
239
243
  parent_type_name = get_type_name(parent_type) if parent_type else "None"
240
244
 
245
+ type_names = [get_type_name(t) for t in supported_types]
246
+ formatted_types = format_list(
247
+ type_names,
248
+ prefix_singular="Supported type is ",
249
+ prefix_plural="Supported types are ",
250
+ )
251
+
241
252
  message = (
242
253
  f"Decorator '{name}' does not support "
243
254
  f"parent '{parent_name}' with type '{parent_type_name}'. "
244
- f"Supported types: {type_names}"
255
+ f"{formatted_types}"
245
256
  )
246
257
  super().__init__(message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: click_extended
3
- Version: 0.1.1
3
+ Version: 0.2.0
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
@@ -20,5 +20,4 @@ tests/test_option.py
20
20
  tests/test_parent_node.py
21
21
  tests/test_root_node.py
22
22
  tests/test_tag.py
23
- tests/test_transform.py
24
23
  tests/test_tree.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "click_extended"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "An extension to Click with additional features like automatic async support, aliasing and a modular decorator system."
5
5
  authors = [
6
6
  { name = "Marcus Fredriksson", email = "marcus@marcusfredriksson.com" },
@@ -523,6 +523,8 @@ class TestChildNodeTypeValidation:
523
523
  parent = MagicMock()
524
524
  parent.name = "count"
525
525
  parent.type = int
526
+ parent.nargs = 1
527
+ parent.multiple = False
526
528
 
527
529
  # Should not raise
528
530
  node.validate_type(parent)
@@ -540,6 +542,8 @@ class TestChildNodeTypeValidation:
540
542
  parent = MagicMock()
541
543
  parent.name = "name"
542
544
  parent.type = str
545
+ parent.nargs = 1
546
+ parent.multiple = False
543
547
 
544
548
  with pytest.raises(TypeMismatchError) as exc_info:
545
549
  node.validate_type(parent)
@@ -581,10 +585,14 @@ class TestChildNodeTypeValidation:
581
585
  parent_int = MagicMock()
582
586
  parent_int.name = "count"
583
587
  parent_int.type = int
588
+ parent_int.nargs = 1
589
+ parent_int.multiple = False
584
590
 
585
591
  parent_float = MagicMock()
586
592
  parent_float.name = "ratio"
587
593
  parent_float.type = float
594
+ parent_float.nargs = 1
595
+ parent_float.multiple = False
588
596
 
589
597
  # Both should pass
590
598
  node.validate_type(parent_int)
@@ -605,6 +613,8 @@ class TestChildNodeTypeValidation:
605
613
  parent = MagicMock()
606
614
  parent.name = "username"
607
615
  parent.type = str
616
+ parent.nargs = 1 # Add nargs attribute
617
+ parent.multiple = False
608
618
 
609
619
  with pytest.raises(TypeMismatchError) as exc_info:
610
620
  node.validate_type(parent)
@@ -17,7 +17,7 @@ class TestOptionInitialization:
17
17
  assert opt.short is None
18
18
  assert opt.is_flag is False
19
19
  assert opt.type == str # Inferred as str when no type/default
20
- assert opt.multiple is False
20
+ assert opt.nargs == 1
21
21
  assert opt.help is None
22
22
  assert opt.required is False
23
23
  assert opt.default is None
@@ -29,7 +29,7 @@ class TestOptionInitialization:
29
29
  short="-p",
30
30
  is_flag=False,
31
31
  type=int,
32
- multiple=False,
32
+ nargs=1,
33
33
  help="Port number",
34
34
  required=True,
35
35
  default=8080,
@@ -38,7 +38,7 @@ class TestOptionInitialization:
38
38
  assert opt.short == "-p"
39
39
  assert opt.is_flag is False
40
40
  assert opt.type == int
41
- assert opt.multiple is False
41
+ assert opt.nargs == 1
42
42
  assert opt.help == "Port number"
43
43
  assert opt.required is True
44
44
  assert opt.default == 8080
@@ -65,9 +65,9 @@ class TestOptionInitialization:
65
65
  assert opt.type == int
66
66
 
67
67
  def test_init_with_multiple_true(self) -> None:
68
- """Test initializing with multiple=True."""
69
- opt = Option(long="--tag", multiple=True)
70
- assert opt.multiple is True
68
+ """Test initializing with nargs=-1 for unlimited values."""
69
+ opt = Option(long="--tag", nargs=-1)
70
+ assert opt.nargs == -1
71
71
 
72
72
  def test_init_with_extra_kwargs(self) -> None:
73
73
  """Test initializing with extra kwargs."""
@@ -412,16 +412,16 @@ class TestOptionDecoratorFunction:
412
412
  def test_option_decorator_with_multiple(
413
413
  self, mock_queue: MagicMock
414
414
  ) -> None:
415
- """Test option decorator with multiple parameter."""
415
+ """Test option decorator with nargs=-1 parameter."""
416
416
 
417
- @option("--tag", multiple=True)
417
+ @option("--tag", nargs=-1)
418
418
  def test_func() -> str: # type: ignore
419
419
  return "test"
420
420
 
421
421
  mock_queue.assert_called_once()
422
422
  args, _ = mock_queue.call_args
423
423
  opt_instance = args[0]
424
- assert opt_instance.multiple is True
424
+ assert opt_instance.nargs == -1
425
425
 
426
426
  @patch("click_extended.core._parent_node.queue_parent")
427
427
  def test_option_decorator_with_help(self, mock_queue: MagicMock) -> None:
@@ -473,7 +473,7 @@ class TestOptionDecoratorFunction:
473
473
  short="-p",
474
474
  is_flag=False,
475
475
  type=int,
476
- multiple=False,
476
+ nargs=1,
477
477
  help="Port number",
478
478
  required=True,
479
479
  default=8080,
@@ -666,17 +666,17 @@ class TestOptionMultiple:
666
666
  def test_multiple_false_by_default(self) -> None:
667
667
  """Test multiple defaults to False."""
668
668
  opt = Option(long="--tag")
669
- assert opt.multiple is False
669
+ assert opt.nargs == 1
670
670
 
671
671
  def test_multiple_true(self) -> None:
672
672
  """Test multiple can be set to True."""
673
- opt = Option(long="--tag", multiple=True)
674
- assert opt.multiple is True
673
+ opt = Option(long="--tag", nargs=-1)
674
+ assert opt.nargs == -1
675
675
 
676
676
  def test_multiple_with_type(self) -> None:
677
677
  """Test multiple with type parameter."""
678
- opt = Option(long="--tag", multiple=True, type=str)
679
- assert opt.multiple is True
678
+ opt = Option(long="--tag", nargs=-1, type=str)
679
+ assert opt.nargs == -1
680
680
  assert opt.type == str
681
681
 
682
682
 
@@ -845,7 +845,7 @@ class TestOptionIntegration:
845
845
  short="-p",
846
846
  is_flag=False,
847
847
  type=int,
848
- multiple=False,
848
+ nargs=1,
849
849
  help="Port number",
850
850
  required=False,
851
851
  default=8080,
@@ -855,7 +855,7 @@ class TestOptionIntegration:
855
855
  assert opt.short == "-p"
856
856
  assert opt.is_flag is False
857
857
  assert opt.type == int
858
- assert opt.multiple is False
858
+ assert opt.nargs == 1
859
859
  assert opt.help == "Port number"
860
860
  assert opt.required is False
861
861
  assert opt.default == 8080
@@ -873,10 +873,10 @@ class TestOptionIntegration:
873
873
  opt = Option(
874
874
  long="--tag",
875
875
  short="-t",
876
- multiple=True,
876
+ nargs=-1,
877
877
  help="Add tags (can be used multiple times)",
878
878
  )
879
- assert opt.multiple is True
879
+ assert opt.nargs == -1
880
880
  assert opt.name == "tag"
881
881
 
882
882
  def test_typed_option_typical_usage(self) -> None:
@@ -1,333 +0,0 @@
1
- """Test the Transform class."""
2
-
3
- from click_extended.utils.transform import Transform
4
-
5
-
6
- class Case:
7
- """A case to test for all transform methods."""
8
-
9
- def __init__(
10
- self,
11
- value: str,
12
- snake_case: str,
13
- screaming_snake_case: str,
14
- camel_case: str,
15
- pascal_case: str,
16
- kebab_case: str,
17
- train_case: str,
18
- flat_case: str,
19
- dot_case: str,
20
- title_case: str,
21
- path_case: str,
22
- ):
23
- """
24
- Initialize a new `Case` instance.
25
-
26
- Args:
27
- value (str):
28
- The value to test.
29
- snake_case (str):
30
- The expected result for the to_snake_case method.
31
- screaming_snake_case (str):
32
- The expected result for the to_screaming_snake_case method.
33
- camel_case (str):
34
- The expected result for the to_camel_case method.
35
- pascal_case (str):
36
- The expected result for the to_pascal_case method.
37
- kebab_case (str):
38
- The expected result for the to_kebab_case method.
39
- train_case (str):
40
- The expected result for the to_train_case method.
41
- flat_case (str):
42
- The expected result for the to_flat_case method.
43
- dot_case (str):
44
- The expected result for the to_dot_case method.
45
- title_case (str):
46
- The expected result for the to_title_case method.
47
- path_case (str):
48
- The expected result for the to_path_case method.
49
- """
50
- self.value = value
51
- self.snake_case = snake_case
52
- self.screaming_snake_case = screaming_snake_case
53
- self.camel_case = camel_case
54
- self.pascal_case = pascal_case
55
- self.kebab_case = kebab_case
56
- self.train_case = train_case
57
- self.flat_case = flat_case
58
- self.dot_case = dot_case
59
- self.title_case = title_case
60
- self.path_case = path_case
61
-
62
-
63
- cases: list[Case] = [
64
- Case(
65
- value="",
66
- snake_case="",
67
- screaming_snake_case="",
68
- camel_case="",
69
- pascal_case="",
70
- kebab_case="",
71
- train_case="",
72
- flat_case="",
73
- dot_case="",
74
- title_case="",
75
- path_case="",
76
- ),
77
- Case(
78
- value="MISSING_ENV2",
79
- snake_case="missing_env2",
80
- screaming_snake_case="MISSING_ENV2",
81
- camel_case="missingEnv2",
82
- pascal_case="MissingEnv2",
83
- kebab_case="missing-env2",
84
- train_case="Missing-Env2",
85
- flat_case="missingenv2",
86
- dot_case="missing.env2",
87
- title_case="Missing Env2",
88
- path_case="MISSING/ENV2",
89
- ),
90
- Case(
91
- value="hello",
92
- snake_case="hello",
93
- screaming_snake_case="HELLO",
94
- camel_case="hello",
95
- pascal_case="Hello",
96
- kebab_case="hello",
97
- train_case="Hello",
98
- flat_case="hello",
99
- dot_case="hello",
100
- title_case="Hello",
101
- path_case="hello",
102
- ),
103
- Case(
104
- value="thisIsCamelCase",
105
- snake_case="this_is_camel_case",
106
- screaming_snake_case="THIS_IS_CAMEL_CASE",
107
- camel_case="thisIsCamelCase",
108
- pascal_case="ThisIsCamelCase",
109
- kebab_case="this-is-camel-case",
110
- train_case="This-Is-Camel-Case",
111
- flat_case="thisiscamelcase",
112
- dot_case="this.is.camel.case",
113
- title_case="This Is Camel Case",
114
- path_case="this/Is/Camel/Case",
115
- ),
116
- Case(
117
- value="test123case",
118
- snake_case="test123_case",
119
- screaming_snake_case="TEST123_CASE",
120
- camel_case="test123Case",
121
- pascal_case="Test123Case",
122
- kebab_case="test123-case",
123
- train_case="Test123-Case",
124
- flat_case="test123case",
125
- dot_case="test123.case",
126
- title_case="Test123 Case",
127
- path_case="test123/case",
128
- ),
129
- Case(
130
- value=" spaces around ",
131
- snake_case="spaces_around",
132
- screaming_snake_case="SPACES_AROUND",
133
- camel_case="spacesAround",
134
- pascal_case="SpacesAround",
135
- kebab_case="spaces-around",
136
- train_case="Spaces-Around",
137
- flat_case="spacesaround",
138
- dot_case="spaces.around",
139
- title_case="Spaces Around",
140
- path_case="spaces/around",
141
- ),
142
- Case(
143
- value="Hello, world",
144
- snake_case="hello_world",
145
- screaming_snake_case="HELLO_WORLD",
146
- camel_case="helloWorld",
147
- pascal_case="HelloWorld",
148
- kebab_case="hello-world",
149
- train_case="Hello-World",
150
- flat_case="helloworld",
151
- dot_case="hello.world",
152
- title_case="Hello World",
153
- path_case="Hello/world",
154
- ),
155
- Case(
156
- value="_Difficult, string!!!",
157
- snake_case="difficult_string",
158
- screaming_snake_case="DIFFICULT_STRING",
159
- camel_case="difficultString",
160
- pascal_case="DifficultString",
161
- kebab_case="difficult-string",
162
- train_case="Difficult-String",
163
- flat_case="difficultstring",
164
- dot_case="difficult.string",
165
- title_case="Difficult String",
166
- path_case="Difficult/string",
167
- ),
168
- Case(
169
- value="HELLO WORLD",
170
- snake_case="hello_world",
171
- screaming_snake_case="HELLO_WORLD",
172
- camel_case="helloWorld",
173
- pascal_case="HelloWorld",
174
- kebab_case="hello-world",
175
- train_case="Hello-World",
176
- flat_case="helloworld",
177
- dot_case="hello.world",
178
- title_case="Hello World",
179
- path_case="HELLO/WORLD",
180
- ),
181
- Case(
182
- value="hello___world---test",
183
- snake_case="hello_world_test",
184
- screaming_snake_case="HELLO_WORLD_TEST",
185
- camel_case="helloWorldTest",
186
- pascal_case="HelloWorldTest",
187
- kebab_case="hello-world-test",
188
- train_case="Hello-World-Test",
189
- flat_case="helloworldtest",
190
- dot_case="hello.world.test",
191
- title_case="Hello World Test",
192
- path_case="hello/world/test",
193
- ),
194
- Case(
195
- value="v2.0.1",
196
- snake_case="v2_0_1",
197
- screaming_snake_case="V2_0_1",
198
- camel_case="v201",
199
- pascal_case="V201",
200
- kebab_case="v2-0-1",
201
- train_case="V2-0-1",
202
- flat_case="v201",
203
- dot_case="v2.0.1",
204
- title_case="V2 0 1",
205
- path_case="v2/0/1",
206
- ),
207
- Case(
208
- value="already_snake_case",
209
- snake_case="already_snake_case",
210
- screaming_snake_case="ALREADY_SNAKE_CASE",
211
- camel_case="alreadySnakeCase",
212
- pascal_case="AlreadySnakeCase",
213
- kebab_case="already-snake-case",
214
- train_case="Already-Snake-Case",
215
- flat_case="alreadysnakecase",
216
- dot_case="already.snake.case",
217
- title_case="Already Snake Case",
218
- path_case="already/snake/case",
219
- ),
220
- Case(
221
- value="PascalCaseInput",
222
- snake_case="pascal_case_input",
223
- screaming_snake_case="PASCAL_CASE_INPUT",
224
- camel_case="pascalCaseInput",
225
- pascal_case="PascalCaseInput",
226
- kebab_case="pascal-case-input",
227
- train_case="Pascal-Case-Input",
228
- flat_case="pascalcaseinput",
229
- dot_case="pascal.case.input",
230
- title_case="Pascal Case Input",
231
- path_case="Pascal/Case/Input",
232
- ),
233
- ]
234
-
235
-
236
- class TestSnakeCase:
237
- """Test the snake case methods."""
238
-
239
- def test_cases(self) -> None:
240
- """Test cases."""
241
- for case in cases:
242
- t = Transform(case.value)
243
- assert t.to_snake_case() == case.snake_case
244
-
245
-
246
- class TestScreamingSnakeCase:
247
- """Test the screaming snake case methods."""
248
-
249
- def test_cases(self) -> None:
250
- """Test cases."""
251
- for case in cases:
252
- t = Transform(case.value)
253
- assert t.to_screaming_snake_case() == case.screaming_snake_case
254
-
255
-
256
- class TestCamelCase:
257
- """Test the camel case methods."""
258
-
259
- def test_cases(self) -> None:
260
- """Test cases."""
261
- for case in cases:
262
- t = Transform(case.value)
263
- assert t.to_camel_case() == case.camel_case
264
-
265
-
266
- class TestPascalCase:
267
- """Test the pascal case methods."""
268
-
269
- def test_cases(self) -> None:
270
- """Test cases."""
271
- for case in cases:
272
- t = Transform(case.value)
273
- assert t.to_pascal_case() == case.pascal_case
274
-
275
-
276
- class TestKebabCase:
277
- """Test the kebab case methods."""
278
-
279
- def test_cases(self) -> None:
280
- """Test cases."""
281
- for case in cases:
282
- t = Transform(case.value)
283
- assert t.to_kebab_case() == case.kebab_case
284
-
285
-
286
- class TestTrainCase:
287
- """Test the train case methods."""
288
-
289
- def test_cases(self) -> None:
290
- """Test cases."""
291
- for case in cases:
292
- t = Transform(case.value)
293
- assert t.to_train_case() == case.train_case
294
-
295
-
296
- class TestFlatCaase:
297
- """Test the flat case methods."""
298
-
299
- def test_cases(self) -> None:
300
- """Test cases."""
301
- for case in cases:
302
- t = Transform(case.value)
303
- assert t.to_flat_case() == case.flat_case
304
-
305
-
306
- class TestDotCase:
307
- """Test the dot case methods."""
308
-
309
- def test_cases(self) -> None:
310
- """Test cases."""
311
- for case in cases:
312
- t = Transform(case.value)
313
- assert t.to_dot_case() == case.dot_case
314
-
315
-
316
- class TestTitleCase:
317
- """Test the pascal case methods."""
318
-
319
- def test_cases(self) -> None:
320
- """Test cases."""
321
- for case in cases:
322
- t = Transform(case.value)
323
- assert t.to_title_case() == case.title_case
324
-
325
-
326
- class TestPathCase:
327
- """Test the path case methods."""
328
-
329
- def test_cases(self) -> None:
330
- """Test cases."""
331
- for case in cases:
332
- t = Transform(case.value)
333
- assert t.to_path_case() == case.path_case
File without changes
File without changes
File without changes