diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md
index c78dc60cc4..b000e14ee1 100644
--- a/docs/tutorial/parameter-types/enum.md
+++ b/docs/tutorial/parameter-types/enum.md
@@ -74,6 +74,37 @@ Training neural network of type: lstm
+### Using Enum names instead of values
+
+Sometimes 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`:
+
+{* docs_src/parameter_types/enum/tutorial007_an.py hl[18] *}
+
+And then the names of the `Enum` will be used instead of values:
+
+
+
+```console
+$ python main.py --log-level debug
+
+Log level set to DEBUG
+```
+
+
+
+This can be particularly useful if the enum values are not strings:
+
+{* docs_src/parameter_types/enum/tutorial005_an.py hl[8:11,18] *}
+
+```console
+$ python main.py --access protected
+
+Access level: protected (2)
+```
+
+
### List of Enum values
A *CLI parameter* can also take a list of `Enum` values:
@@ -112,6 +143,29 @@ Buying groceries: Eggs, Bacon
+You can also combine `enum_by_name=True` with a list of enums:
+
+{* docs_src/parameter_types/enum/tutorial006_an.py hl[19] *}
+
+This works exactly the same, but you're using the enum names instead of values:
+
+
+
+```console
+// Try it with a single value
+$ python main.py --groceries "f1"
+
+Buying groceries: Eggs
+
+// Try it with multiple values
+$ python main.py --groceries "f1" --groceries "f2"
+
+Buying groceries: Eggs, Bacon
+```
+
+
+
+
### Literal choices
You can also use `Literal` to represent a set of possible predefined choices, without having to use an `Enum`:
diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py
new file mode 100644
index 0000000000..0ddcdd4d74
--- /dev/null
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py
@@ -0,0 +1,32 @@
+from enum import Enum
+
+import typer
+
+
+class SuperHero(str, Enum):
+ hero1 = "Superman"
+ hero2 = "Spiderman"
+ hero3 = "Wonder woman"
+
+
+app = typer.Typer()
+
+
+@app.command()
+def main(
+ names: tuple[str, str, str, SuperHero] = typer.Argument(
+ ("Harry", "Hermione", "Ron", "hero3"),
+ enum_by_name=True,
+ case_sensitive=False,
+ help="Select 4 characters to play with",
+ ),
+):
+ for name in names:
+ if isinstance(name, Enum):
+ print(f"Hello {name.value}")
+ else:
+ print(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py
new file mode 100644
index 0000000000..12327332b0
--- /dev/null
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py
@@ -0,0 +1,35 @@
+from enum import Enum
+from typing import Annotated
+
+import typer
+
+
+class SuperHero(str, Enum):
+ hero1 = "Superman"
+ hero2 = "Spiderman"
+ hero3 = "Wonder woman"
+
+
+app = typer.Typer()
+
+
+@app.command()
+def main(
+ names: Annotated[
+ tuple[str, str, str, SuperHero],
+ typer.Argument(
+ enum_by_name=True,
+ help="Select 4 characters to play with",
+ case_sensitive=False,
+ ),
+ ] = ("Harry", "Hermione", "Ron", "hero3"),
+):
+ for name in names:
+ if isinstance(name, Enum):
+ print(f"Hello {name.value}")
+ else:
+ print(f"Hello {name}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py
new file mode 100644
index 0000000000..21306a5744
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py
@@ -0,0 +1,28 @@
+from enum import Enum
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+def main(user: 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__":
+ app()
diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py
new file mode 100644
index 0000000000..98a1ea26d1
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py
@@ -0,0 +1,36 @@
+from enum import Enum
+from typing import Annotated
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py
new file mode 100644
index 0000000000..d815ee2297
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py
@@ -0,0 +1,32 @@
+from enum import Enum
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py
new file mode 100644
index 0000000000..e978fd3d6c
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py
@@ -0,0 +1,36 @@
+from enum import Enum
+from typing import Annotated
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py
new file mode 100644
index 0000000000..fc53a87a30
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial005.py
@@ -0,0 +1,22 @@
+import enum
+
+import typer
+
+
+class Access(enum.IntEnum):
+ private = 1
+ protected = 2
+ public = 3
+ open = 4
+
+
+app = typer.Typer()
+
+
+@app.command()
+def main(access: Access = typer.Option("private", enum_by_name=True)):
+ typer.echo(f"Access level: {access.name} ({access.value})")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial005_an.py b/docs_src/parameter_types/enum/tutorial005_an.py
new file mode 100644
index 0000000000..96aa2c7b4d
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial005_an.py
@@ -0,0 +1,23 @@
+import enum
+from typing import Annotated
+
+import typer
+
+
+class Access(enum.IntEnum):
+ private = 1
+ protected = 2
+ public = 3
+ open = 4
+
+
+app = typer.Typer()
+
+
+@app.command()
+def main(access: Annotated[Access, typer.Option(enum_by_name=True)] = "private"):
+ typer.echo(f"Access level: {access.name} ({access.value})")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006.py
new file mode 100644
index 0000000000..aa4edd78e9
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial006.py
@@ -0,0 +1,21 @@
+from enum import Enum
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial006_an.py b/docs_src/parameter_types/enum/tutorial006_an.py
new file mode 100644
index 0000000000..0d633f792e
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial006_an.py
@@ -0,0 +1,24 @@
+from enum import Enum
+from typing import Annotated
+
+import typer
+
+
+class Food(str, Enum):
+ f1 = "Eggs"
+ f2 = "Bacon"
+ f3 = "Cheese"
+
+
+app = typer.Typer()
+
+
+@app.command()
+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])}")
+
+
+if __name__ == "__main__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial007.py b/docs_src/parameter_types/enum/tutorial007.py
new file mode 100644
index 0000000000..a4cc486c6e
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial007.py
@@ -0,0 +1,22 @@
+import enum
+import logging
+
+import typer
+
+
+class LogLevel(enum.Enum):
+ debug = logging.DEBUG
+ info = logging.INFO
+ warning = logging.WARNING
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/docs_src/parameter_types/enum/tutorial007_an.py b/docs_src/parameter_types/enum/tutorial007_an.py
new file mode 100644
index 0000000000..eaf9b3da36
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial007_an.py
@@ -0,0 +1,23 @@
+import enum
+import logging
+from typing import Annotated
+
+import typer
+
+
+class LogLevel(enum.Enum):
+ debug = logging.DEBUG
+ info = logging.INFO
+ warning = logging.WARNING
+
+
+app = typer.Typer()
+
+
+@app.command()
+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__":
+ app()
diff --git a/pyproject.toml b/pyproject.toml
index 5897e1aa8a..87ad99f0b0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -225,6 +225,7 @@ ignore = [
"docs_src/options_autocompletion/tutorial008_an_py39.py" = ["B006"]
"docs_src/options_autocompletion/tutorial009_an_py39.py" = ["B006"]
"docs_src/parameter_types/enum/tutorial003_an_py39.py" = ["B006"]
+"docs_src/parameter_types/enum/tutorial006_an.py" = ["B006"]
# Loop control variable `value` not used within loop body
"docs_src/progressbar/tutorial001_py39.py" = ["B007"]
"docs_src/progressbar/tutorial003_py39.py" = ["B007"]
diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py
new file mode 100644
index 0000000000..cc57aac480
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py
@@ -0,0 +1,50 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.arguments_with_multiple_values import tutorial003 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAMES]..." in result.output
+ assert "Arguments" in result.output
+ assert "[default: Harry, Hermione, Ron, hero3]" in result.output
+
+
+def test_defaults():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Hello Harry" in result.output
+ assert "Hello Hermione" in result.output
+ assert "Hello Ron" in result.output
+ assert "Hello Wonder woman" in result.output
+
+
+def test_invalid_args():
+ result = runner.invoke(app, ["Draco", "Hagrid"])
+ assert result.exit_code != 0
+ assert "Argument 'names' takes 4 values" in result.output
+
+
+def test_valid_args():
+ result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "hero1"])
+ assert result.exit_code == 0
+ assert "Hello Draco" in result.stdout
+ assert "Hello Hagrid" in result.stdout
+ assert "Hello Dobby" in result.stdout
+ assert "Hello Superman" in result.stdout
+
+
+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
diff --git a/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py
new file mode 100644
index 0000000000..6ff743ea2f
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py
@@ -0,0 +1,52 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.arguments_with_multiple_values import (
+ tutorial003_an as mod,
+)
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "[OPTIONS] [NAMES]..." in result.output
+ assert "Arguments" in result.output
+ assert "[default: Harry, Hermione, Ron, hero3]" in result.output
+
+
+def test_defaults():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Hello Harry" in result.output
+ assert "Hello Hermione" in result.output
+ assert "Hello Ron" in result.output
+ assert "Hello Wonder woman" in result.output
+
+
+def test_invalid_args():
+ result = runner.invoke(app, ["Draco", "Hagrid"])
+ assert result.exit_code != 0
+ assert "Argument 'names' takes 4 values" in result.output
+
+
+def test_valid_args():
+ result = runner.invoke(app, ["Draco", "Hagrid", "Dobby", "HERO1"])
+ assert result.exit_code == 0
+ assert "Hello Draco" in result.stdout
+ assert "Hello Hagrid" in result.stdout
+ assert "Hello Dobby" in result.stdout
+ assert "Hello Superman" in result.stdout
+
+
+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
diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py
new file mode 100644
index 0000000000..1407974161
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.options_with_multiple_values import tutorial002 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+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
diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py
new file mode 100644
index 0000000000..bef118c1ab
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.options_with_multiple_values import tutorial002_an as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+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
diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py
new file mode 100644
index 0000000000..fa1a1de195
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.options_with_multiple_values import tutorial003 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+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", "f1"])
+ 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", "f2"])
+ 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
diff --git a/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py
new file mode 100644
index 0000000000..d008030297
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.options_with_multiple_values import tutorial003_an as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+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", "f1"])
+ 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", "f2"])
+ 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
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py
new file mode 100644
index 0000000000..6aaf8521bd
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial005 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_int_enum_default():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Access level: private (1)" in result.output
+
+
+def test_int_enum():
+ result = runner.invoke(app, ["--access", "open"])
+ assert result.exit_code == 0
+ assert "Access level: open (4)" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ ["coverage", "run", mod.__file__, "--help"],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py
new file mode 100644
index 0000000000..d77070dc37
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial005_an as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_int_enum_default():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Access level: private (1)" in result.output
+
+
+def test_int_enum():
+ result = runner.invoke(app, ["--access", "open"])
+ assert result.exit_code == 0
+ assert "Access level: open (4)" in result.output
+
+
+def test_script():
+ result = subprocess.run(
+ ["coverage", "run", mod.__file__, "--help"],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py
new file mode 100644
index 0000000000..bf95a14e0f
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py
@@ -0,0 +1,44 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial006 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--groceries" in result.output
+ assert "[f1|f2|f3]" in result.output
+ assert "default: f1, f3" in result.output
+
+
+def test_call_no_arg():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Buying groceries: Eggs, Cheese" in result.output
+
+
+def test_call_single_arg():
+ result = runner.invoke(app, ["--groceries", "f2"])
+ assert result.exit_code == 0
+ assert "Buying groceries: Bacon" in result.output
+
+
+def test_call_multiple_arg():
+ result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"])
+ assert result.exit_code == 0
+ assert "Buying groceries: Eggs, Bacon" 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
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py
new file mode 100644
index 0000000000..fd9b2404e7
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py
@@ -0,0 +1,44 @@
+import subprocess
+import sys
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial006_an as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_help():
+ result = runner.invoke(app, ["--help"])
+ assert result.exit_code == 0
+ assert "--groceries" in result.output
+ assert "[f1|f2|f3]" in result.output
+ assert "default: f1, f3" in result.output
+
+
+def test_call_no_arg():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Buying groceries: Eggs, Cheese" in result.output
+
+
+def test_call_single_arg():
+ result = runner.invoke(app, ["--groceries", "f2"])
+ assert result.exit_code == 0
+ assert "Buying groceries: Bacon" in result.output
+
+
+def test_call_multiple_arg():
+ result = runner.invoke(app, ["--groceries", "f1", "--groceries", "f2"])
+ assert result.exit_code == 0
+ assert "Buying groceries: Eggs, Bacon" 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
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py
new file mode 100644
index 0000000000..d0b2834ee1
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial007 as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_enum_names_default():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Log level set to: WARNING" in result.output
+
+
+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"],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py
new file mode 100644
index 0000000000..62ecc82e9d
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial007_an.py
@@ -0,0 +1,29 @@
+import subprocess
+
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial007_an as mod
+
+runner = CliRunner()
+app = mod.app
+
+
+def test_enum_names_default():
+ result = runner.invoke(app)
+ assert result.exit_code == 0
+ assert "Log level set to: WARNING" in result.output
+
+
+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"],
+ capture_output=True,
+ encoding="utf-8",
+ )
+ assert "Usage" in result.stdout
diff --git a/typer/main.py b/typer/main.py
index e8c6b9e429..bd0c7d8bc5 100644
--- a/typer/main.py
+++ b/typer/main.py
@@ -616,12 +616,17 @@ def get_command_from_info(
return command
-def determine_type_convertor(type_: Any) -> Optional[Callable[[Any], Any]]:
+def determine_type_convertor(
+ type_: Any, enum_by_name: bool
+) -> Optional[Callable[[Any], Any]]:
convertor: Optional[Callable[[Any], Any]] = None
if lenient_issubclass(type_, Path):
convertor = param_path_convertor
if lenient_issubclass(type_, Enum):
- convertor = generate_enum_convertor(type_)
+ if enum_by_name:
+ convertor = generate_enum_name_convertor(type_)
+ else:
+ convertor = generate_enum_convertor(type_)
return convertor
@@ -646,6 +651,18 @@ def convertor(value: Any) -> Any:
return convertor
+def generate_enum_name_convertor(enum: type[Enum]) -> Callable[..., Any]:
+ val_map = {str(item.name): item for item in enum}
+
+ def convertor(value: Any) -> Any:
+ if value is not None:
+ val = str(value)
+ if val in val_map:
+ return val_map[val]
+
+ return convertor
+
+
def generate_list_convertor(
convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any]
) -> Callable[[Optional[Sequence[Any]]], Optional[list[Any]]]:
@@ -659,8 +676,9 @@ def internal_convertor(value: Optional[Sequence[Any]]) -> Optional[list[Any]]:
def generate_tuple_convertor(
types: Sequence[Any],
+ enum_by_name: bool,
) -> Callable[[Optional[tuple[Any, ...]]], Optional[tuple[Any, ...]]]:
- convertors = [determine_type_convertor(type_) for type_ in types]
+ convertors = [determine_type_convertor(type_, enum_by_name) for type_ in types]
def internal_convertor(
param_args: Optional[tuple[Any, ...]],
@@ -795,15 +813,16 @@ def get_click_type(
atomic=parameter_info.atomic,
)
elif lenient_issubclass(annotation, Enum):
+ if parameter_info.enum_by_name:
+ choices = [item.name for item in annotation]
+ else:
+ choices = [item.value for item in annotation]
# The custom TyperChoice is only needed for Click < 8.2.0, to parse the
# command line values matching them to the enum values. Click 8.2.0 added
# support for enum values but reading enum names.
# Passing here the list of enum values (instead of just the enum) accounts for
# Click < 8.2.0.
- return TyperChoice(
- [item.value for item in annotation],
- case_sensitive=parameter_info.case_sensitive,
- )
+ return TyperChoice(choices, case_sensitive=parameter_info.case_sensitive)
elif is_literal_type(annotation):
return click.Choice(
literal_values(annotation),
@@ -884,13 +903,14 @@ def get_click_param(
parameter_type = get_click_type(
annotation=main_type, parameter_info=parameter_info
)
- convertor = determine_type_convertor(main_type)
+ enum_by_name = parameter_info.enum_by_name
+ convertor = determine_type_convertor(main_type, enum_by_name)
if is_list:
convertor = generate_list_convertor(
convertor=convertor, default_value=default_value
)
if is_tuple:
- convertor = generate_tuple_convertor(get_args(main_type))
+ convertor = generate_tuple_convertor(get_args(main_type), enum_by_name)
if isinstance(parameter_info, OptionInfo):
if main_type is bool:
is_flag = True
diff --git a/typer/models.py b/typer/models.py
index 78d1a5354d..7e975fa4b9 100644
--- a/typer/models.py
+++ b/typer/models.py
@@ -191,6 +191,7 @@ def __init__(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -243,6 +244,7 @@ def __init__(
self.hidden = hidden
# Choice
self.case_sensitive = case_sensitive
+ self.enum_by_name = enum_by_name
# Numbers
self.min = min
self.max = max
@@ -310,6 +312,7 @@ def __init__(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -356,6 +359,7 @@ def __init__(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,
@@ -430,6 +434,7 @@ def __init__(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -476,6 +481,7 @@ def __init__(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,
diff --git a/typer/params.py b/typer/params.py
index 2a03c03e71..d0315814c6 100644
--- a/typer/params.py
+++ b/typer/params.py
@@ -48,6 +48,7 @@ def Option(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -114,6 +115,7 @@ def Option(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -179,6 +181,7 @@ def Option(
show_envvar: bool = True,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -234,6 +237,7 @@ def Option(
show_envvar=show_envvar,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,
@@ -291,6 +295,7 @@ def Argument(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -348,6 +353,7 @@ def Argument(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -404,6 +410,7 @@ def Argument(
hidden: bool = False,
# Choice
case_sensitive: bool = True,
+ enum_by_name: bool = False,
# Numbers
min: Optional[Union[int, float]] = None,
max: Optional[Union[int, float]] = None,
@@ -453,6 +460,7 @@ def Argument(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,