click-extended 1.0.8__py3-none-any.whl → 1.0.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,7 @@
4
4
  # pylint: disable=too-many-positional-arguments
5
5
  # pylint: disable=too-many-locals
6
6
  # pylint: disable=too-many-branches
7
+ # pylint: disable=too-many-statements
7
8
  # pylint: disable=redefined-builtin
8
9
 
9
10
  import asyncio
@@ -82,6 +83,21 @@ class Option(OptionNode):
82
83
  **kwargs (Any):
83
84
  Additional Click option parameters.
84
85
  """
86
+ if is_short_flag(name):
87
+ long_flag = next(
88
+ (flag for flag in flags if is_long_flag(flag)), None
89
+ )
90
+ if long_flag is None:
91
+ raise ValueError(
92
+ f"Short flag '{name}' provided without a long flag. "
93
+ "Provide a long flag to derive the option name, e.g. "
94
+ f"@option('{name}', '--flag'), or pass an explicit "
95
+ f"snake_case name like @option('flag', '{name}')."
96
+ )
97
+ if name not in flags:
98
+ flags = (name,) + flags
99
+ name = long_flag
100
+
85
101
  if name.startswith("--"):
86
102
  if is_long_flag(name):
87
103
  derived_name = name[2:]
@@ -664,7 +664,33 @@ class RootNode(Node):
664
664
  context,
665
665
  )
666
666
 
667
- for validation_node in root.tree.validations:
667
+ catch_nodes = [
668
+ v
669
+ for v in root.tree.validations
670
+ if v.__class__.__name__ == "Catch"
671
+ ]
672
+ other_nodes = [
673
+ v
674
+ for v in root.tree.validations
675
+ if v.__class__.__name__ != "Catch"
676
+ ]
677
+ root.tree.validations = catch_nodes + other_nodes
678
+
679
+ for i, validation_node in enumerate(root.tree.validations):
680
+ if validation_node.__class__.__name__ == "Catch":
681
+ if hasattr(
682
+ validation_node, "remaining_validations"
683
+ ):
684
+ validation_node.remaining_validations = (
685
+ root.tree.validations[i + 1 :]
686
+ )
687
+ validation_node.on_finalize(
688
+ custom_context,
689
+ *validation_node.process_args,
690
+ **validation_node.process_kwargs,
691
+ )
692
+ break
693
+
668
694
  validation_node.on_finalize(
669
695
  custom_context,
670
696
  *validation_node.process_args,
@@ -2,6 +2,10 @@
2
2
  Validation node to catch and handle exceptions from command/group functions.
3
3
  """
4
4
 
5
+ # pylint: disable=wrong-import-position
6
+ # pylint: disable=wrong-import-order
7
+ # pylint: disable=ungrouped-imports
8
+
5
9
  import asyncio
6
10
  import inspect
7
11
  from functools import wraps
@@ -23,11 +27,16 @@ _catch_handlers: WeakKeyDictionary[
23
27
 
24
28
  class Catch(ValidationNode):
25
29
  """
26
- Catch and handle exceptions from command/group functions.
30
+ Catch and handle exceptions from command/group functions and
31
+ validators.
32
+
33
+ Wraps both the validation phase and function execution in a
34
+ try-except block. When an exception is caught, an optional handler
35
+ is invoked. Without a handler, exceptions are silently suppressed.
27
36
 
28
- Wraps the decorated function in a try-except block. When an exception
29
- is caught, an optional handler is invoked. Without a handler, exceptions
30
- are silently suppressed.
37
+ Catches exceptions from:
38
+ - Validation decorators (e.g., @exclusive, @requires)
39
+ - The command/group function itself
31
40
 
32
41
  Handler signatures supported:
33
42
  - `handler()` - No arguments, just execute code
@@ -36,15 +45,16 @@ class Catch(ValidationNode):
36
45
 
37
46
  Examples:
38
47
  ```py
39
- # Simple error logging
48
+ # Catch validation errors from @exclusive
40
49
  @command()
41
- @catch(ValueError, handler=lambda: print("Invalid value!"))
42
- def cmd():
43
- raise ValueError("Bad input")
50
+ @exclusive("--stock", "--query")
51
+ @catch(ValueError, handler=lambda e: print(f"Error: {e}"))
52
+ def search(stock, query):
53
+ pass
44
54
  ```
45
55
 
46
56
  ```py
47
- # Handle exception with details
57
+ # Catch errors from command function
48
58
  @command()
49
59
  @catch(ValueError, handler=lambda e: print(f"Error: {e}"))
50
60
  def cmd():
@@ -73,19 +83,52 @@ class Catch(ValidationNode):
73
83
  """Initialize Catch validation node with function to wrap."""
74
84
  super().__init__(name, process_args, process_kwargs, **kwargs)
75
85
  self.wrapped_func: Callable[..., Any] | None = None
86
+ self.remaining_validations: list[ValidationNode] = []
76
87
 
77
88
  def on_finalize(self, context: Context, *args: Any, **kwargs: Any) -> None:
78
89
  """
79
- Store exception handler configuration for later use.
90
+ Execute remaining validations wrapped in exception handling.
80
91
 
81
- The actual exception catching happens when the function is invoked,
82
- which is done by wrapping the function in the decorator.
92
+ This catches exceptions from all validators that run after @catch,
93
+ allowing it to catch validation errors like those from @exclusive.
83
94
 
84
95
  Args:
85
96
  context: The execution context
86
97
  *args: Contains exception types tuple at index 0
87
98
  **kwargs: Contains handler, reraise parameters
88
99
  """
100
+ exception_types: tuple[type[BaseException], ...] = args[0]
101
+ handler: Callable[..., Any] | None = kwargs.get("handler")
102
+ reraise: bool = kwargs.get("reraise", False)
103
+
104
+ for validation_node in self.remaining_validations:
105
+ try:
106
+ if asyncio.iscoroutinefunction(validation_node.on_finalize):
107
+ asyncio.run(
108
+ validation_node.on_finalize(
109
+ context,
110
+ *validation_node.process_args,
111
+ **validation_node.process_kwargs,
112
+ )
113
+ )
114
+ else:
115
+ validation_node.on_finalize(
116
+ context,
117
+ *validation_node.process_args,
118
+ **validation_node.process_kwargs,
119
+ )
120
+ except BaseException as exc:
121
+ if isinstance(exc, exception_types):
122
+ if handler is not None:
123
+ if asyncio.iscoroutinefunction(handler):
124
+ asyncio.run(_call_handler_async(handler, exc))
125
+ else:
126
+ _call_handler_sync(handler, exc)
127
+
128
+ if not reraise:
129
+ return
130
+
131
+ raise
89
132
 
90
133
 
91
134
  def catch(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: click_extended
3
- Version: 1.0.8
3
+ Version: 1.0.10
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
@@ -9,12 +9,12 @@ click_extended/core/decorators/command.py,sha256=82rwi8PIMGgOagFoNISOQkdEebKxvS9
9
9
  click_extended/core/decorators/context.py,sha256=y_jzARNNdu2JyUhIaUFpCAurztOTsYJTX96HQ2csZs0,1546
10
10
  click_extended/core/decorators/env.py,sha256=DgbQsOZ2De-Ur5hxR8hzN3-3T16K3FLsoZHS2EsKK6c,4777
11
11
  click_extended/core/decorators/group.py,sha256=_bammCVi_7vsw7YgSUTQuQp6XFUHy7knNcOmyg6DiPc,2859
12
- click_extended/core/decorators/option.py,sha256=E7NzM7dNtICh9YcAvYPvJkdvuxKDNjCIFl9a8ZyhXWQ,11810
12
+ click_extended/core/decorators/option.py,sha256=iUJ6O60GgGy1resjmC9DHuvLg_2UTmw8fcjptNiAI0Y,12476
13
13
  click_extended/core/decorators/prompt.py,sha256=QwySQ6ZK0j0iL_jlwH3AU0U4G1X3MKAJ8_TysEXxFX8,1735
14
14
  click_extended/core/decorators/selection.py,sha256=5cjWpAwReu8EY0mkziwpqhi_T5ucIlAPx1p-t3MkGJQ,5589
15
15
  click_extended/core/decorators/tag.py,sha256=395e6GN59QBj-L0NSBPynQOX3_2LAOIXkj8XKNXrRPs,3055
16
16
  click_extended/core/nodes/__init__.py,sha256=jSsKTiHPUpqXdk54cRXDotT0YMmVGezIIb3nQ0rcWko,737
17
- click_extended/core/nodes/_root_node.py,sha256=PitK6GT7LPYugxPFdVO6blG0-mZV9S2apUupgWCsYI0,43060
17
+ click_extended/core/nodes/_root_node.py,sha256=cNmTPtREVjNi6WZj3chtYFmn9_2iqJh4l32NEmO0Db4,44233
18
18
  click_extended/core/nodes/argument_node.py,sha256=gqG4HTDR40Xru_U_beKsbjVtLtwmPKfwwAUmcf5-1iQ,5284
19
19
  click_extended/core/nodes/child_node.py,sha256=yIuXmUzdXN0EToR9ohBRvHFBriSZ-xaLkv7P27PbTrA,17095
20
20
  click_extended/core/nodes/child_validation_node.py,sha256=Xcqid9EL3pHVrgv_43zz-ZL4W4yFm0Esj2qkk9qhWMc,3807
@@ -98,7 +98,7 @@ click_extended/decorators/math/sqrt.py,sha256=CWZoQpVw2lbP_LNeg-tWW8fz9VEHTX8eQN
98
98
  click_extended/decorators/math/subtract.py,sha256=A7TMByTUH3coSEe7iRN9NkHW2mv47kufb1H1adomG4A,806
99
99
  click_extended/decorators/math/to_percent.py,sha256=39vY13u7Z-pyob1vCoPBDy10FqJasJTVzHcrpTH6ERQ,1374
100
100
  click_extended/decorators/misc/__init__.py,sha256=Cl2u81f_dkvHoMoSb82L1KuNVM-3b1lzKHUMAuxhmH0,662
101
- click_extended/decorators/misc/catch.py,sha256=Ft8CbTlX6OgeNA8re_epKfwK4HyWQ1eUuCbhNqzpNbw,9343
101
+ click_extended/decorators/misc/catch.py,sha256=LkBxGHNo7_RrXjUP0LOTYOLvb9q1fTBIULJJIcYXo4Y,11095
102
102
  click_extended/decorators/misc/choice.py,sha256=RTVVjl0YUZ2Ti4j6yoHkUpJ0aqNwcnc3rTfmCVr0y0U,4354
103
103
  click_extended/decorators/misc/confirm_if.py,sha256=Rf3HcVVTRqz3sGSiPT02yz65_L351Fzkuw2V6pljlmY,4786
104
104
  click_extended/decorators/misc/default.py,sha256=TjZJKmHEqNThzyD6hOjQy2_QgHRenKILnAwfV1V5KAw,2473
@@ -144,9 +144,9 @@ click_extended/utils/naming.py,sha256=kNmzOqidgZZ1dE5aMYrxpWt4VwcLuEiFunpumpfHuZ
144
144
  click_extended/utils/process.py,sha256=sU3ZCMjBgjKcDnTRLKPdQ_TAjk0AT7Q8SatagD0JyHA,9729
145
145
  click_extended/utils/selection.py,sha256=_OQC88pGPUh29boxmS5ugXEi9jZGqAG180S27PeQaj0,9875
146
146
  click_extended/utils/time.py,sha256=H5m5caIEau_1GHkiYgKL_LcTtVdw2TkFVbkqJu7A9rQ,1067
147
- click_extended-1.0.8.dist-info/licenses/AUTHORS.md,sha256=NkShPinjqtnRDQVRyVnfJuOGM56sejauE3WRoYCcbtw,132
148
- click_extended-1.0.8.dist-info/licenses/LICENSE,sha256=gjO8hzM4mFSBXFikktaXVSgmXGcre91_GPJ-E_yP56E,1075
149
- click_extended-1.0.8.dist-info/METADATA,sha256=j42ol1yFZRyvuaiM_g7_-LVNJHFuMX6BDdcog4EjTC8,10841
150
- click_extended-1.0.8.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
151
- click_extended-1.0.8.dist-info/top_level.txt,sha256=2G3bm6tCNv80okRm773jKTk-_z1ElY-seaozZrn_TxA,15
152
- click_extended-1.0.8.dist-info/RECORD,,
147
+ click_extended-1.0.10.dist-info/licenses/AUTHORS.md,sha256=NkShPinjqtnRDQVRyVnfJuOGM56sejauE3WRoYCcbtw,132
148
+ click_extended-1.0.10.dist-info/licenses/LICENSE,sha256=gjO8hzM4mFSBXFikktaXVSgmXGcre91_GPJ-E_yP56E,1075
149
+ click_extended-1.0.10.dist-info/METADATA,sha256=gPGL5Ui0QKLmj7LywnsrtOL0NLNGa_JywqU2BTWPIxU,10842
150
+ click_extended-1.0.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
151
+ click_extended-1.0.10.dist-info/top_level.txt,sha256=2G3bm6tCNv80okRm773jKTk-_z1ElY-seaozZrn_TxA,15
152
+ click_extended-1.0.10.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5