click-extended 0.0.4__py3-none-any.whl → 0.1.0__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.
@@ -1,7 +1,11 @@
1
1
  """Initialization file for the 'click_extended' module."""
2
2
 
3
- from click_extended.core._child_node import ChildNode
3
+ from click_extended.core._child_node import ChildNode, ProcessContext
4
+ from click_extended.core._global_node import GlobalNode
5
+ from click_extended.core._node import Node
4
6
  from click_extended.core._parent_node import ParentNode
7
+ from click_extended.core._root_node import RootNode
8
+ from click_extended.core._tree import Tree
5
9
  from click_extended.core.argument import argument
6
10
  from click_extended.core.command import command
7
11
  from click_extended.core.env import env
@@ -11,7 +15,12 @@ from click_extended.core.tag import tag
11
15
 
12
16
  __all__ = [
13
17
  "ChildNode",
18
+ "ProcessContext",
19
+ "GlobalNode",
20
+ "Node",
14
21
  "ParentNode",
22
+ "RootNode",
23
+ "Tree",
15
24
  "argument",
16
25
  "command",
17
26
  "env",
click_extended/errors.py CHANGED
@@ -3,21 +3,100 @@
3
3
  # pylint: disable=too-many-arguments
4
4
  # pylint: disable=too-many-positional-arguments
5
5
 
6
+ import typing as t
7
+
8
+ import click
9
+ from click import ClickException
10
+ from click._compat import get_text_stderr
11
+ from click.utils import echo
12
+
6
13
 
7
14
  class ClickExtendedError(Exception):
8
15
  """Base exception for exceptions defined in the `click_extended` library."""
9
16
 
17
+
18
+ class CatchableError(ClickExtendedError):
19
+ """Base exception for exceptions raised inside a child node.
20
+
21
+ These exceptions are caught by the framework and reformatted with
22
+ parameter context before being displayed to the user.
23
+ """
24
+
10
25
  def __init__(self, message: str) -> None:
11
26
  """
12
- Initialize a new `ClickExtendedError` instance.
27
+ Initialize a CatchableError.
13
28
 
14
29
  Args:
15
- message (str):
16
- The message to show.
30
+ message: The error message describing what went wrong.
17
31
  """
18
32
  super().__init__(message)
19
33
 
20
34
 
35
+ class ValidationError(CatchableError):
36
+ """Exception raised when validation fails in a child node."""
37
+
38
+
39
+ class TransformError(CatchableError):
40
+ """Exception raised when transformation fails in a child node."""
41
+
42
+
43
+ class ParameterError(ClickException):
44
+ """Exception raised when parameter validation or transformation fails.
45
+
46
+ This exception is raised by the framework after catching a CatchableError
47
+ and adding parameter context information.
48
+ """
49
+
50
+ exit_code = 2 # Match Click's UsageError exit code
51
+
52
+ def __init__(
53
+ self,
54
+ message: str,
55
+ param_hint: str | None = None,
56
+ ctx: click.Context | None = None,
57
+ ) -> None:
58
+ """
59
+ Initialize a ParameterError.
60
+
61
+ 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.
65
+ """
66
+ super().__init__(message)
67
+ self.param_hint = param_hint
68
+ self.ctx = ctx
69
+
70
+ def format_message(self) -> str:
71
+ """Format the error message with parameter context."""
72
+ if self.param_hint:
73
+ return f"({self.param_hint}): {self.message}"
74
+ return self.message
75
+
76
+ def show(self, file: t.IO[t.Any] | None = None) -> None:
77
+ """Display the error with usage information (like Click does)."""
78
+ if file is None:
79
+ file = get_text_stderr()
80
+
81
+ color = None
82
+
83
+ if self.ctx is not None:
84
+ color = self.ctx.color
85
+
86
+ echo(self.ctx.get_usage(), file=file, color=color)
87
+
88
+ if self.ctx.command.get_help_option(self.ctx) is not None:
89
+ hint = (
90
+ f"Try '{self.ctx.command_path} "
91
+ f"{self.ctx.help_option_names[0]}' for help."
92
+ )
93
+ echo(hint, file=file, color=color)
94
+
95
+ echo("", file=file)
96
+
97
+ echo(f"Error {self.format_message()}", file=file, color=color)
98
+
99
+
21
100
  class NoParentError(ClickExtendedError):
22
101
  """Exception raised when no `ParentNode` has been defined."""
23
102
 
@@ -126,3 +205,42 @@ class DuplicateNameError(ClickExtendedError):
126
205
  f"must be unique within a command."
127
206
  )
128
207
  super().__init__(message)
208
+
209
+
210
+ class TypeMismatchError(ClickExtendedError):
211
+ """Exception raised when a child node doesn't support the parent's type."""
212
+
213
+ def __init__(
214
+ self,
215
+ name: str,
216
+ parent_name: str,
217
+ parent_type: type | None,
218
+ supported_types: list[type],
219
+ ) -> None:
220
+ """
221
+ Initialize a new `TypeMismatchError` instance.
222
+
223
+ Args:
224
+ name (str):
225
+ The name of the decorator.
226
+ parent_name (str):
227
+ The name of the parent node.
228
+ parent_type (type | None):
229
+ The actual type of the parent.
230
+ supported_types (list[type]):
231
+ List of types supported by the child node.
232
+ """
233
+
234
+ def get_type_name(type_obj: type) -> str:
235
+ """Get type name, handling both regular types and UnionType."""
236
+ return getattr(type_obj, "__name__", str(type_obj))
237
+
238
+ type_names = ", ".join(get_type_name(t) for t in supported_types)
239
+ parent_type_name = get_type_name(parent_type) if parent_type else "None"
240
+
241
+ message = (
242
+ f"Decorator '{name}' does not support "
243
+ f"parent '{parent_name}' with type '{parent_type_name}'. "
244
+ f"Supported types: {type_names}"
245
+ )
246
+ super().__init__(message)
click_extended/types.py CHANGED
@@ -1,10 +1,9 @@
1
1
  """Types used in `click_extended` which can be useful for users."""
2
2
 
3
- from click_extended.core._child_node import ChildNode
4
- from click_extended.core._node import Node
5
- from click_extended.core._parent_node import ParentNode
6
- from click_extended.core._root_node import RootNode
7
- from click_extended.core._tree import Tree
3
+ # pylint: disable=invalid-name
4
+
5
+ from typing import TYPE_CHECKING, Any, Callable
6
+
8
7
  from click_extended.core.argument import Argument
9
8
  from click_extended.core.command import Command
10
9
  from click_extended.core.env import Env
@@ -12,17 +11,17 @@ from click_extended.core.group import Group
12
11
  from click_extended.core.option import Option
13
12
  from click_extended.core.tag import Tag
14
13
 
14
+ if TYPE_CHECKING:
15
+ from click_extended.core._parent_node import ParentNode
16
+
15
17
  Tags = dict[str, Tag]
16
18
  Siblings = list[str]
17
- Parent = ParentNode | Tag
19
+ Parent = "ParentNode | Tag"
20
+ Decorator = Callable[[Callable[..., Any]], Callable[..., Any]]
18
21
 
19
22
  __all__ = [
20
- "ChildNode",
21
- "Node",
23
+ "Decorator",
22
24
  "Parent",
23
- "ParentNode",
24
- "RootNode",
25
- "Tree",
26
25
  "Argument",
27
26
  "Command",
28
27
  "Env",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: click_extended
3
- Version: 0.0.4
3
+ Version: 0.1.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
@@ -80,10 +80,10 @@ An extension of the [Click](https://github.com/pallets/click) library with addit
80
80
  ## Features
81
81
 
82
82
  - **Aliasing**: Add multiple aliases to a group or command.
83
- - **Async supprt**: Automatically run both synchronous and asynchronous functions.
83
+ - **Async support**: Automatically run both synchronous and asynchronous functions.
84
84
  - **Extensible decorator API**: Extend the Click decorator API with custom decorators like validators, transformers, and more.
85
+ - **Type-hint First**: Built using the type-hinting system to it's full potential.
85
86
  - **Environment variables**: Automatically validate and inject environment variables into the function.
86
- - **Fully type-hinted**: The library is fully type-hinted and provides good types for easy development.
87
87
  - **Help alias**: The `-h` and `--help` automatically show the help menu unless overridden.
88
88
 
89
89
  ## Installation
@@ -191,17 +191,18 @@ $ python app.py
191
191
  Using token: secret12...
192
192
  ```
193
193
 
194
- ### Value Validation
194
+ ### Custom Validators
195
195
 
196
196
  ```python
197
- from click_extended import command, option, ChildNode
197
+ from click_extended import command, option, ChildNode, ProcessContext
198
+ from click_extended.errors import ValidationError
198
199
 
199
200
  class IsPositive(ChildNode):
200
201
  """Validate that a number is positive."""
201
202
 
202
- def process(self, value, *args, **kwargs):
203
+ def process(self, value: float | int, context: ProcessContext):
203
204
  if value <= 0:
204
- raise ValueError(f"Value must be positive, got {value}")
205
+ raise ValidationError(f"{value} is not positive")
205
206
 
206
207
  def is_positive(*args, **kwargs):
207
208
  """Validate positive numbers."""
@@ -222,7 +223,10 @@ if __name__ == "__main__":
222
223
  $ python app.py --count 5
223
224
  Processing 5 items
224
225
  $ python app.py --count -1
225
- Error: Value must be positive, got -1
226
+ Usage: app.py [OPTIONS]
227
+ Try 'app.py --help' for help.
228
+
229
+ Error (--count): -1 is not positive
226
230
  ```
227
231
 
228
232
  ## Documentation
@@ -0,0 +1,9 @@
1
+ click_extended/__init__.py,sha256=wBUV7-CmgDBKSnDj3_MbYCjHv2zAaATQ795Y9Xs-0NE,858
2
+ click_extended/errors.py,sha256=hHKYsi61__uksU33-S80gANZg2ZsoHGhLdz6qR49r-I,7774
3
+ click_extended/types.py,sha256=tiz-toTYABwFTsxUMIDRhPiq3H_V3nhrAcV8VGEKyzA,786
4
+ click_extended-0.1.0.dist-info/licenses/AUTHORS.md,sha256=NkShPinjqtnRDQVRyVnfJuOGM56sejauE3WRoYCcbtw,132
5
+ click_extended-0.1.0.dist-info/licenses/LICENSE,sha256=gjO8hzM4mFSBXFikktaXVSgmXGcre91_GPJ-E_yP56E,1075
6
+ click_extended-0.1.0.dist-info/METADATA,sha256=-nzGqQFMk7dsfpMQIiVD7edfV3yGZVvRuymWP73WiSk,8295
7
+ click_extended-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ click_extended-0.1.0.dist-info/top_level.txt,sha256=2G3bm6tCNv80okRm773jKTk-_z1ElY-seaozZrn_TxA,15
9
+ click_extended-0.1.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- click_extended/__init__.py,sha256=dqtS3EH0att5KwxtbvkSdvN9CzvISgys6YT1OAzgsT8,568
2
- click_extended/errors.py,sha256=nFhxTtEHYJBO1tWwx1YdMwEKFvenEOD5dLfuZY9yCzI,4099
3
- click_extended/types.py,sha256=Xed59Ss7tRr0RQOXCgqRIAMCI7uopkOY4LrvT43Qu40,867
4
- click_extended-0.0.4.dist-info/licenses/AUTHORS.md,sha256=NkShPinjqtnRDQVRyVnfJuOGM56sejauE3WRoYCcbtw,132
5
- click_extended-0.0.4.dist-info/licenses/LICENSE,sha256=gjO8hzM4mFSBXFikktaXVSgmXGcre91_GPJ-E_yP56E,1075
6
- click_extended-0.0.4.dist-info/METADATA,sha256=lhsHDcllPzdiP-7pcQGi4wCCGr5kmbzpGcR1zo_lfA4,8181
7
- click_extended-0.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- click_extended-0.0.4.dist-info/top_level.txt,sha256=2G3bm6tCNv80okRm773jKTk-_z1ElY-seaozZrn_TxA,15
9
- click_extended-0.0.4.dist-info/RECORD,,