♻️ Refactor docs structure and add CLI option names
This commit is contained in:
parent
19a4f6e217
commit
d8a5236c08
61 changed files with 1512 additions and 844 deletions
23
docs/src/commands/help/tutorial002.py
Normal file
23
docs/src/commands/help/tutorial002.py
Normal 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()
|
9
docs/src/options/name/tutorial001.py
Normal file
9
docs/src/options/name/tutorial001.py
Normal 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)
|
9
docs/src/options/name/tutorial002.py
Normal file
9
docs/src/options/name/tutorial002.py
Normal 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)
|
9
docs/src/options/name/tutorial003.py
Normal file
9
docs/src/options/name/tutorial003.py
Normal 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)
|
9
docs/src/options/name/tutorial004.py
Normal file
9
docs/src/options/name/tutorial004.py
Normal 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)
|
15
docs/src/options/name/tutorial005.py
Normal file
15
docs/src/options/name/tutorial005.py
Normal 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)
|
|
@ -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
|
||||
```
|
||||
|
|
|
@ -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.
|
39
docs/tutorial/commands/arguments.md
Normal file
39
docs/tutorial/commands/arguments.md
Normal 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.
|
120
docs/tutorial/commands/help.md
Normal file
120
docs/tutorial/commands/help.md
Normal 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>
|
182
docs/tutorial/commands/index.md
Normal file
182
docs/tutorial/commands/index.md
Normal 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.
|
46
docs/tutorial/commands/name.md
Normal file
46
docs/tutorial/commands/name.md
Normal 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>
|
91
docs/tutorial/commands/one-or-multiple.md
Normal file
91
docs/tutorial/commands/one-or-multiple.md
Normal 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.
|
96
docs/tutorial/commands/options.md
Normal file
96
docs/tutorial/commands/options.md
Normal 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>
|
|
@ -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.
|
100
docs/tutorial/options/help.md
Normal file
100
docs/tutorial/options/help.md
Normal 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.
|
3
docs/tutorial/options/index.md
Normal file
3
docs/tutorial/options/index.md
Normal 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.
|
328
docs/tutorial/options/name.md
Normal file
328
docs/tutorial/options/name.md
Normal 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>
|
88
docs/tutorial/options/prompt.md
Normal file
88
docs/tutorial/options/prompt.md
Normal 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>
|
56
docs/tutorial/options/required.md
Normal file
56
docs/tutorial/options/required.md
Normal 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>
|
15
mkdocs.yml
15
mkdocs.yml
|
@ -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'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import subprocess
|
||||
from commands import tutorial003 as mod
|
||||
from commands.arguments import tutorial001 as mod
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
0
tests/test_tutorial/test_commands/test_help/__init__.py
Normal file
0
tests/test_tutorial/test_commands/test_help/__init__.py
Normal 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
|
||||
|
|
@ -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
|
0
tests/test_tutorial/test_commands/test_index/__init__.py
Normal file
0
tests/test_tutorial/test_commands/test_index/__init__.py
Normal 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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import subprocess
|
||||
from commands import tutorial002 as mod
|
||||
from commands.index import tutorial002 as mod
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
0
tests/test_tutorial/test_commands/test_name/__init__.py
Normal file
0
tests/test_tutorial/test_commands/test_name/__init__.py
Normal 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
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import subprocess
|
||||
from commands import tutorial004 as mod
|
||||
from commands.options import tutorial001 as mod
|
||||
|
||||
from typer.testing import CliRunner
|
||||
|
0
tests/test_tutorial/test_options/test_help/__init__.py
Normal file
0
tests/test_tutorial/test_options/test_help/__init__.py
Normal 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()
|
||||
|
|
@ -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()
|
||||
|
0
tests/test_tutorial/test_options/test_name/__init__.py
Normal file
0
tests/test_tutorial/test_options/test_name/__init__.py
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
0
tests/test_tutorial/test_options/test_prompt/__init__.py
Normal file
0
tests/test_tutorial/test_options/test_prompt/__init__.py
Normal 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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue