Skip to content

Commit

Permalink
✨ Make typer.run() not add completion scripts by default, it only m…
Browse files Browse the repository at this point in the history
…akes sense in installed apps (tiangolo#488)
  • Loading branch information
tiangolo authored and alexreg committed Nov 6, 2022
1 parent 17d37c2 commit 1d4f1b5
Show file tree
Hide file tree
Showing 24 changed files with 129 additions and 67 deletions.
40 changes: 39 additions & 1 deletion docs/tutorial/commands/index.md
Expand Up @@ -76,7 +76,7 @@ When you use `typer.run()`, **Typer** is doing more or less the same as above, i

In our case, this decorator tells **Typer** that the function below is a "`command`".

Both ways, with `typer.run()` and creating the explicit application, achieve the same.
Both ways, with `typer.run()` and creating the explicit application, achieve almost the same.

!!! tip
If your use case is solved with just `typer.run()`, that's fine, you don't have to create the explicit `app` and use `@app.command()`, etc.
Expand Down Expand Up @@ -114,6 +114,44 @@ Options:

</div>

## CLI application completion

There's a little detail that is worth noting here.

To get shell/tab completion, it's necessary to build a package that you and your users can install and **call directly**.

So instead of running a Python script like:

<div class="termy">

```console
$ python main.py

✨ Some magic here ✨
```

</div>

...It would be called like:

<div class="termy">

```console
$ magic-app

✨ Some magic here ✨
```

</div>

Having a standalone program like that allows setting up shell/tab completion.

The first step to be able to create an installable package like that is to use an explicit `typer.Typer()` app.

Later you can learn all the process to create a standalone CLI application and [Build a Package](../package.md){.internal-link target=_blank}.

But for now, it's just good to know that you are on that path. 😎

## A CLI application with multiple commands

Coming back to the CLI applications with multiple commands/subcommands, **Typer** allows creating CLI applications with multiple of them.
Expand Down
Expand Up @@ -12,10 +12,10 @@ After installing completion (for your own Python package or for **Typer CLI**),

To check it quickly without creating a new Python package, install [Typer CLI](../../typer-cli.md){.internal-link target=_blank}.

Then let's create small example script:
Then let's create small example program:

```Python
{!../docs_src/options/autocompletion/tutorial001.py!}
{!../docs_src/options_autocompletion/tutorial001.py!}
```

And let's try it with **Typer CLI** to get completion:
Expand Down Expand Up @@ -50,8 +50,8 @@ Right now we get completion for the *CLI option* names, but not for the values.

We can provide completion for the values creating an `autocompletion` function, similar to the `callback` functions from [CLI Option Callback and Context](./callback-and-context.md){.internal-link target=_blank}:

```Python hl_lines="4 5 10"
{!../docs_src/options/autocompletion/tutorial002.py!}
```Python hl_lines="4-5 14"
{!../docs_src/options_autocompletion/tutorial002.py!}
```

We return a `list` of strings from the `complete_name()` function.
Expand Down Expand Up @@ -81,8 +81,8 @@ Modify the `complete_name()` function to receive a parameter of type `str`, it w

Then we can check and return only the values that start with the incomplete value from the command line:

```Python hl_lines="6 7 8 9 10 11"
{!../docs_src/options/autocompletion/tutorial003.py!}
```Python hl_lines="6-11"
{!../docs_src/options_autocompletion/tutorial003.py!}
```

Now let's try it:
Expand Down Expand Up @@ -120,7 +120,7 @@ In the `complete_name()` function, instead of providing one `str` per completion
So, in the end, we return a `list` of `tuples` of `str`:

```Python hl_lines="3 4 5 6 7 10 11 12 13 14 15 16"
{!../docs_src/options/autocompletion/tutorial004.py!}
{!../docs_src/options_autocompletion/tutorial004.py!}
```

!!! tip
Expand Down Expand Up @@ -157,7 +157,7 @@ Instead of creating and returning a list with values (`str` or `tuple`), we can
That way our function will be a <a href="https://docs.python.org/3.8/glossary.html#index-19" class="external-link" target="_blank">generator</a> that **Typer** (actually Click) can iterate:

```Python hl_lines="10 11 12 13"
{!../docs_src/options/autocompletion/tutorial005.py!}
{!../docs_src/options_autocompletion/tutorial005.py!}
```

That simplifies our code a bit and works the same.
Expand Down Expand Up @@ -185,8 +185,8 @@ So, we will allow multiple `--name` *CLI options*.

For this we use a `List` of `str`:

```Python hl_lines="6 7 8"
{!../docs_src/options/autocompletion/tutorial006.py!}
```Python hl_lines="8-11"
{!../docs_src/options_autocompletion/tutorial006.py!}
```

And then we can use it like:
Expand All @@ -213,7 +213,7 @@ But you can access the context by declaring a function parameter of type `typer.
And from that context you can get the current values for each parameter.

```Python hl_lines="12 13 15"
{!../docs_src/options/autocompletion/tutorial007.py!}
{!../docs_src/options_autocompletion/tutorial007.py!}
```

We are getting the `names` already provided with `--name` in the command line before this completion was triggered.
Expand Down Expand Up @@ -281,8 +281,8 @@ You can print to "standard error" with `typer.echo("some text", err=True)`.

Using `err=True` tells **Typer** (actually Click) that the output should be shown in "standard error".

```Python hl_lines="12 13"
{!../docs_src/options/autocompletion/tutorial008.py!}
```Python hl_lines="13 14"
{!../docs_src/options_autocompletion/tutorial008.py!}
```

We get all the *CLI parameters* as a raw `list` of `str` by declaring a parameter with type `List[str]`, here it's named `args`.
Expand Down Expand Up @@ -319,8 +319,8 @@ Sebastian -- The type hints guy.

Of course, you can declare everything if you need it, the context, the raw *CLI parameters*, and the incomplete `str`:

```Python hl_lines="12"
{!../docs_src/options/autocompletion/tutorial009.py!}
```Python hl_lines="13"
{!../docs_src/options_autocompletion/tutorial009.py!}
```

Check it:
Expand Down
@@ -1,9 +1,12 @@
import typer

app = typer.Typer()


@app.command()
def main(name: str = typer.Option("World", help="The name to say hi to.")):
typer.echo(f"Hello {name}")


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -5,6 +5,10 @@ def complete_name(ctx, param, incomplete):
return ["Camila", "Carlos", "Sebastian"]


app = typer.Typer()


@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -14,4 +18,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -11,6 +11,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -20,4 +24,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -16,6 +16,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -25,4 +29,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -16,6 +16,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: str = typer.Option(
"World", help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -25,4 +29,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -2,11 +2,14 @@

import typer

app = typer.Typer()


@app.command()
def main(name: List[str] = typer.Option(["World"], help="The name to say hi to.")):
for each_name in name:
typer.echo(f"Hello {each_name}")


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -19,6 +19,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -29,4 +33,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -19,6 +19,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -29,4 +33,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
Expand Up @@ -20,6 +20,10 @@ def complete_name(ctx, param, incomplete):
return completion


app = typer.Typer()


@app.command()
def main(
name: List[str] = typer.Option(
["World"], help="The name to say hi to.", shell_complete=complete_name
Expand All @@ -30,4 +34,4 @@ def main(


if __name__ == "__main__":
typer.run(main)
app()
2 changes: 1 addition & 1 deletion mkdocs.yml
Expand Up @@ -41,7 +41,6 @@ nav:
- CLI Option Name: tutorial/options/name.md
- CLI Option Callback and Context: tutorial/options/callback-and-context.md
- Version CLI Option, is_eager: tutorial/options/version.md
- CLI Option autocompletion: tutorial/options/autocompletion.md
- Commands:
- Commands Intro: tutorial/commands/index.md
- Command CLI Arguments: tutorial/commands/arguments.md
Expand All @@ -51,6 +50,7 @@ nav:
- Typer Callback: tutorial/commands/callback.md
- One or Multiple Commands: tutorial/commands/one-or-multiple.md
- Using the Context: tutorial/commands/context.md
- CLI Option autocompletion: tutorial/options-autocompletion.md
- CLI Parameter Types:
- CLI Parameter Types Intro: tutorial/parameter-types/index.md
- Number: tutorial/parameter-types/number.md
Expand Down
14 changes: 14 additions & 0 deletions pyproject.toml
Expand Up @@ -88,3 +88,17 @@ skip_glob = [
"docs_src/subcommands/tutorial003/lands.py",
"docs_src/subcommands/tutorial003/main.py",
]

[tool.pytest.ini_options]
addopts = [
"--strict-config",
"--strict-markers",
]
xfail_strict = true
junit_family = "xunit2"
filterwarnings = [
"error",
'ignore:starlette.middleware.wsgi is deprecated and will be removed in a future release\..*:DeprecationWarning:starlette',
# For pytest-xdist
'ignore::DeprecationWarning:xdist',
]
2 changes: 1 addition & 1 deletion tests/test_completion/test_completion.py
Expand Up @@ -3,7 +3,7 @@
import sys
from pathlib import Path

from docs_src.first_steps import tutorial001 as mod
from docs_src.commands.index import tutorial001 as mod


def test_show_completion():
Expand Down
2 changes: 1 addition & 1 deletion tests/test_completion/test_completion_install.py
Expand Up @@ -7,7 +7,7 @@
import typer
from typer.testing import CliRunner

from docs_src.first_steps import tutorial001 as mod
from docs_src.commands.index import tutorial001 as mod

runner = CliRunner()
app = typer.Typer()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_completion/test_completion_show.py
@@ -1,7 +1,7 @@
import os
import subprocess

from docs_src.first_steps import tutorial001 as mod
from docs_src.commands.index import tutorial001 as mod


def test_completion_show_no_shell():
Expand Down
@@ -1,16 +1,12 @@
import os
import subprocess

import typer
from typer.testing import CliRunner

from docs_src.options.autocompletion import tutorial002 as mod
from docs_src.options_autocompletion import tutorial002 as mod

runner = CliRunner()

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


def test_completion():
result = subprocess.run(
Expand All @@ -31,7 +27,7 @@ def test_completion():


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

Expand Down

0 comments on commit 1d4f1b5

Please sign in to comment.