Skip to content

Commit 8243840

Browse files
committed
Fixing compatibility with click.Choice.
1 parent 07958ba commit 8243840

File tree

3 files changed

+55
-30
lines changed

3 files changed

+55
-30
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ repos:
5959
- id: nbstripout
6060
exclude: (docs)
6161
- repo: https://github.com/crate-ci/typos
62-
rev: v1
62+
rev: v1.32.0
6363
hooks:
6464
- id: typos
6565
exclude: (\.ipynb)

src/_pytask/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from __future__ import annotations
44

5+
import importlib.metadata
56
from typing import Any
67

78
import click
8-
from packaging.version import parse as parse_version
99

1010
from _pytask.click import ColoredGroup
1111
from _pytask.pluginmanager import storage
@@ -16,7 +16,7 @@
1616
}
1717

1818

19-
if parse_version(click.__version__) >= parse_version("8"): # pragma: no cover
19+
if importlib.metadata.version("click") >= "8": # pragma: no cover
2020
_VERSION_OPTION_KWARGS = {"package_name": "pytask"}
2121
else: # pragma: no cover
2222
_VERSION_OPTION_KWARGS = {}

src/_pytask/click.py

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,24 @@
22

33
from __future__ import annotations
44

5+
import importlib.metadata
56
import inspect
67
from enum import Enum
78
from gettext import gettext as _
89
from gettext import ngettext
910
from typing import TYPE_CHECKING
1011
from typing import Any
1112
from typing import ClassVar
13+
from typing import TypeVar
1214

1315
import click
1416
from click import Choice
1517
from click import Command
1618
from click import Context
1719
from click import Parameter
18-
from click.parser import split_opt
1920
from click_default_group import DefaultGroup
2021
from rich.highlighter import RegexHighlighter
22+
from rich.padding import Padding
2123
from rich.panel import Panel
2224
from rich.table import Table
2325
from rich.text import Text
@@ -27,40 +29,57 @@
2729
from _pytask.console import create_panel_title
2830

2931
if TYPE_CHECKING:
32+
from collections.abc import Iterable
3033
from collections.abc import Sequence
3134

3235

3336
__all__ = ["ColoredCommand", "ColoredGroup", "EnumChoice"]
3437

3538

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
3841

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): # type: ignore[type-arg]
43+
"""An enum-based choice type.
4144
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.
4447
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.
4750
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.
4953
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 _split_opt as split_opt
75+
76+
ParamTypeValue = TypeVar("ParamTypeValue")
5677

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)
78+
class EnumChoice(Choice):
79+
def __init__(
80+
self, choices: Iterable[ParamTypeValue], case_sensitive: bool = False
81+
) -> None:
82+
super().__init__(choices=choices, case_sensitive=case_sensitive)
6483

6584

6685
class _OptionHighlighter(RegexHighlighter):
@@ -119,7 +138,10 @@ def format_help(
119138
_print_options(self, ctx)
120139

121140
console.print(
122-
"[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]",
141+
Padding(
142+
"[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]",
143+
(0, 3, 0, 0),
144+
),
123145
justify="right",
124146
)
125147

@@ -197,7 +219,10 @@ def format_help(
197219
_print_options(self, ctx)
198220

199221
console.print(
200-
"[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]",
222+
Padding(
223+
"[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]",
224+
(0, 3, 0, 0),
225+
),
201226
justify="right",
202227
)
203228

@@ -235,7 +260,7 @@ def _print_options(group_or_command: Command | DefaultGroup, ctx: Context) -> No
235260
if param.metavar:
236261
opt2 += Text(f" {param.metavar}", style="metavar")
237262
elif isinstance(param.type, click.Choice):
238-
choices = "[" + "|".join(param.type.choices) + "]"
263+
choices = "[" + "|".join([str(c.value) for c in param.type.choices]) + "]"
239264
opt2 += Text(f" {choices}", style="metavar", overflow="fold")
240265

241266
help_text = _format_help_text(param, ctx)
@@ -313,7 +338,7 @@ def _format_help_text( # noqa: C901, PLR0912, PLR0915
313338
elif param.is_bool_flag and param.secondary_opts: # type: ignore[attr-defined]
314339
# For boolean flags that have distinct True/False opts,
315340
# use the opt without prefix instead of the value.
316-
default_string = split_opt(
341+
default_string = split_opt( # type: ignore[operator]
317342
(param.opts if param.default else param.secondary_opts)[0]
318343
)[1]
319344
elif (

0 commit comments

Comments
 (0)