♻️ Refactor docs structure and add CLI option names

This commit is contained in:
Sebastián Ramírez 2020-01-02 12:49:28 +01:00
parent 19a4f6e217
commit d8a5236c08
61 changed files with 1512 additions and 844 deletions

View file

@ -0,0 +1,23 @@
import typer
app = typer.Typer()
@app.command(help="Create a new user with USERNAME.")
def create(username: str):
"""
Some internal utility function to create.
"""
typer.echo(f"Creating user: {username}")
@app.command(help="Delete a user with USERNAME.")
def delete(username: str):
"""
Some internal utility function to delete.
"""
typer.echo(f"Deleting user: {username}")
if __name__ == "__main__":
app()

View file

@ -0,0 +1,9 @@
import typer
def main(user_name: str = typer.Option(..., "--name")):
typer.echo(f"Hello {user_name}")
if __name__ == "__main__":
typer.run(main)

View file

@ -0,0 +1,9 @@
import typer
def main(user_name: str = typer.Option(..., "--name", "-n")):
typer.echo(f"Hello {user_name}")
if __name__ == "__main__":
typer.run(main)

View file

@ -0,0 +1,9 @@
import typer
def main(user_name: str = typer.Option(..., "-n")):
typer.echo(f"Hello {user_name}")
if __name__ == "__main__":
typer.run(main)

View file

@ -0,0 +1,9 @@
import typer
def main(user_name: str = typer.Option(..., "--user-name", "-n")):
typer.echo(f"Hello {user_name}")
if __name__ == "__main__":
typer.run(main)

View file

@ -0,0 +1,15 @@
import typer
def main(
name: str = typer.Option(..., "--name", "-n"),
formal: bool = typer.Option(False, "--formal", "-f"),
):
if formal:
typer.echo(f"Good day Ms. {name}.")
else:
typer.echo(f"Hello {name}")
if __name__ == "__main__":
typer.run(main)

View file

@ -1,4 +1,4 @@
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*.
Let's see how to configure *CLI arguments* with `typer.Argument()`.
## Optional *CLI arguments*
@ -63,20 +63,15 @@ And then we changed it to:
name: str = typer.Argument(...)
```
The same as with `typer.Option()`, there is a `typer.Argument()`.
But now as `typer.Argument()` is the "default value" of the function's parameter, it would mean that "it is no longer required" (in Python terms).
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 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.
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.
To make it *required*, we pass `...` as the first function argument passed to `typer.Argument(...)`.
!!! 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">
@ -150,7 +145,7 @@ $ python main.py
Hello World!
// With one optional CLI argument
$ python main.py
$ python main.py Camila
Hello Camila
```

View file

@ -1,555 +0,0 @@
We have seen how to create a CLI program with possibly several *CLI Options* and *CLI Arguments*.
But **Typer** allows you to create CLI programs with several commands (also known as subcommands).
For example, the program `git` has several commands.
One command of `git` is `git push`. And `git push` in turn takes its own *CLI arguments* and *CLI options*.
For example:
<div class="termy">
```console
// The push command with no parameters
$ git push
---> 100%
// The push command with one CLI option --set-upstream and 2 CLI arguments
$ git push --set-upstream origin master
---> 100%
```
</div>
Another command of `git` is `git pull`, it also has some *CLI parameters*.
It's like if the same big program `git` had several small programs inside.
!!! tip
A command looks the same as a *CLI argument*, it's just some name without a preceding `--`. But commands have predefined name, and are used to group different sets of functionalities into the same CLI application.
## Command or subcommand
It's common to call a CLI program a "command".
But when one of these programs have subcommands, those subcommands are also frequently called just "commands".
Have that in mind so you don't get confused.
Here I'll use **CLI application** or **program** to refer to the program you are building in Python with Typer, and **command** to refer to one of these "subcommands" of your program.
## Explicit application
Before creating CLI applications with multiple commands/subcommands we need to understand how to create an explicit `typer.Typer()` application.
In the *CLI Options* and *CLI Argument* tutorials you have seen how to create a single function and then pass that function to `typer.run()`.
For example:
```Python hl_lines="9"
{!./src/first_steps/tutorial002.py!}
```
But that is actually a shortcut. Under the hood, **Typer** converts that to a CLI application with `typer.Typer()` and executes it. All that inside of `typer.run()`.
There's also a more explicit way to achieve the same:
```Python hl_lines="3 6 12"
{!./src/commands/tutorial001.py!}
```
When you use `typer.run()`, **Typer** is doing more or less the same as above, it will:
* Create a new `typer.Typer()` "application".
* Create a new "`command`" with your function.
* Call the same "application" as if it was a function with "`app()`".
!!! info "`@decorator` Info"
That `@something` syntax in Python is called a "decorator".
You put it on top of a function. Like a pretty decorative hat (I guess that's where the term came from).
A "decorator" takes the function below and does something with it.
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.
!!! 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.
You might want to do that later when your app needs the extra features, but if it doesn't need them yet, that's fine.
If you run the second example, with the explicit `app`, it works exactly the same:
<div class="termy">
```console
// Without a CLI argument
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument "NAME".
// With the NAME CLI argument
$ python main.py Camila
Hello Camila
// Asking for 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>
## 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.
Now that you know how to create an explicit `typer.Typer()` application and add one command, let's see how to add multiple commands.
Let's say that we have a CLI application to manage users.
We'll have a command to `create` users and another command to `delete` them.
To begin, let's say it can only create and delete one single predefined user:
```Python hl_lines="6 11"
{!./src/commands/tutorial002.py!}
```
Now we have a CLI application with 2 commands, `create` and `delete`:
<div class="termy">
```console
// Check the help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test them
$ python main.py create
Creating user: Hiro Hamada
$ python main.py delete
Deleting user: Hiro Hamada
// Now we have 2 commands! 🎉
```
</div>
Notice that the help text now shows the 2 commands: `create` and `delete`.
!!! tip
By default, the names of the commands are generated from the function name.
## Command *CLI arguments*
The same way as with a CLI application with a single command, subcommands (or just "commands") can also have their own *CLI arguments*:
```Python hl_lines="7 12"
{!./src/commands/tutorial003.py!}
```
<div class="termy">
```console
// Check the help for create
$ python main.py create --help
Usage: main.py create [OPTIONS] USERNAME
Options:
--help Show this message and exit.
// Call it with a CLI argument
$ python main.py create Camila
Creating user: Camila
// The same for delete
$ python main.py delete Camila
Deleting user: Camila
```
</div>
!!! tip
Everything to the *right* of the *command* are *CLI parameters* (*CLI arguments* and *CLI options*) for that command.
!!! note "Technical Details"
Actually, it's everything to the right of that command, *before any subcommand*.
It's possible to have groups of *subcommands*, it's like if one *command* also had *subcommands*. And then those *subcommands* could have their own *CLI parameters*, taking their own *CLI parameters*.
You will see about them later in another section.
## Command *CLI options*
Commands can also have their own *CLI options*.
In fact, each command can have different *CLI arguments* and *CLI options*:
```Python hl_lines="7 13 14 24 33"
{!./src/commands/tutorial004.py!}
```
Here we have multiple commands, with different *CLI parameters*:
* `create`:
* `username`: a *CLI argument*.
* `delete`:
* `username`: a *CLI argument*.
* `--force`: a *CLI option*, if not provided, it's prompted.
* `delete-all`:
* `--force`: a *CLI option*, if not provided, it's prompted.
* `init`:
* Doesn't take any *CLI parameters*.
<div class="termy">
```console
// Check the help
python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
delete-all
info
```
</div>
!!! tip
Check the command `delete-all`, by default command names are generated from the function name, replacing `_` with `-`.
Test it:
<div class="termy">
```console
// Check the command create
$ python main.py create Camila
Creating user: Camila
// Now test the command delete
$ python main.py delete Camila
# Are you sure you want to delete the user? [y/N]: $ y
Deleting user: Camila
$ python main.py delete Wade
# Are you sure you want to delete the user? [y/N]: $ n
Operation cancelled
// And finally, the command delete-all
// Notice it doesn't have CLI arguments, only a CLI option
$ python main.py delete-all
# Are you sure you want to delete ALL users? [y/N]: $ y
Deleting all users
$ python main.py delete-all
# Are you sure you want to delete ALL users? [y/N]: $ n
Operation cancelled
// And if you pass the --force CLI option, it doesn't need to confirm
$ python main.py delete-all --force
Deleting all users
// And init that doesn't take any CLI parameter
$ python main.py init
Initializing user database
```
</div>
## Command help
The same as before, you can add help for the commands in the docstrings and the *CLI options*.
And the `typer.Typer()` application receives a parameter `help` that you can pass with the main help text for your CLI program:
```Python hl_lines="3 8 9 10 20 23 24 25 26 27 39 42 43 44 45 46 55 56 57"
{!./src/commands/tutorial005.py!}
```
Check it:
<div class="termy">
```console
// Check the new help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Awesome CLI user manager.
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.
Commands:
create Create a new user with USERNAME.
delete Delete a user with USERNAME.
delete-all Delete ALL users in the database.
init Initialize the users database.
// Now the commands have inline help 🎉
// Check the help for create
$ python main.py create --help
Usage: main.py create [OPTIONS] USERNAME
Create a new user with USERNAME.
Options:
--help Show this message and exit.
// Check the help for delete
$ python main.py delete --help
Usage: main.py delete [OPTIONS] USERNAME
Delete a user with USERNAME.
If --force is not used, will ask for confirmation.
Options:
--force / --no-force Force deletion without confirmation. [required]
--help Show this message and exit.
// Check the help for delete-all
$ python main.py delete-all --help
Usage: main.py delete-all [OPTIONS]
Delete ALL users in the database.
If --force is not used, will ask for confirmation.
Options:
--force / --no-force Force deletion without confirmation. [required]
--help Show this message and exit.
// Check the help for init
$ python main.py init --help
Usage: main.py init [OPTIONS]
Initialize the users database.
Options:
--help Show this message and exit.
```
</div>
!!! tip
`typer.Typer()` receives several other parameters for other things, we'll see that later.
You will also see how to use "Callbacks" later, and those include a way to add this same help message in a function docstring.
## Custom command name
By default, the command names are generated from the function name.
So, if your function is something like:
```Python
def create(username: str):
...
```
Then the command name will be `create`.
But if you already had a function called `create()` somewhere in your code, you would have to name your CLI function differently.
And what if you wanted the command to still be named `create`?
For this, you can set the name of the command in the first parameter for the `@app.command()` decorator:
```Python hl_lines="6 11"
{!./src/commands/tutorial006.py!}
```
Now, even though the functions are named `cli_create_user()` and `cli_delete_user()`, the commands will still be named `create` and `delete`:
<div class="termy">
```console
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test it
$ python main.py create Camila
Creating user: Camila
```
</div>
## One command vs multiple commands
You might have noticed that if you create a single command, as in the first example:
```Python hl_lines="3 6 12"
{!./src/commands/tutorial001.py!}
```
**Typer** is smart enough to create a CLI application with that single function as the main CLI application, not as a command/subcommand:
<div class="termy">
```console
// Without a CLI argument
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument "NAME".
// With the NAME CLI argument
$ python main.py Camila
Hello Camila
// Asking for help
$ python main.py
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 it doesn't show a command `main`, even though the function name is `main`.
But if you add multiple commands, **Typer** will create one *CLI command* for each one of them:
```Python hl_lines="6 11"
{!./src/commands/tutorial002.py!}
```
Here we have 2 commands `create` and `delete`:
<div class="termy">
```console
// Check the help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test the commands
$ python main.py create
Creating user: Hiro Hamada
$ python main.py delete
Deleting user: Hiro Hamada
```
</div>
!!! tip
This is **Typer**'s behavior by default, but you could still create a CLI application with one single command, like:
```
$ python main.py main
```
instead of just:
```
$ python main.py
```
You will learn more about this in the section about application Callbacks.
## Decorator Technical Details
When you use `@app.command()` the function under the decorator is registered in the **Typer** application and is then used later by the application.
But Typer doesn't modify that function itself, the function is left as is.
That means that if your function is simple enough that you could create it without using `typer.Option()` or `typer.Argument()`, you could use the same function for a **Typer** application and a **FastAPI** application putting both decorators on top, or similar tricks.
!!! note "Click Technical Details"
This behavior is a design difference with Click.
In Click, when you add a `@click.command()` decorator it actually modifies the function underneath and replaces it with an object.

View file

@ -0,0 +1,39 @@
The same way as with a CLI application with a single command, subcommands (or just "commands") can also have their own *CLI arguments*:
```Python hl_lines="7 12"
{!./src/commands/arguments/tutorial001.py!}
```
<div class="termy">
```console
// Check the help for create
$ python main.py create --help
Usage: main.py create [OPTIONS] USERNAME
Options:
--help Show this message and exit.
// Call it with a CLI argument
$ python main.py create Camila
Creating user: Camila
// The same for delete
$ python main.py delete Camila
Deleting user: Camila
```
</div>
!!! tip
Everything to the *right* of the *command* are *CLI parameters* (*CLI arguments* and *CLI options*) for that command.
!!! note "Technical Details"
Actually, it's everything to the right of that command, *before any subcommand*.
It's possible to have groups of *subcommands*, it's like if one *command* also had *subcommands*. And then those *subcommands* could have their own *CLI parameters*, taking their own *CLI parameters*.
You will see about them later in another section.

View file

@ -0,0 +1,120 @@
The same as before, you can add help for the commands in the docstrings and the *CLI options*.
And the `typer.Typer()` application receives a parameter `help` that you can pass with the main help text for your CLI program:
```Python hl_lines="3 8 9 10 20 23 24 25 26 27 39 42 43 44 45 46 55 56 57"
{!./src/commands/help/tutorial001.py!}
```
Check it:
<div class="termy">
```console
// Check the new help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
Awesome CLI user manager.
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.
Commands:
create Create a new user with USERNAME.
delete Delete a user with USERNAME.
delete-all Delete ALL users in the database.
init Initialize the users database.
// Now the commands have inline help 🎉
// Check the help for create
$ python main.py create --help
Usage: main.py create [OPTIONS] USERNAME
Create a new user with USERNAME.
Options:
--help Show this message and exit.
// Check the help for delete
$ python main.py delete --help
Usage: main.py delete [OPTIONS] USERNAME
Delete a user with USERNAME.
If --force is not used, will ask for confirmation.
Options:
--force / --no-force Force deletion without confirmation. [required]
--help Show this message and exit.
// Check the help for delete-all
$ python main.py delete-all --help
Usage: main.py delete-all [OPTIONS]
Delete ALL users in the database.
If --force is not used, will ask for confirmation.
Options:
--force / --no-force Force deletion without confirmation. [required]
--help Show this message and exit.
// Check the help for init
$ python main.py init --help
Usage: main.py init [OPTIONS]
Initialize the users database.
Options:
--help Show this message and exit.
```
</div>
!!! tip
`typer.Typer()` receives several other parameters for other things, we'll see that later.
You will also see how to use "Callbacks" later, and those include a way to add this same help message in a function docstring.
## Overwrite command help
You will probably be better adding the help text as a docstring to your functions, but if for some reason you wanted to overwrite it, you can use the `help` function argument passed to `@app.command()`:
```Python hl_lines="6 14"
{!./src/commands/help/tutorial002.py!}
```
Check it:
<div class="termy">
```console
// Check the help
$ python main.py --help
// Notice it uses the help passed to @app.command()
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create Create a new user with USERNAME.
delete Delete a user with USERNAME.
// It uses "Create a new user with USERNAME." instead of "Some internal utility function to create."
```
</div>

View file

@ -0,0 +1,182 @@
We have seen how to create a CLI program with possibly several *CLI Options* and *CLI Arguments*.
But **Typer** allows you to create CLI programs with several commands (also known as subcommands).
For example, the program `git` has several commands.
One command of `git` is `git push`. And `git push` in turn takes its own *CLI arguments* and *CLI options*.
For example:
<div class="termy">
```console
// The push command with no parameters
$ git push
---> 100%
// The push command with one CLI option --set-upstream and 2 CLI arguments
$ git push --set-upstream origin master
---> 100%
```
</div>
Another command of `git` is `git pull`, it also has some *CLI parameters*.
It's like if the same big program `git` had several small programs inside.
!!! tip
A command looks the same as a *CLI argument*, it's just some name without a preceding `--`. But commands have predefined name, and are used to group different sets of functionalities into the same CLI application.
## Command or subcommand
It's common to call a CLI program a "command".
But when one of these programs have subcommands, those subcommands are also frequently called just "commands".
Have that in mind so you don't get confused.
Here I'll use **CLI application** or **program** to refer to the program you are building in Python with Typer, and **command** to refer to one of these "subcommands" of your program.
## Explicit application
Before creating CLI applications with multiple commands/subcommands we need to understand how to create an explicit `typer.Typer()` application.
In the *CLI Options* and *CLI Argument* tutorials you have seen how to create a single function and then pass that function to `typer.run()`.
For example:
```Python hl_lines="9"
{!./src/first_steps/tutorial002.py!}
```
But that is actually a shortcut. Under the hood, **Typer** converts that to a CLI application with `typer.Typer()` and executes it. All that inside of `typer.run()`.
There's also a more explicit way to achieve the same:
```Python hl_lines="3 6 12"
{!./src/commands/index/tutorial001.py!}
```
When you use `typer.run()`, **Typer** is doing more or less the same as above, it will:
* Create a new `typer.Typer()` "application".
* Create a new "`command`" with your function.
* Call the same "application" as if it was a function with "`app()`".
!!! info "`@decorator` Info"
That `@something` syntax in Python is called a "decorator".
You put it on top of a function. Like a pretty decorative hat (I guess that's where the term came from).
A "decorator" takes the function below and does something with it.
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.
!!! 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.
You might want to do that later when your app needs the extra features, but if it doesn't need them yet, that's fine.
If you run the second example, with the explicit `app`, it works exactly the same:
<div class="termy">
```console
// Without a CLI argument
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument "NAME".
// With the NAME CLI argument
$ python main.py Camila
Hello Camila
// Asking for 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>
## 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.
Now that you know how to create an explicit `typer.Typer()` application and add one command, let's see how to add multiple commands.
Let's say that we have a CLI application to manage users.
We'll have a command to `create` users and another command to `delete` them.
To begin, let's say it can only create and delete one single predefined user:
```Python hl_lines="6 11"
{!./src/commands/index/tutorial002.py!}
```
Now we have a CLI application with 2 commands, `create` and `delete`:
<div class="termy">
```console
// Check the help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test them
$ python main.py create
Creating user: Hiro Hamada
$ python main.py delete
Deleting user: Hiro Hamada
// Now we have 2 commands! 🎉
```
</div>
Notice that the help text now shows the 2 commands: `create` and `delete`.
!!! tip
By default, the names of the commands are generated from the function name.
## Decorator Technical Details
When you use `@app.command()` the function under the decorator is registered in the **Typer** application and is then used later by the application.
But Typer doesn't modify that function itself, the function is left as is.
That means that if your function is simple enough that you could create it without using `typer.Option()` or `typer.Argument()`, you could use the same function for a **Typer** application and a **FastAPI** application putting both decorators on top, or similar tricks.
!!! note "Click Technical Details"
This behavior is a design difference with Click.
In Click, when you add a `@click.command()` decorator it actually modifies the function underneath and replaces it with an object.

View file

@ -0,0 +1,46 @@
By default, the command names are generated from the function name.
So, if your function is something like:
```Python
def create(username: str):
...
```
Then the command name will be `create`.
But if you already had a function called `create()` somewhere in your code, you would have to name your CLI function differently.
And what if you wanted the command to still be named `create`?
For this, you can set the name of the command in the first parameter for the `@app.command()` decorator:
```Python hl_lines="6 11"
{!./src/commands/name/tutorial001.py!}
```
Now, even though the functions are named `cli_create_user()` and `cli_delete_user()`, the commands will still be named `create` and `delete`:
<div class="termy">
```console
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test it
$ python main.py create Camila
Creating user: Camila
```
</div>

View file

@ -0,0 +1,91 @@
You might have noticed that if you create a single command, as in the first example:
```Python hl_lines="3 6 12"
{!./src/commands/tutorial001.py!}
```
**Typer** is smart enough to create a CLI application with that single function as the main CLI application, not as a command/subcommand:
<div class="termy">
```console
// Without a CLI argument
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument "NAME".
// With the NAME CLI argument
$ python main.py Camila
Hello Camila
// Asking for help
$ python main.py
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 it doesn't show a command `main`, even though the function name is `main`.
But if you add multiple commands, **Typer** will create one *CLI command* for each one of them:
```Python hl_lines="6 11"
{!./src/commands/tutorial002.py!}
```
Here we have 2 commands `create` and `delete`:
<div class="termy">
```console
// Check the help
$ python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
// Test the commands
$ python main.py create
Creating user: Hiro Hamada
$ python main.py delete
Deleting user: Hiro Hamada
```
</div>
!!! tip
This is **Typer**'s behavior by default, but you could still create a CLI application with one single command, like:
```
$ python main.py main
```
instead of just:
```
$ python main.py
```
You will learn more about this in the section about application Callbacks.

View file

@ -0,0 +1,96 @@
Commands can also have their own *CLI options*.
In fact, each command can have different *CLI arguments* and *CLI options*:
```Python hl_lines="7 13 14 24 33"
{!./src/commands/options/tutorial001.py!}
```
Here we have multiple commands, with different *CLI parameters*:
* `create`:
* `username`: a *CLI argument*.
* `delete`:
* `username`: a *CLI argument*.
* `--force`: a *CLI option*, if not provided, it's prompted.
* `delete-all`:
* `--force`: a *CLI option*, if not provided, it's prompted.
* `init`:
* Doesn't take any *CLI parameters*.
<div class="termy">
```console
// Check the help
python main.py --help
Usage: main.py [OPTIONS] COMMAND [ARGS]...
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.
Commands:
create
delete
delete-all
info
```
</div>
!!! tip
Check the command `delete-all`, by default command names are generated from the function name, replacing `_` with `-`.
Test it:
<div class="termy">
```console
// Check the command create
$ python main.py create Camila
Creating user: Camila
// Now test the command delete
$ python main.py delete Camila
# Are you sure you want to delete the user? [y/N]: $ y
Deleting user: Camila
$ python main.py delete Wade
# Are you sure you want to delete the user? [y/N]: $ n
Operation cancelled
// And finally, the command delete-all
// Notice it doesn't have CLI arguments, only a CLI option
$ python main.py delete-all
# Are you sure you want to delete ALL users? [y/N]: $ y
Deleting all users
$ python main.py delete-all
# Are you sure you want to delete ALL users? [y/N]: $ n
Operation cancelled
// And if you pass the --force CLI option, it doesn't need to confirm
$ python main.py delete-all --force
Deleting all users
// And init that doesn't take any CLI parameter
$ python main.py init
Initializing user database
```
</div>

View file

@ -1,263 +0,0 @@
## *CLI options* with help
In the *First Steps* section you saw how to add help for a CLI app/command by adding it to a function's <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr>.
Here's how that last example looked like:
```Python
{!./src/first_steps/tutorial006.py!}
```
Now we'll add a *help* section to the *CLI options*:
```Python hl_lines="6 7"
{!./src/options/tutorial001.py!}
```
We are replacing the default values we had before with `typer.Option()`.
As we no longer have a default value there, the first parameter to `typer.Option()` serves the same purpose of defining that default value.
So, if we had:
```Python
lastname: str = ""
```
now we write:
```Python
lastname: str = typer.Option("")
```
And both forms achieve the same: a *CLI option* with a default value of an empty string (`""`).
And then we can pass the `help` keyword parameter:
```Python
lastname: str = typer.Option("", help="this option does this and that")
```
to create the help for that *CLI option*.
Copy that example from above to a file `main.py`.
Test it:
<div class="termy">
```console
$ python main.py --help
Usage: main.py [OPTIONS] NAME
Say hi to NAME, optionally with a --lastname.
If --formal is used, say hi very formally.
Options:
--lastname TEXT Last name of person to greet.
--formal / --no-formal Say hi formally.
--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.
// Now you have a help text for the --lastname and --formal CLI options 🎉
```
</div>
## Make a *CLI option* required
We said before that *by default*:
* *CLI options* are **optional**
* *CLI arguments* are **required**
Well, that's how they work *by default*, and that's the convention in many CLI programs and systems.
But if you really want, you can change that.
To make a *CLI option* required, pass `...` to `typer.Option()`.
!!! 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>.
That will tell **Typer** that it's still a *CLI option*, but it doesn't have a default value, and it's required.
Let's make the `--lastname` a required *CLI option*.
We'll also simplify the example to focus on the new parts:
```Python hl_lines="4"
{!./src/options/tutorial002.py!}
```
!!! tip
You could still add `help` to `typer.Option()` as before, but we are omitting it here to simplify the example.
And test it:
<div class="termy">
```console
// Pass the NAME CLI argument
$ python main.py Camila
// We didn't pass the now required --lastname CLI option
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing option "--lastname".
// Now update it to pass the required --lastname CLI option
$ python main.py Camila --lastname Gutiérrez
Hello Camila Gutiérrez
// And if you check the help
$ python main.py --help
Usage: main.py [OPTIONS] NAME
Options:
--lastname TEXT [required]
--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.
// It now tells you that --lastname is required 🎉
```
</div>
## Prompt for a *CLI option*
It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`:
```Python hl_lines="4"
{!./src/options/tutorial003.py!}
```
And then your program will ask the user for it in the terminal:
<div class="termy">
```console
// Call it with the NAME CLI argument
$ python main.py Camila
// It asks for the missing CLI option --lastname
# Lastname: $ Gutiérrez
Hello Camila Gutiérrez
```
</div>
### Customize the prompt
You can also set a custom prompt, passing the string that you want to use instead of just `True`:
```Python hl_lines="6"
{!./src/options/tutorial004.py!}
```
And then your program will ask for it using with your custom prompt:
<div class="termy">
```console
// Call it with the NAME CLI argument
$ python main.py Camila
// It uses the custom prompt
# Please tell me your last name: $ Gutiérrez
Hello Camila Gutiérrez
```
</div>
## Confirmation prompt
In some cases you could want to prompt for something and then ask the user to confirm it by typing it twice.
You can do it passing the parameter `confirmation_prompt=True`.
Let's say it's a CLI app to delete a project:
```Python hl_lines="4"
{!./src/options/tutorial005.py!}
```
And it will prompt the user for a value and then for the confirmation:
<div class="termy">
```console
$ python main.py
// Your app will first prompt for the project name, and then for the confirmation
# Project name: $ Old Project
# Repeat for confirmation: $ Old Project
Deleting project Old Project
// If the user doesn't type the same, receives an error and a new prompt
$ python main.py
# Project name: $ Old Project
# Repeat for confirmation: $ New Spice
Error: the two entered values do not match
# Project name: $ Old Project
# Repeat for confirmation: $ Old Project
Deleting project Old Project
// Now it works 🎉
```
</div>
## Show default in help
You can tell Typer to show the default value in the help text with `show_default=True`:
```Python hl_lines="4"
{!./src/options/tutorial006.py!}
```
And it will show up in the help text:
<div class="termy">
```console
$ python main.py
Hello Wade Wilson
// Show the help
$ python main.py --help
Usage: main.py [OPTIONS]
Options:
--fullname TEXT [default: Wade Wilson]
--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 the `[default: Wade Wilson]` in the help text.
## Other uses
`typer.Option()` has several other users. For data validation, to enable other features, etc.
But you will see about that later in the docs.

View file

@ -0,0 +1,100 @@
In the *First Steps* section you saw how to add help for a CLI app/command by adding it to a function's <abbr title="a multi-line string as the first expression inside a function (not assigned to any variable) used for documentation">docstring</abbr>.
Here's how that last example looked like:
```Python
{!./src/first_steps/tutorial006.py!}
```
Now we'll add a *help* section to the *CLI options*:
```Python hl_lines="6 7"
{!./src/options/help/tutorial001.py!}
```
We are replacing the default values we had before with `typer.Option()`.
As we no longer have a default value there, the first parameter to `typer.Option()` serves the same purpose of defining that default value.
So, if we had:
```Python
lastname: str = ""
```
now we write:
```Python
lastname: str = typer.Option("")
```
And both forms achieve the same: a *CLI option* with a default value of an empty string (`""`).
And then we can pass the `help` keyword parameter:
```Python
lastname: str = typer.Option("", help="this option does this and that")
```
to create the help for that *CLI option*.
Copy that example from above to a file `main.py`.
Test it:
<div class="termy">
```console
$ python main.py --help
Usage: main.py [OPTIONS] NAME
Say hi to NAME, optionally with a --lastname.
If --formal is used, say hi very formally.
Options:
--lastname TEXT Last name of person to greet.
--formal / --no-formal Say hi formally.
--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.
// Now you have a help text for the --lastname and --formal CLI options 🎉
```
</div>
## Show default in help
You can tell Typer to show the default value in the help text with `show_default=True`:
```Python hl_lines="4"
{!./src/options/help/tutorial002.py!}
```
And it will show up in the help text:
<div class="termy">
```console
$ python main.py
Hello Wade Wilson
// Show the help
$ python main.py --help
Usage: main.py [OPTIONS]
Options:
--fullname TEXT [default: Wade Wilson]
--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 the `[default: Wade Wilson]` in the help text.

View file

@ -0,0 +1,3 @@
In the next short sections we will see how to modify *CLI options* using `typer.Option()`.
`typer.Option()` works very similarly to `typer.Argument()`, but has some extra features that we'll see next.

View file

@ -0,0 +1,328 @@
By default **Typer** will create a *CLI option* name from the function parameter.
So, if you have a function with:
```Python
def main(user_name: str = None):
pass
```
or
```Python
def main(user_name: str = typer.Option(None)):
pass
```
**Typer** will create a *CLI option*:
```
--user-name
```
But you can customize it if you want to.
Let's say the function parameter name is `user_name` as above, but you want the *CLI option* to be just `--name`.
You can pass the *CLI option* name that you want to have in the next positional argument passed to `typer.Option()`:
```Python hl_lines="4"
{!./src/options/name/tutorial001.py!}
```
Here you are passing the string `"--name"` as the second positional argument to `typer.Option()`.
!!! info
"<a href="https://docs.python.org/3.8/glossary.html#term-argument" target="_blank">Positional</a>" means that it's not a function argument with a keyword name.
For example `show_default=True` is a keyword argument. "`show_default`" is the keyword.
But in `"--name"` there's no `option_name="--name"` or something similar, it's just the string value `"--name"` that goes in `typer.Option()` after the `...` value passed in the first position.
That's a "positional argument" in a function.
Check it:
<div class="termy">
```console
$ python main.py --help
// Notice the --name instead of --user-name
Usage: main.py [OPTIONS]
Options:
--name TEXT [required]
--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.
// Try it
$ python --name Camila
Hello Camila
```
</div>
## *CLI option* short names
A short name is a *CLI option* name with a single dash (`-`) instead of 2 (`--`) and a single letter, like `-n` instead of `--name`.
For example, the `ls` program has a *CLI option* named `--size`, and the same *CLI option* also has a short name `-s`:
<div class="termy">
```console
// With the long name --size
$ ls ./myproject --size
12 first-steps.md 4 intro.md
// With the short name -s
$ ls ./myproject -s
12 first-steps.md 4 intro.md
// Both CLI option names do the same
```
</div>
### *CLI option* short names together
Short names have another feature, when they have a single letter, as in `-s`, you can put several of these *CLI options* together, with a single dash.
For example, the `ls` program has these 2 *CLI options* (among others):
* `--size`: show the sizes of the listed files.
* `--human`: show a human-readable format, like `1MB` instead of just `1024`.
And these 2 *CLI options* have short versions too:
* `--size`: short version `-s`.
* `--human`: short version `-h`.
So, you can put them together with `-sh` or `-hs`:
<div class="termy">
```console
// Call ls with long CLI options
$ ls --size --human
12K first-steps.md 4.0K intro.md
// Now with short versions
$ ls -s -h
12K first-steps.md 4.0K intro.md
// And with short versions together
$ ls -sh
12K first-steps.md 4.0K intro.md
// Order in short versions doesn't matter
$ ls -hs
12K first-steps.md 4.0K intro.md
// They all work the same 🎉
```
</div>
### *CLI option* short names with values
When you use *CLI options* with short names, you can put them together if they are just boolean flags, like `--size` or `--human`.
But if you have a *CLI option* `--file` with a short name `-f` that takes a value, if you put it with other short names for *CLI options*, you have to put it as the last letter, so that it can receive the value that comes right after.
For example, let's say you are decompressing/extracting a file `myproject.tar.gz` with the program `tar`.
You can pass these *CLI option* short names to `tar`:
* `-x`: means "e`X`tract", to decompress and extract the contents.
* `-v`: means "`V`erbose", to print on the screen what it is doing, so you can know that it's decompressing each file and can entertain yourself while you wait.
* `-f`: means "`F`ile", this one requires a value, the compressed file to extract (in our example, this is `myproject.tar.gz`).
* So if you use all the short names together, this `-f` has to come last, to receive the value that comes next to it.
For example:
<div class="termy">
```console
$ tar -xvf myproject.tar.gz
myproject/
myproject/first-steps.md
myproject/intro.md
// But if you put the -f before
$ tar -fxv myproject.tar.gz
// You get an ugly error
tar: You must specify one of the blah, blah, error, error
```
</div>
### Defining *CLI option* short names
In **Typer** you can also define *CLI option* short names the same way you can customize the long names.
`typer.Option()` receives as a first function argument the default value, e.g. `None`, and all the next *positional* values are to define the *CLI option* name(s).
!!! tip
Remember the *positional* function arguments are those that don't have a keyword.
All the other function arguments/parameters you pass to `typer.Option()` like `prompt=True` and `help="This option blah, blah"` require the keyword.
You can overwrite the *CLI option* name to use as in the previous example, but you can also declare extra alternatives, including short names.
For example, extending the previous example, let's add a *CLI option* short name `-n`:
```Python hl_lines="4"
{!./src/options/name/tutorial002.py!}
```
Here we are overwriting the *CLI option* name that by default would be `--user-name`, and we are defining it to be `--name`. And we are also declaring a *CLI option* short name of `-n`.
Check it:
<div class="termy">
```console
// Check the help
$ python main.py --help
// Notice the two CLI option names -n and --name
Usage: main.py [OPTIONS]
Options:
-n, --name TEXT [required]
--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.
// Try the short version
$ python main.py -n Camila
Hello Camila
```
</div>
### *CLI option* only short name
If you only declare a short name like `-n` then that will be the only *CLI option* name. And neither `--name` nor `--user-name` will be available.
```Python hl_lines="4"
{!./src/options/name/tutorial003.py!}
```
Check it:
<div class="termy">
```console
$ python main.py --help
// Notice there's no --name nor --user-name, only -n
Usage: main.py [OPTIONS]
Options:
-n TEXT [required]
--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.
// Try it
$ python main.py -n Camila
Hello Camila
```
</div>
### *CLI option* short name and default
Continuing with the example above, as **Typer** allows you to declare a *CLI option* as having only a short name, if you want to have the default long name plus a short name, you have to declare both explicitly:
```Python hl_lines="4"
{!./src/options/name/tutorial004.py!}
```
Check it:
<div class="termy">
```console
$ python main.py --help
// Notice that we have the long version --user-name back
// and we also have the short version -n
Usage: main.py [OPTIONS]
Options:
-n, --user-name TEXT [required]
--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.
// Try it
$ python main.py --user-name Camila
Hello Camila
// And try the short version
$ python main.py -n Camila
```
</div>
### *CLI option* short names together
You can create multiple short names and use them together.
You don't have to do anything special for it to work (apart from declaring those short versions):
```Python hl_lines="5 6"
{!./src/options/name/tutorial005.py!}
```
!!! tip
Notice that, again, we are declaring the long and short version of the *CLI option* names.
Check it:
<div class="termy">
```console
$ python main.py --help
// We now have short versions -n and -f
// And also long versions --name and --formal
Usage: main.py [OPTIONS]
Options:
-n, --name TEXT [required]
-f, --formal
--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.
// Try the short versions
$ python main.py -n Camila -f
Good day Ms. Camila.
// And try the 2 short versions together
// See how -n has to go last, to be able to get the value
$ python main.py -fn Camila
Good day Ms. Camila.
```
</div>

View file

@ -0,0 +1,88 @@
It's also possible to, instead of just showing an error, ask for the missing value with `prompt=True`:
```Python hl_lines="4"
{!./src/options/prompt/tutorial001.py!}
```
And then your program will ask the user for it in the terminal:
<div class="termy">
```console
// Call it with the NAME CLI argument
$ python main.py Camila
// It asks for the missing CLI option --lastname
# Lastname: $ Gutiérrez
Hello Camila Gutiérrez
```
</div>
## Customize the prompt
You can also set a custom prompt, passing the string that you want to use instead of just `True`:
```Python hl_lines="6"
{!./src/options/prompt/tutorial002.py!}
```
And then your program will ask for it using with your custom prompt:
<div class="termy">
```console
// Call it with the NAME CLI argument
$ python main.py Camila
// It uses the custom prompt
# Please tell me your last name: $ Gutiérrez
Hello Camila Gutiérrez
```
</div>
## Confirmation prompt
In some cases you could want to prompt for something and then ask the user to confirm it by typing it twice.
You can do it passing the parameter `confirmation_prompt=True`.
Let's say it's a CLI app to delete a project:
```Python hl_lines="4"
{!./src/options/prompt/tutorial003.py!}
```
And it will prompt the user for a value and then for the confirmation:
<div class="termy">
```console
$ python main.py
// Your app will first prompt for the project name, and then for the confirmation
# Project name: $ Old Project
# Repeat for confirmation: $ Old Project
Deleting project Old Project
// If the user doesn't type the same, receives an error and a new prompt
$ python main.py
# Project name: $ Old Project
# Repeat for confirmation: $ New Spice
Error: the two entered values do not match
# Project name: $ Old Project
# Repeat for confirmation: $ Old Project
Deleting project Old Project
// Now it works 🎉
```
</div>

View file

@ -0,0 +1,56 @@
We said before that *by default*:
* *CLI options* are **optional**
* *CLI arguments* are **required**
Well, that's how they work *by default*, and that's the convention in many CLI programs and systems.
But if you really want, you can change that.
To make a *CLI option* required, pass `...` to `typer.Option()`.
!!! 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>.
That will tell **Typer** that it's still a *CLI option*, but it doesn't have a default value, and it's required.
Let's make `--lastname` a required *CLI option*:
```Python hl_lines="4"
{!./src/options/required/tutorial001.py!}
```
And test it:
<div class="termy">
```console
// Pass the NAME CLI argument
$ python main.py Camila
// We didn't pass the now required --lastname CLI option
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing option "--lastname".
// Now update it to pass the required --lastname CLI option
$ python main.py Camila --lastname Gutiérrez
Hello Camila Gutiérrez
// And if you check the help
$ python main.py --help
Usage: main.py [OPTIONS] NAME
Options:
--lastname TEXT [required]
--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.
// It now tells you that --lastname is required 🎉
```
</div>

View file

@ -23,9 +23,20 @@ nav:
- Tutorial - User Guide:
- Tutorial - User Guide - Intro: 'tutorial/index.md'
- First Steps: 'tutorial/first-steps.md'
- CLI Options: 'tutorial/options.md'
- CLI Arguments: 'tutorial/arguments.md'
- Commands: 'tutorial/commands.md'
- CLI Options:
- CLI Options Intro: 'tutorial/options/index.md'
- CLI Options with Help: 'tutorial/options/help.md'
- Required CLI Options: 'tutorial/options/required.md'
- CLI Option Prompt: 'tutorial/options/prompt.md'
- CLI Option Name: 'tutorial/options/name.md'
- Commands:
- Commands Intro: 'tutorial/commands/index.md'
- Command CLI Arguments: 'tutorial/commands/arguments.md'
- Command CLI Options: 'tutorial/commands/options.md'
- Command Help: 'tutorial/commands/help.md'
- Custom Command Name: 'tutorial/commands/name.md'
- One or Multiple Commands: 'tutorial/commands/one-or-multiple.md'
- CLI Parameter Types:
- CLI Parameter Types Intro: 'tutorial/parameter-types/index.md'
- Number: 'tutorial/parameter-types/number.md'

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial003 as mod
from commands.arguments import tutorial001 as mod
from typer.testing import CliRunner

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial005 as mod
from commands.help import tutorial001 as mod
from typer.testing import CliRunner

View file

@ -0,0 +1,55 @@
import subprocess
from commands.help import tutorial002 as mod
from typer.testing import CliRunner
app = mod.app
runner = CliRunner()
def test_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "create" in result.output
assert "Create a new user with USERNAME." in result.output
assert "delete" in result.output
assert "Delete a user with USERNAME." in result.output
assert "Some internal utility function to create." not in result.output
assert "Some internal utility function to delete." not in result.output
def test_help_create():
result = runner.invoke(app, ["create", "--help"])
assert result.exit_code == 0
assert "Create a new user with USERNAME." in result.output
assert "Some internal utility function to create." not in result.output
def test_help_create():
result = runner.invoke(app, ["delete", "--help"])
assert result.exit_code == 0
assert "Delete a user with USERNAME." in result.output
assert "Some internal utility function to delete." not in result.output
def test_create():
result = runner.invoke(app, ["create", "Camila"])
assert result.exit_code == 0
assert "Creating user: Camila" in result.output
def test_delete():
result = runner.invoke(app, ["delete", "Camila"])
assert result.exit_code == 0
assert "Deleting user: 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

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial001 as mod
from commands.index import tutorial001 as mod
from typer.testing import CliRunner

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial002 as mod
from commands.index import tutorial002 as mod
from typer.testing import CliRunner

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial006 as mod
from commands.name import tutorial001 as mod
from typer.testing import CliRunner

View file

@ -1,5 +1,5 @@
import subprocess
from commands import tutorial004 as mod
from commands.options import tutorial001 as mod
from typer.testing import CliRunner

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial001 as mod
from options.help import tutorial001 as mod
runner = CliRunner()

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial006 as mod
from options.help import tutorial002 as mod
runner = CliRunner()

View file

@ -0,0 +1,34 @@
import subprocess
import typer
from typer.testing import CliRunner
from options.name import tutorial001 as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "--name TEXT" in result.output
assert "--user-name" not in result.output
def test_call():
result = runner.invoke(app, ["--name", "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

View file

@ -0,0 +1,40 @@
import subprocess
import typer
from typer.testing import CliRunner
from options.name import tutorial002 as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "-n, --name TEXT" in result.output
assert "--user-name" not in result.output
def test_call():
result = runner.invoke(app, ["-n", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_call_long():
result = runner.invoke(app, ["--name", "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

View file

@ -0,0 +1,35 @@
import subprocess
import typer
from typer.testing import CliRunner
from options.name import tutorial003 as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "-n TEXT" in result.output
assert "--user-name" not in result.output
assert "--name" not in result.output
def test_call():
result = runner.invoke(app, ["-n", "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

View file

@ -0,0 +1,40 @@
import subprocess
import typer
from typer.testing import CliRunner
from options.name import tutorial004 as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "-n, --user-name TEXT" in result.output
assert "--name" not in result.output
def test_call():
result = runner.invoke(app, ["-n", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_call_long():
result = runner.invoke(app, ["--user-name", "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

View file

@ -0,0 +1,51 @@
import subprocess
import typer
from typer.testing import CliRunner
from options.name import tutorial005 as mod
runner = CliRunner()
app = typer.Typer()
app.command()(mod.main)
def test_option_help():
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
assert "-n, --name TEXT" in result.output
assert "-f, --formal" in result.output
def test_call():
result = runner.invoke(app, ["-n", "Camila"])
assert result.exit_code == 0
assert "Hello Camila" in result.output
def test_call_formal():
result = runner.invoke(app, ["-n", "Camila", "-f"])
assert result.exit_code == 0
assert "Good day Ms. Camila." in result.output
def test_call_formal_condensed():
result = runner.invoke(app, ["-fn", "Camila"])
assert result.exit_code == 0
assert "Good day Ms. Camila." in result.output
def test_call_condensed_wrong_order():
result = runner.invoke(app, ["-nf", "Camila"])
assert result.exit_code != 0
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

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial003 as mod
from options.prompt import tutorial001 as mod
runner = CliRunner()

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial004 as mod
from options.prompt import tutorial002 as mod
runner = CliRunner()

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial005 as mod
from options.prompt import tutorial003 as mod
runner = CliRunner()

View file

@ -3,7 +3,7 @@ import subprocess
import typer
from typer.testing import CliRunner
from options import tutorial002 as mod
from options.required import tutorial001 as mod
runner = CliRunner()

View file

@ -106,7 +106,7 @@ def Option(
def Argument(
# Parameter
default: Optional[Any],
*param_decls: str,
*,
callback: Optional[Callable[[click.Context, click.Parameter, str], Any]] = None,
metavar: Optional[str] = None,
expose_value: bool = True,
@ -142,7 +142,9 @@ def Argument(
return ArgumentInfo(
# Parameter
default=default,
param_decls=param_decls,
# Arguments can only have one param declaration
# it will be generated from the param name
param_decls=None,
callback=callback,
metavar=metavar,
expose_value=expose_value,