Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add option to use Enum names instead of values on the commandline #224

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
afc9cf4
Add option to use Enum names
sirex Jan 21, 2021
2e079f2
Fix black issues
sirex Jan 21, 2021
b0f534e
Fix isort issues
sirex Jan 21, 2021
2a18e31
Merge branch 'master' into enum-names
svlandeg Aug 14, 2024
5769c4a
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Aug 14, 2024
f4a99b2
rename 004 to 005
svlandeg Aug 14, 2024
0ce6ade
Merge branch 'enum-names' of https://github.com/sirex/typer into enum…
svlandeg Aug 14, 2024
5d63dbd
restore the original 003 as 004
svlandeg Aug 14, 2024
9a00cd9
Merge branch 'master' into enum-names
svlandeg Aug 16, 2024
2c9192f
Merge branch 'master' into enum-names
svlandeg Aug 19, 2024
ad046fc
Merge branch 'master' into enum-names
svlandeg Sep 11, 2024
a77dddd
fix issues
svlandeg Sep 11, 2024
dfaf7b3
rename to enum_by_name and fix the convertor code order
svlandeg Sep 11, 2024
d77ac5b
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2024
c39a5ea
Add console example for IntEnum
svlandeg Sep 11, 2024
b77a29f
Merge remote-tracking branch 'upstream_sirex/enum-names' into enum-names
svlandeg Sep 11, 2024
8312bb8
Fix default values
svlandeg Sep 11, 2024
b5648ed
Add additional unit tests combining enums with list/tuple
svlandeg Sep 11, 2024
e24f446
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2024
0644919
pass along enum_by_name parameter to generate_X_convertor functions
svlandeg Sep 11, 2024
478d183
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2024
eee3a4c
add Annotated versions of the new tests
svlandeg Sep 11, 2024
b5bd589
Merge remote-tracking branch 'upstream_sirex/enum-names' into enum-names
svlandeg Sep 11, 2024
e2053b1
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2024
221a865
ignore 006 tutorial just like 003 (mutable default argument)
svlandeg Sep 12, 2024
7b59934
update enum.md to use the annotated versions as well
svlandeg Sep 12, 2024
5759360
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 12, 2024
87ae5ad
add tests with Argument
svlandeg Sep 12, 2024
e13a083
Merge remote-tracking branch 'upstream_sirex/enum-names' into enum-names
svlandeg Sep 12, 2024
adb7c03
fix printing of enum value (needed for Python 3.11 and 3.12)
svlandeg Sep 12, 2024
9ad26c2
remove lowercasing from generator function - should be done with case…
svlandeg Sep 12, 2024
6ae6c9b
🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
pre-commit-ci[bot] Sep 12, 2024
bfae3ef
fix hl_lines
svlandeg Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/tutorial/parameter-types/enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,32 @@ Training neural network of type: lstm
```

</div>


### Using Enum names instead of values

Some times you want to accept `Enum` names from command line and convert
that into `Enum` values in command handler. You can enable this with
`names=True` parameter:
svlandeg marked this conversation as resolved.
Show resolved Hide resolved

```Python hl_lines="14"
{!../docs_src/parameter_types/enum/tutorial003.py!}
```

And then the names of the `Enum` will be used instead of values:

<div class="termy">

```console
$ python main.py --log-level debug

Log level set to DEBUG
```

</div>

If `IntEnum` type is given, then enum names are used implicitly.
svlandeg marked this conversation as resolved.
Show resolved Hide resolved

```Python hl_lines="14"
{!../docs_src/parameter_types/enum/tutorial004.py!}
```
18 changes: 18 additions & 0 deletions docs_src/parameter_types/enum/tutorial003.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import enum
import logging

import typer


class LogLevel(enum.Enum):
debug = logging.DEBUG
info = logging.INFO
warning = logging.WARNING


def main(log_level: LogLevel = typer.Option(LogLevel.warning, names=True)):
svlandeg marked this conversation as resolved.
Show resolved Hide resolved
typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}")


if __name__ == "__main__":
typer.run(main)
18 changes: 18 additions & 0 deletions docs_src/parameter_types/enum/tutorial004.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import enum

import typer


class Access(enum.IntEnum):
private = 1
protected = 2
public = 3
open = 4


def main(access: Access = typer.Option(Access.private)):
svlandeg marked this conversation as resolved.
Show resolved Hide resolved
typer.echo(f"Access level: {access.name}")


if __name__ == "__main__":
typer.run(main)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import subprocess

import typer
from typer.testing import CliRunner

from docs_src.parameter_types.enum import tutorial003 as mod

runner = CliRunner()

app = typer.Typer()
app.command()(mod.main)


def test_enum_names():
result = runner.invoke(app, ["--log-level", "debug"])
assert result.exit_code == 0
assert "Log level set to: DEBUG" in result.output


def test_script():
result = subprocess.run(
["coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert "Usage" in result.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import subprocess

import typer
from typer.testing import CliRunner

from docs_src.parameter_types.enum import tutorial004 as mod

runner = CliRunner()

app = typer.Typer()
app.command()(mod.main)


def test_int_enum():
result = runner.invoke(app, ["--access", "open"])
assert result.exit_code == 0
assert "Access level: open" in result.output


def test_script():
result = subprocess.run(
["coverage", "run", mod.__file__, "--help"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert "Usage" in result.stdout
35 changes: 30 additions & 5 deletions typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,18 @@ def convertor(value: Any) -> Any:
return convertor


def generate_enum_name_convertor(enum: Type[Enum]) -> Callable[..., Any]:
lower_name_map = {str(item.name).lower(): item for item in enum}

def convertor(value: Any) -> Any:
if value is not None:
low = str(value).lower()
if low in lower_name_map:
return lower_name_map[low]

return convertor


def generate_iter_convertor(convertor: Callable[[Any], Any]) -> Callable[..., Any]:
def internal_convertor(value: Any) -> List[Any]:
return [convertor(v) for v in value]
Expand Down Expand Up @@ -580,10 +592,11 @@ def get_click_type(
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, Enum):
return click.Choice(
[item.value for item in annotation],
case_sensitive=parameter_info.case_sensitive,
)
if use_enum_names(parameter_info, annotation):
choices = [item.name for item in annotation]
else:
choices = [item.value for item in annotation]
return click.Choice(choices, case_sensitive=parameter_info.case_sensitive)
raise RuntimeError(f"Type not yet supported: {annotation}") # pragma no cover


Expand All @@ -593,6 +606,15 @@ def lenient_issubclass(
return isinstance(cls, type) and issubclass(cls, class_or_tuple)


def use_enum_names(parameter_info: ParameterInfo, annotation: Type[Enum]) -> bool:
"""Check if Enum names or values should be used

If ParameterInfo.names is explicitly set to True, always use names, but also
try to guess if names should be used in cases, when Enum is ant IntEnum.
"""
return parameter_info.names or issubclass(annotation, int)


def get_click_param(
param: ParamMeta,
) -> Tuple[Union[click.Argument, click.Option], Any]:
Expand Down Expand Up @@ -660,7 +682,10 @@ def get_click_param(
if lenient_issubclass(main_type, Path):
convertor = param_path_convertor
if lenient_issubclass(main_type, Enum):
convertor = generate_enum_convertor(main_type)
if use_enum_names(parameter_info, main_type):
convertor = generate_enum_name_convertor(main_type)
else:
convertor = generate_enum_convertor(main_type)
if convertor and is_list:
convertor = generate_iter_convertor(convertor)
# TODO: handle recursive conversion for tuples
Expand Down
6 changes: 6 additions & 0 deletions typer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ def __init__(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
names: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
Expand Down Expand Up @@ -211,6 +212,7 @@ def __init__(
self.hidden = hidden
# Choice
self.case_sensitive = case_sensitive
self.names = names
# Numbers
self.min = min
self.max = max
Expand Down Expand Up @@ -262,6 +264,7 @@ def __init__(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
names: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
Expand Down Expand Up @@ -301,6 +304,7 @@ def __init__(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
names=names,
# Numbers
min=min,
max=max,
Expand Down Expand Up @@ -353,6 +357,7 @@ def __init__(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
names: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
Expand Down Expand Up @@ -392,6 +397,7 @@ def __init__(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
names=names,
# Numbers
min=min,
max=max,
Expand Down
4 changes: 4 additions & 0 deletions typer/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def Option(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
names: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
Expand Down Expand Up @@ -75,6 +76,7 @@ def Option(
show_envvar=show_envvar,
# Choice
case_sensitive=case_sensitive,
names=names,
# Numbers
min=min,
max=max,
Expand Down Expand Up @@ -117,6 +119,7 @@ def Argument(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
names: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
Expand Down Expand Up @@ -159,6 +162,7 @@ def Argument(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
names=names,
# Numbers
min=min,
max=max,
Expand Down