-
-
Notifications
You must be signed in to change notification settings - Fork 672
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
360 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,6 @@ | |
display: block; | ||
} | ||
|
||
.termy { | ||
.termy [data-termynal] { | ||
white-space: pre-wrap; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import typer | ||
|
||
|
||
def main(name: str = typer.Argument(...)): | ||
typer.echo(f"Hello {name}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import typer | ||
|
||
|
||
def main(name: str = typer.Argument(None)): | ||
if name is None: | ||
typer.echo("Hello World!") | ||
else: | ||
typer.echo(f"Hello {name}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import typer | ||
|
||
|
||
def main(name: str = typer.Argument("Wade Wilson")): | ||
typer.echo(f"Hello {name}") | ||
|
||
|
||
if __name__ == "__main__": | ||
typer.run(main) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
The same way that you have `typer.Option()` to help you define things for *CLI options*, there's also the equivalent `typer.Argument()` for *CLI arguments*. | ||
|
||
## Optional *CLI arguments* | ||
|
||
We said before that *by default*: | ||
|
||
* *CLI options* are **optional** | ||
* *CLI arguments* are **required** | ||
|
||
Again, that's how they work *by default*, and that's the convention in many CLI programs and systems. | ||
|
||
But you can change that. | ||
|
||
In fact, it's very common to have **optional** *CLI arguments*, it's way more common than having **required** *CLI options*. | ||
|
||
As an example of how it could be useful, let's see how the `ls` command works. | ||
|
||
<div class="termy"> | ||
|
||
```console | ||
// If you just type | ||
$ ls | ||
|
||
// ls will "list" the files and directories in the current directory | ||
typer tests README.md LICENSE | ||
|
||
// But it also receives an optional CLI argument | ||
$ ls ./tests/ | ||
|
||
// And then ls will list the files and directories inside of that directory from the CLI argument | ||
__init__.py test_tutorial | ||
``` | ||
|
||
</div> | ||
|
||
### An alternative *CLI argument* declaration | ||
|
||
In the <a href="https://typer.tiangolo.com/tutorial/first-steps/#add-a-cli-argument" target="_blank">First Steps</a> you saw how to add a *CLI argument*: | ||
|
||
```Python hl_lines="4" | ||
{!./src/first_steps/tutorial002.py!} | ||
``` | ||
|
||
Now let's see an alternative way to create the same *CLI argument*: | ||
|
||
```Python hl_lines="4" | ||
{!./src/arguments/tutorial001.py!} | ||
``` | ||
|
||
Before you had this function parameter: | ||
|
||
```Python | ||
name: str | ||
``` | ||
|
||
And because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms. | ||
|
||
**Typer** does the same and makes it a **required** *CLI argument*. | ||
|
||
And then we changed it to: | ||
|
||
```Python | ||
name: str = typer.Argument(...) | ||
``` | ||
|
||
The same as with `typer.Option()`, there is a `typer.Argument()`. | ||
|
||
And now as `typer.Argument()` is the "default value" of the function's parameter, in Python terms, it would mean that "it is no longer required" (in Python terms). | ||
|
||
As we no longer have the Python function default value (or its absence) to tell it if something is required or not and what is the default value, the first parameter to `typer.Argument()` serves the same purpose of defining that default value, or making it required. | ||
|
||
To make it *required*, we pass `...` as that first parameter to the function. | ||
|
||
!!! info | ||
If you hadn't seen that `...` before: it is a a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" target="_blank">part of Python and is called "Ellipsis"</a>. | ||
|
||
!!! tip | ||
This works exactly the same way `typer.Option()` does. | ||
|
||
All we did there achieves the same thing as before, a **required** *CLI argument*: | ||
|
||
<div class="termy"> | ||
|
||
```console | ||
$ python main.py | ||
|
||
Usage: main.py [OPTIONS] NAME | ||
Try "main.py --help" for help. | ||
|
||
Error: Missing argument "NAME". | ||
``` | ||
|
||
</div> | ||
|
||
It's still not very useful, but it works correctly. | ||
|
||
And being able to declare a **required** *CLI argument* using `name: str = typer.Argument(...)` that works exactly the same as `name: str` will come handy later. | ||
|
||
### Make an optional *CLI argument* | ||
|
||
Now, finally what we came for, an optional *CLI argument*. | ||
|
||
To make a *CLI argument* optional, use `typer.Argument()` and pass a different "default" as the first parameter to `typer.Argument()`, for example `None`: | ||
|
||
```Python hl_lines="4" | ||
{!./src/arguments/tutorial002.py!} | ||
``` | ||
|
||
Now we have: | ||
|
||
```Python | ||
name: str = typer.Argument(None) | ||
``` | ||
|
||
Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*). | ||
|
||
And because the first parameter passed to `typer.Argument(None)` (the new "default" value) is `None`, **Typer** knows that this is an **optional** *CLI argument*, if no value is provided when calling it in the command line, it will have that default value of `None`. | ||
|
||
Check the help: | ||
|
||
<div class="termy"> | ||
|
||
```console | ||
// First check the help | ||
$ python main.py --help | ||
|
||
Usage: main.py [OPTIONS] [NAME] | ||
|
||
Options: | ||
--install-completion Install completion for the current shell. | ||
--show-completion Show completion for the current shell, to copy it or customize the installation. | ||
--help Show this message and exit. | ||
``` | ||
|
||
</div> | ||
|
||
!!! tip | ||
Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...". | ||
|
||
Also notice that now `[NAME]` has brackets ("`[`" and "`]`") around (before it was just `NAME`) to denote that it's **optional**, not **required**. | ||
|
||
Now run it and test it: | ||
|
||
<div class="termy"> | ||
|
||
```console | ||
// With no CLI argument | ||
$ python main.py | ||
|
||
Hello World! | ||
|
||
// With one optional CLI argument | ||
$ python main.py | ||
|
||
Hello Camila | ||
``` | ||
|
||
</div> | ||
|
||
!!! tip | ||
Notice that "`Camila`" here is an optional *CLI argument*, not a *CLI option*, because we didn't use something like "`--name Camila`", we just passed "`Camila`" directly to the program/command. | ||
|
||
## An optional *CLI argument* with a default | ||
|
||
We can also make a *CLI argument* have a default value other than `None`: | ||
|
||
```Python hl_lines="4" | ||
{!./src/arguments/tutorial003.py!} | ||
``` | ||
|
||
And test it: | ||
|
||
<div class="termy"> | ||
|
||
```console | ||
// With no optional CLI argument | ||
$ python main.py | ||
|
||
Hello Wade Wilson | ||
|
||
// With one CLI argument | ||
$ python main.py Camila | ||
|
||
Hello Camila | ||
``` | ||
|
||
</div> | ||
|
||
## About *CLI arguments* help | ||
|
||
*CLI arguments* are commonly used for the most necessary things in a program. | ||
|
||
They are normally required and, when present, they are normally the main subject of whatever the command is doing. | ||
|
||
For that reason, Typer (actually Click underneath) doesn't attempt to automatically document *CLI arguments*. | ||
|
||
And you should document them as part of the command documentation, normally in a <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr>. | ||
|
||
Check the last example from the <a href="https://typer.tiangolo.com/tutorial/first-steps/#document-your-cli-app" target="_blank">First Steps</a>: | ||
|
||
```Python hl_lines="5 6 7 8 9" | ||
{!./src/first_steps/tutorial006.py!} | ||
``` | ||
|
||
Here the *CLI argument* `NAME` is documented as part of the command help text. | ||
|
||
You should document your *CLI arguments* the same way. | ||
|
||
## Other uses | ||
|
||
`typer.Argument()` has several other users. For data validation, to enable other features, etc. | ||
|
||
But you will see about that later in the docs. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import subprocess | ||
|
||
import typer | ||
from typer.testing import CliRunner | ||
|
||
from arguments import tutorial001 as mod | ||
|
||
runner = CliRunner() | ||
|
||
app = typer.Typer() | ||
app.command()(mod.main) | ||
|
||
|
||
def test_call_no_arg(): | ||
result = runner.invoke(app) | ||
assert result.exit_code != 0 | ||
assert 'Error: Missing argument "NAME".' in result.output | ||
|
||
|
||
def test_call_arg(): | ||
result = runner.invoke(app, ["Camila"]) | ||
assert result.exit_code == 0 | ||
assert "Hello Camila" in result.output | ||
|
||
|
||
def test_script(): | ||
result = subprocess.run( | ||
["coverage", "run", mod.__file__, "--help"], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
encoding="utf-8", | ||
) | ||
assert "Usage" in result.stdout |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import subprocess | ||
|
||
import typer | ||
from typer.testing import CliRunner | ||
|
||
from arguments import tutorial002 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] [NAME]" in result.output | ||
|
||
|
||
def test_call_no_arg(): | ||
result = runner.invoke(app) | ||
assert result.exit_code == 0 | ||
assert "Hello World!" in result.output | ||
|
||
|
||
def test_call_arg(): | ||
result = runner.invoke(app, ["Camila"]) | ||
assert result.exit_code == 0 | ||
assert "Hello Camila" in result.output | ||
|
||
|
||
def test_script(): | ||
result = subprocess.run( | ||
["coverage", "run", mod.__file__, "--help"], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
encoding="utf-8", | ||
) | ||
assert "Usage" in result.stdout |
Oops, something went wrong.