|
2 | 2 |
|
3 | 3 | from __future__ import annotations
|
4 | 4 |
|
| 5 | +import importlib.metadata |
5 | 6 | import inspect
|
6 | 7 | from enum import Enum
|
7 | 8 | from gettext import gettext as _
|
8 | 9 | from gettext import ngettext
|
9 | 10 | from typing import TYPE_CHECKING
|
10 | 11 | from typing import Any
|
11 | 12 | from typing import ClassVar
|
| 13 | +from typing import TypeVar |
12 | 14 |
|
13 | 15 | import click
|
14 | 16 | from click import Choice
|
15 | 17 | from click import Command
|
16 | 18 | from click import Context
|
17 | 19 | from click import Parameter
|
18 |
| -from click.parser import split_opt |
19 | 20 | from click_default_group import DefaultGroup
|
20 | 21 | from rich.highlighter import RegexHighlighter
|
| 22 | +from rich.padding import Padding |
21 | 23 | from rich.panel import Panel
|
22 | 24 | from rich.table import Table
|
23 | 25 | from rich.text import Text
|
|
27 | 29 | from _pytask.console import create_panel_title
|
28 | 30 |
|
29 | 31 | if TYPE_CHECKING:
|
| 32 | + from collections.abc import Iterable |
30 | 33 | from collections.abc import Sequence
|
31 | 34 |
|
32 | 35 |
|
33 | 36 | __all__ = ["ColoredCommand", "ColoredGroup", "EnumChoice"]
|
34 | 37 |
|
35 | 38 |
|
36 |
| -class EnumChoice(Choice): |
37 |
| - """An enum-based choice type. |
| 39 | +if importlib.metadata.version("click") < "8.2": |
| 40 | + from click.parser import split_opt |
38 | 41 |
|
39 |
| - The implementation is copied from https://github.com/pallets/click/pull/2210 and |
40 |
| - related discussion can be found in https://github.com/pallets/click/issues/605. |
| 42 | + class EnumChoice(Choice): |
| 43 | + """An enum-based choice type. |
41 | 44 |
|
42 |
| - In contrast to using :class:`click.Choice`, using this type ensures that the error |
43 |
| - message does not show the enum members. |
| 45 | + The implementation is copied from https://github.com/pallets/click/pull/2210 and |
| 46 | + related discussion can be found in https://github.com/pallets/click/issues/605. |
44 | 47 |
|
45 |
| - In contrast to the proposed implementation in the PR, this implementation does not |
46 |
| - use the members than rather the values of the enum. |
| 48 | + In contrast to using :class:`click.Choice`, using this type ensures that the |
| 49 | + error message does not show the enum members. |
47 | 50 |
|
48 |
| - """ |
| 51 | + In contrast to the proposed implementation in the PR, this implementation does |
| 52 | + not use the members than rather the values of the enum. |
49 | 53 |
|
50 |
| - def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: |
51 |
| - super().__init__( |
52 |
| - choices=[element.value for element in enum_type], |
53 |
| - case_sensitive=case_sensitive, |
54 |
| - ) |
55 |
| - self.enum_type = enum_type |
| 54 | + """ |
| 55 | + |
| 56 | + def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: |
| 57 | + super().__init__( |
| 58 | + choices=[element.value for element in enum_type], |
| 59 | + case_sensitive=case_sensitive, |
| 60 | + ) |
| 61 | + self.enum_type = enum_type |
| 62 | + |
| 63 | + def convert( |
| 64 | + self, value: Any, param: Parameter | None, ctx: Context | None |
| 65 | + ) -> Any: |
| 66 | + if isinstance(value, Enum): |
| 67 | + value = value.value |
| 68 | + value = super().convert(value=value, param=param, ctx=ctx) |
| 69 | + if value is None: |
| 70 | + return None |
| 71 | + return self.enum_type(value) |
| 72 | + |
| 73 | +else: |
| 74 | + from click.parser import ( # type: ignore[attr-defined, no-redef] |
| 75 | + _split_opt as split_opt, |
| 76 | + ) |
| 77 | + |
| 78 | + ParamTypeValue = TypeVar("ParamTypeValue") |
56 | 79 |
|
57 |
| - def convert(self, value: Any, param: Parameter | None, ctx: Context | None) -> Any: |
58 |
| - if isinstance(value, Enum): |
59 |
| - value = value.value |
60 |
| - value = super().convert(value=value, param=param, ctx=ctx) |
61 |
| - if value is None: |
62 |
| - return None |
63 |
| - return self.enum_type(value) |
| 80 | + class EnumChoice(Choice): # type: ignore[no-redef] |
| 81 | + def __init__( |
| 82 | + self, choices: Iterable[ParamTypeValue], case_sensitive: bool = False |
| 83 | + ) -> None: |
| 84 | + super().__init__(choices=choices, case_sensitive=case_sensitive) # type: ignore[arg-type] |
64 | 85 |
|
65 | 86 |
|
66 | 87 | class _OptionHighlighter(RegexHighlighter):
|
@@ -119,7 +140,10 @@ def format_help(
|
119 | 140 | _print_options(self, ctx)
|
120 | 141 |
|
121 | 142 | console.print(
|
122 |
| - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 143 | + Padding( |
| 144 | + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 145 | + (0, 3, 0, 0), |
| 146 | + ), |
123 | 147 | justify="right",
|
124 | 148 | )
|
125 | 149 |
|
@@ -197,7 +221,10 @@ def format_help(
|
197 | 221 | _print_options(self, ctx)
|
198 | 222 |
|
199 | 223 | console.print(
|
200 |
| - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 224 | + Padding( |
| 225 | + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", |
| 226 | + (0, 3, 0, 0), |
| 227 | + ), |
201 | 228 | justify="right",
|
202 | 229 | )
|
203 | 230 |
|
|
0 commit comments