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 24 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
35 changes: 35 additions & 0 deletions docs/tutorial/parameter-types/enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,38 @@ Buying groceries: Eggs, Bacon
```

</div>


### Using Enum names instead of values

Some times you want to accept `Enum` names from the command line and convert
that into `Enum` values in the command handler. You can enable this by setting
`enum_by_name=True`:

```Python hl_lines="14"
{!../docs_src/parameter_types/enum/tutorial004.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>

This can be particularly useful if the enum values are not strings:

```Python hl_lines="7-10, 13"
{!../docs_src/parameter_types/enum/tutorial005.py!}
```

```console
$ python main.py --access protected

Access level: protected (2)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from enum import Enum
from typing import Tuple

import typer


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(user: Tuple[str, int, bool, Food] = typer.Option((None, None, None, Food.f1))):
svlandeg marked this conversation as resolved.
Show resolved Hide resolved
username, coins, is_wizard, food = user
if not username:
print("No user provided")
raise typer.Abort()
print(f"The username {username} has {coins} coins")
if is_wizard:
print("And this user is a wizard!")
print(f"And they love eating {food.value}")


if __name__ == "__main__":
typer.run(main)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from enum import Enum
from typing import Tuple

import typer
from typing_extensions import Annotated


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(
user: Annotated[Tuple[str, int, bool, Food], typer.Option()] = (
None,
None,
None,
Food.f1,
),
):
username, coins, is_wizard, food = user
if not username:
print("No user provided")
raise typer.Abort()
print(f"The username {username} has {coins} coins")
if is_wizard:
print("And this user is a wizard!")
print(f"And they love eating {food.value}")


if __name__ == "__main__":
typer.run(main)
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from enum import Enum
from typing import Tuple

import typer


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(
user: Tuple[str, int, bool, Food] = typer.Option(
(None, None, None, "f1"), enum_by_name=True
),
):
username, coins, is_wizard, food = user
if not username:
print("No user provided")
raise typer.Abort()
print(f"The username {username} has {coins} coins")
if is_wizard:
print("And this user is a wizard!")
print(f"And they love eating {food.value}")


if __name__ == "__main__":
typer.run(main)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from enum import Enum
from typing import Tuple

import typer
from typing_extensions import Annotated


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(
user: Annotated[Tuple[str, int, bool, Food], typer.Option(enum_by_name=True)] = (
None,
None,
None,
"f1",
),
):
username, coins, is_wizard, food = user
if not username:
print("No user provided")
raise typer.Abort()
print(f"The username {username} has {coins} coins")
if is_wizard:
print("And this user is a wizard!")
print(f"And they love eating {food.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 logging

import typer


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


def main(log_level: LogLevel = typer.Option("warning", enum_by_name=True)):
typer.echo(f"Log level set to: {logging.getLevelName(log_level.value)}")


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

import typer
from typing_extensions import Annotated


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


def main(log_level: Annotated[LogLevel, typer.Option(enum_by_name=True)] = "warning"):
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/tutorial005.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("private", enum_by_name=True)):
typer.echo(f"Access level: {access.name} ({access.value})")


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

import typer
from typing_extensions import Annotated


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


def main(access: Annotated[Access, typer.Option(enum_by_name=True)] = "private"):
typer.echo(f"Access level: {access.name} ({access.value})")


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

import typer


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(groceries: List[Food] = typer.Option(["f1", "f3"], enum_by_name=True)):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")


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

import typer
from typing_extensions import Annotated


class Food(str, Enum):
f1 = "Eggs"
f2 = "Bacon"
f3 = "Cheese"


def main(
groceries: Annotated[List[Food], typer.Option(enum_by_name=True)] = ["f1", "f3"],
):
print(f"Buying groceries: {', '.join([f.value for f in groceries])}")

svlandeg marked this conversation as resolved.
Show resolved Hide resolved

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

import typer
from typer.testing import CliRunner

from docs_src.multiple_values.options_with_multiple_values import tutorial002 as mod

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


def test_main():
result = runner.invoke(app)
assert result.exit_code != 0
assert "No user provided" in result.output
assert "Aborted" in result.output


def test_user_1():
result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"])
assert result.exit_code == 0
assert "The username Camila has 50 coins" in result.output
assert "And this user is a wizard!" in result.output
assert "And they love eating Eggs" in result.output


def test_user_2():
result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"])
assert result.exit_code == 0
assert "The username Morty has 3 coins" in result.output
assert "And this user is a wizard!" not in result.output
assert "And they love eating Bacon" in result.output


def test_invalid_user():
result = runner.invoke(app, ["--user", "Camila", "50"])
assert result.exit_code != 0
assert "Option '--user' requires 4 arguments" in result.output


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

import typer
from typer.testing import CliRunner

from docs_src.multiple_values.options_with_multiple_values import tutorial002_an as mod

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


def test_main():
result = runner.invoke(app)
assert result.exit_code != 0
assert "No user provided" in result.output
assert "Aborted" in result.output


def test_user_1():
result = runner.invoke(app, ["--user", "Camila", "50", "yes", "Eggs"])
assert result.exit_code == 0
assert "The username Camila has 50 coins" in result.output
assert "And this user is a wizard!" in result.output
assert "And they love eating Eggs" in result.output


def test_user_2():
result = runner.invoke(app, ["--user", "Morty", "3", "no", "Bacon"])
assert result.exit_code == 0
assert "The username Morty has 3 coins" in result.output
assert "And this user is a wizard!" not in result.output
assert "And they love eating Bacon" in result.output


def test_invalid_user():
result = runner.invoke(app, ["--user", "Camila", "50"])
assert result.exit_code != 0
assert "Option '--user' requires 4 arguments" in result.output


def test_script():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, "--help"],
capture_output=True,
encoding="utf-8",
)
assert "Usage" in result.stdout
Loading
Loading