Skip to content
Open
27 changes: 27 additions & 0 deletions docs_src/options/callback/tutorial005.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import List, Optional

import typer


def names_callback(ctx: typer.Context, param: typer.CallbackParam, value: str):
if ctx.resilient_parsing:
return
print(f"Validating param: {param.name}")
if value is None:
return value
if "Camila" not in value:
raise typer.BadParameter("Camila must be in the list")
return value


def main(
names: Optional[List[str]] = typer.Option(None, "--name", callback=names_callback),
):
if names is None:
print("No names provided")
else:
print("Hello {}".format(", ".join(names)))


if __name__ == "__main__":
typer.run(main)
30 changes: 30 additions & 0 deletions docs_src/options/callback/tutorial005_an.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import List, Optional

import typer
from typing_extensions import Annotated


def names_callback(ctx: typer.Context, param: typer.CallbackParam, value: str):
if ctx.resilient_parsing:
return
print(f"Validating param: {param.name}")
if value is None:
return value
if "Camila" not in value:
raise typer.BadParameter("Camila must be in the list")
return value


def main(
names: Annotated[
Optional[List[str]], typer.Option("--name", callback=names_callback)
] = None,
):
if names is None:
print("No names provided")
else:
print("Hello {}".format(", ".join(names)))


if __name__ == "__main__":
typer.run(main)
56 changes: 56 additions & 0 deletions tests/test_tutorial/test_options/test_callback/test_tutorial005.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import subprocess
import sys

import typer
from typer.testing import CliRunner

from docs_src.options.callback import tutorial005 as mod

runner = CliRunner()

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


def test_1():
result = runner.invoke(app, ["--name", "Camila", "--name", "Victor"])
assert result.exit_code == 0
assert "Validating param: name" in result.output
assert "Hello Camila, Victor" in result.output


def test_2():
result = runner.invoke(app, ["--name", "rick", "--name", "Victor"])
assert result.exit_code != 0
assert "Invalid value for '--name': Camila must be in the list" in result.output


def test_3():
result = runner.invoke(app, [])
assert result.exit_code == 0
assert "No names provided" 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


def test_completion():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
capture_output=True,
encoding="utf-8",
env={
**os.environ,
"_TUTORIAL005.PY_COMPLETE": "complete_bash",
"COMP_WORDS": "tutorial005.py --",
"COMP_CWORD": "1",
},
)
assert "--name" in result.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import os
import subprocess
import sys

import typer
from typer.testing import CliRunner

from docs_src.options.callback import tutorial005_an as mod

runner = CliRunner()

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


def test_1():
result = runner.invoke(app, ["--name", "Camila", "--name", "Victor"])
assert result.exit_code == 0
assert "Validating param: name" in result.output
assert "Hello Camila, Victor" in result.output


def test_2():
result = runner.invoke(app, ["--name", "rick", "--name", "Victor"])
assert result.exit_code != 0
assert "Invalid value for '--name': Camila must be in the list" in result.output


def test_3():
result = runner.invoke(app, [])
assert result.exit_code == 0
assert "No names provided" 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


def test_completion():
result = subprocess.run(
[sys.executable, "-m", "coverage", "run", mod.__file__, " "],
capture_output=True,
encoding="utf-8",
env={
**os.environ,
"_TUTORIAL005_AN.PY_COMPLETE": "complete_bash",
"COMP_WORDS": "tutorial005_an.py --",
"COMP_CWORD": "1",
},
)
assert "--name" in result.stdout
2 changes: 1 addition & 1 deletion typer/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ def generate_list_convertor(
convertor: Optional[Callable[[Any], Any]], default_value: Optional[Any]
) -> Callable[[Sequence[Any]], Optional[List[Any]]]:
def internal_convertor(value: Sequence[Any]) -> Optional[List[Any]]:
if default_value is None and len(value) == 0:
if default_value is None and (value is None or len(value) == 0):
return None
return [convertor(v) if convertor else v for v in value]

Expand Down
Loading