diff --git a/docs/tutorial/parameter-types/enum.md b/docs/tutorial/parameter-types/enum.md
index 288eee5a92..7064070cba 100644
--- a/docs/tutorial/parameter-types/enum.md
+++ b/docs/tutorial/parameter-types/enum.md
@@ -96,6 +96,77 @@ 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`:
+
+//// tab | Python 3.7+
+
+```Python hl_lines="14"
+{!> ../docs_src/parameter_types/enum/tutorial004_an.py!}
+```
+
+////
+
+//// tab | Python 3.7+ non-Annotated
+
+/// tip
+
+Prefer to use the `Annotated` version if possible.
+
+///
+
+```Python hl_lines="13"
+{!> ../docs_src/parameter_types/enum/tutorial004.py!}
+```
+
+////
+
+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:
+
+//// tab | Python 3.7+
+
+```Python hl_lines="8-11 14"
+{!> ../docs_src/parameter_types/enum/tutorial005_an.py!}
+```
+
+////
+
+//// tab | Python 3.7+ non-Annotated
+
+/// tip
+
+Prefer to use the `Annotated` version if possible.
+
+///
+
+```Python hl_lines="7-10 13"
+{!../docs_src/parameter_types/enum/tutorial005.py!}
+```
+
+////
+
+```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:
@@ -153,3 +224,45 @@ Buying groceries: Eggs, Bacon
```
+
+You can also combine `enum_by_name=True` with a list of enums:
+
+//// tab | Python 3.7+
+
+```Python hl_lines="15"
+{!> ../docs_src/parameter_types/enum/tutorial006_an.py!}
+```
+
+////
+
+//// tab | Python 3.7+ non-Annotated
+
+/// tip
+
+Prefer to use the `Annotated` version if possible.
+
+///
+
+```Python hl_lines="13"
+{!> ../docs_src/parameter_types/enum/tutorial006.py!}
+```
+
+////
+
+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
+```
+
+
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..bbfd575643
--- /dev/null
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003.py
@@ -0,0 +1,29 @@
+from enum import Enum
+from typing import Tuple
+
+import typer
+
+
+class SuperHero(str, Enum):
+ hero1 = "Superman"
+ hero2 = "Spiderman"
+ hero3 = "Wonder woman"
+
+
+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__":
+ typer.run(main)
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..801ee8c6ce
--- /dev/null
+++ b/docs_src/multiple_values/arguments_with_multiple_values/tutorial003_an.py
@@ -0,0 +1,32 @@
+from enum import Enum
+from typing import Tuple
+
+import typer
+from typing_extensions import Annotated
+
+
+class SuperHero(str, Enum):
+ hero1 = "Superman"
+ hero2 = "Spiderman"
+ hero3 = "Wonder woman"
+
+
+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__":
+ typer.run(main)
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..bf3a2bf556
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002.py
@@ -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))):
+ 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)
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..c28f832681
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial002_an.py
@@ -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)
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..94715e1a13
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003.py
@@ -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)
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..534825977f
--- /dev/null
+++ b/docs_src/multiple_values/options_with_multiple_values/tutorial003_an.py
@@ -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)
diff --git a/docs_src/parameter_types/enum/tutorial004.py b/docs_src/parameter_types/enum/tutorial004.py
new file mode 100644
index 0000000000..d2ecd0c16d
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial004.py
@@ -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)
diff --git a/docs_src/parameter_types/enum/tutorial004_an.py b/docs_src/parameter_types/enum/tutorial004_an.py
new file mode 100644
index 0000000000..ca4a15e416
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial004_an.py
@@ -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)
diff --git a/docs_src/parameter_types/enum/tutorial005.py b/docs_src/parameter_types/enum/tutorial005.py
new file mode 100644
index 0000000000..2804d3960f
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial005.py
@@ -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)
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..8a0d2d4bac
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial005_an.py
@@ -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)
diff --git a/docs_src/parameter_types/enum/tutorial006.py b/docs_src/parameter_types/enum/tutorial006.py
new file mode 100644
index 0000000000..6c8a238dd1
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial006.py
@@ -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)
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..bbe605428d
--- /dev/null
+++ b/docs_src/parameter_types/enum/tutorial006_an.py
@@ -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])}")
+
+
+if __name__ == "__main__":
+ typer.run(main)
diff --git a/pyproject.toml b/pyproject.toml
index ce9d61afa3..e936bb6d22 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -184,6 +184,7 @@ ignore = [
"docs_src/options_autocompletion/tutorial008_an.py" = ["B006"]
"docs_src/options_autocompletion/tutorial009_an.py" = ["B006"]
"docs_src/parameter_types/enum/tutorial003_an.py" = ["B006"]
+"docs_src/parameter_types/enum/tutorial006_an.py" = ["B006"]
# Loop control variable `value` not used within loop body
"docs_src/progressbar/tutorial001.py" = ["B007"]
"docs_src/progressbar/tutorial003.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..7e53566854
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003.py
@@ -0,0 +1,52 @@
+import subprocess
+import sys
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.arguments_with_multiple_values import tutorial003 as mod
+
+runner = CliRunner()
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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.stdout
+
+
+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..6e5f8c2d09
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_arguments_with_multiple_values/test_tutorial003_an.py
@@ -0,0 +1,54 @@
+import subprocess
+import sys
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.multiple_values.arguments_with_multiple_values import (
+ tutorial003_an as mod,
+)
+
+runner = CliRunner()
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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.stdout
+
+
+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..1533509a77
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002.py
@@ -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
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..e38c33329e
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial002_an.py
@@ -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
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..75f457c806
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003.py
@@ -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 tutorial003 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", "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..1f8da4a329
--- /dev/null
+++ b/tests/test_tutorial/test_multiple_values/test_options_with_multiple_values/test_tutorial003_an.py
@@ -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 tutorial003_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", "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_tutorial004.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py
new file mode 100644
index 0000000000..0eab8e9be9
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004.py
@@ -0,0 +1,32 @@
+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_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_tutorial004_an.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py
new file mode 100644
index 0000000000..d183ad3cb9
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial004_an.py
@@ -0,0 +1,32 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial004_an as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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_tutorial005.py b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py
new file mode 100644
index 0000000000..db63a8dc48
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005.py
@@ -0,0 +1,32 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial005 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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..7e7ffcd02c
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial005_an.py
@@ -0,0 +1,32 @@
+import subprocess
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial005_an as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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..6afefbae02
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial006 as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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..695a817863
--- /dev/null
+++ b/tests/test_tutorial/test_parameter_types/test_enum/test_tutorial006_an.py
@@ -0,0 +1,47 @@
+import subprocess
+import sys
+
+import typer
+from typer.testing import CliRunner
+
+from docs_src.parameter_types.enum import tutorial006_an as mod
+
+runner = CliRunner()
+
+app = typer.Typer()
+app.command()(mod.main)
+
+
+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/typer/main.py b/typer/main.py
index a621bda6ad..462f6d65bf 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
@@ -644,6 +649,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[[Sequence[Any]], Optional[List[Any]]]:
@@ -657,8 +674,9 @@ def internal_convertor(value: 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, ...]],
@@ -793,10 +811,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 parameter_info.enum_by_name:
+ 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
@@ -872,13 +891,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 and parameter_info.is_flag is not False:
is_flag = True
diff --git a/typer/models.py b/typer/models.py
index 9bbe2a36d2..16b37ff962 100644
--- a/typer/models.py
+++ b/typer/models.py
@@ -192,6 +192,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,
@@ -244,6 +245,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
@@ -308,6 +310,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,
@@ -354,6 +357,7 @@ def __init__(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,
@@ -419,6 +423,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,
@@ -465,6 +470,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 2fd025c90d..846172710c 100644
--- a/typer/params.py
+++ b/typer/params.py
@@ -45,6 +45,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,
@@ -108,6 +109,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,
@@ -170,6 +172,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,
@@ -225,6 +228,7 @@ def Option(
show_envvar=show_envvar,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,
@@ -280,6 +284,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,
@@ -335,6 +340,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,
@@ -389,6 +395,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,
@@ -438,6 +445,7 @@ def Argument(
hidden=hidden,
# Choice
case_sensitive=case_sensitive,
+ enum_by_name=enum_by_name,
# Numbers
min=min,
max=max,