From f6b61af6ae5266999cdb2a61cbfa6e24eaa925c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 29 Dec 2019 17:08:31 +0100 Subject: [PATCH] :memo: Add docs for CLI Arguments --- docs/src/arguments/tutorial001.py | 9 ++ docs/src/arguments/tutorial002.py | 12 ++ docs/src/arguments/tutorial003.py | 9 ++ docs/tutorial/arguments.md | 213 ++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 244 insertions(+) create mode 100644 docs/src/arguments/tutorial001.py create mode 100644 docs/src/arguments/tutorial002.py create mode 100644 docs/src/arguments/tutorial003.py create mode 100644 docs/tutorial/arguments.md diff --git a/docs/src/arguments/tutorial001.py b/docs/src/arguments/tutorial001.py new file mode 100644 index 0000000..e0412c8 --- /dev/null +++ b/docs/src/arguments/tutorial001.py @@ -0,0 +1,9 @@ +import typer + + +def main(name: str = typer.Argument(...)): + typer.echo(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/src/arguments/tutorial002.py b/docs/src/arguments/tutorial002.py new file mode 100644 index 0000000..c624c87 --- /dev/null +++ b/docs/src/arguments/tutorial002.py @@ -0,0 +1,12 @@ +import typer + + +def main(name: str = typer.Argument(None)): + if name is None: + typer.echo("Hello World!") + else: + typer.echo(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/src/arguments/tutorial003.py b/docs/src/arguments/tutorial003.py new file mode 100644 index 0000000..538397c --- /dev/null +++ b/docs/src/arguments/tutorial003.py @@ -0,0 +1,9 @@ +import typer + + +def main(name: str = typer.Argument("Wade Wilson")): + typer.echo(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) diff --git a/docs/tutorial/arguments.md b/docs/tutorial/arguments.md new file mode 100644 index 0000000..a1d7ff8 --- /dev/null +++ b/docs/tutorial/arguments.md @@ -0,0 +1,213 @@ +The same way that you have `typer.Option()` to help you define things for *CLI options*, there's also the equivalent `typer.Argument()` for *CLI arguments*. + +## Optional *CLI arguments* + +We said before that *by default*: + +* *CLI options* are **optional** +* *CLI arguments* are **required** + +Again, that's how they work *by default*, and that's the convention in many CLI programs and systems. + +But you can change that. + +In fact, it's very common to have **optional** *CLI arguments*, it's way more common than having **required** *CLI options*. + +As an example of how it could be useful, let's see how the `ls` command works. + +
+ +```console +// If you just type +$ ls + +// ls will "list" the files and directories in the current directory +typer tests README.md LICENSE + +// But it also receives an optional CLI argument +$ ls ./tests/ + +// And then ls will list the files and directories inside of that directory from the CLI argument +__init__.py test_tutorial +``` + +
+ +### An alternative *CLI argument* declaration + +In the First Steps you saw how to add a *CLI argument*: + +```Python hl_lines="4" +{!./src/first_steps/tutorial002.py!} +``` + +Now let's see an alternative way to create the same *CLI argument*: + +```Python hl_lines="4" +{!./src/arguments/tutorial001.py!} +``` + +Before you had this function parameter: + +```Python +name: str +``` + +And because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms. + +**Typer** does the same and makes it a **required** *CLI argument*. + +And then we changed it to: + +```Python +name: str = typer.Argument(...) +``` + +The same as with `typer.Option()`, there is a `typer.Argument()`. + +And now as `typer.Argument()` is the "default value" of the function's parameter, in Python terms, it would mean that "it is no longer required" (in Python terms). + +As we no longer have the Python function default value (or its absence) to tell it if something is required or not and what is the default value, the first parameter to `typer.Argument()` serves the same purpose of defining that default value, or making it required. + +To make it *required*, we pass `...` as that first parameter to the function. + +!!! info + If you hadn't seen that `...` before: it is a a special single value, it is part of Python and is called "Ellipsis". + +!!! tip + This works exactly the same way `typer.Option()` does. + +All we did there achieves the same thing as before, a **required** *CLI argument*: + +
+ +```console +$ python main.py + +Usage: main.py [OPTIONS] NAME +Try "main.py --help" for help. + +Error: Missing argument "NAME". +``` + +
+ +It's still not very useful, but it works correctly. + +And being able to declare a **required** *CLI argument* using `name: str = typer.Argument(...)` that works exactly the same as `name: str` will come handy later. + +### Make an optional *CLI argument* + +Now, finally what we came for, an optional *CLI argument*. + +To make a *CLI argument* optional, use `typer.Argument()` and pass a different "default" as the first parameter to `typer.Argument()`, for example `None`: + +```Python hl_lines="4" +{!./src/arguments/tutorial002.py!} +``` + +Now we have: + +```Python +name: str = typer.Argument(None) +``` + +Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*). + +And because the first parameter passed to `typer.Argument(None)` (the new "default" value) is `None`, **Typer** knows that this is an **optional** *CLI argument*, if no value is provided when calling it in the command line, it will have that default value of `None`. + +Check the help: + +
+ +```console +// First check the help +$ python main.py --help + +Usage: main.py [OPTIONS] [NAME] + +Options: + --install-completion Install completion for the current shell. + --show-completion Show completion for the current shell, to copy it or customize the installation. + --help Show this message and exit. +``` + +
+ +!!! tip + Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...". + + Also notice that now `[NAME]` has brackets ("`[`" and "`]`") around (before it was just `NAME`) to denote that it's **optional**, not **required**. + +Now run it and test it: + +
+ +```console +// With no CLI argument +$ python main.py + +Hello World! + +// With one optional CLI argument +$ python main.py + +Hello Camila +``` + +
+ +!!! tip + Notice that "`Camila`" here is an optional *CLI argument*, not a *CLI option*, because we didn't use something like "`--name Camila`", we just passed "`Camila`" directly to the program/command. + +## An optional *CLI argument* with a default + +We can also make a *CLI argument* have a default value other than `None`: + +```Python hl_lines="4" +{!./src/arguments/tutorial003.py!} +``` + +And test it: + +
+ +```console +// With no optional CLI argument +$ python main.py + +Hello Wade Wilson + +// With one CLI argument +$ python main.py Camila + +Hello Camila +``` + +
+ +## About *CLI arguments* help + +*CLI arguments* are commonly used for the most necessary things in a program. + +They are normally required and, when present, they are normally the main subject of whatever the command is doing. + +For that reason, Typer (actually Click underneath) doesn't attempt to automatically document *CLI arguments*. + +And you should document them as part of the command documentation, normally in a docstring. + +Check the last example from the First Steps: + +```Python hl_lines="5 6 7 8 9" +{!./src/first_steps/tutorial006.py!} +``` + +Here the *CLI argument* `NAME` is documented as part of the command help text. + +You should document your *CLI arguments* the same way. + +## Other uses + +`typer.Argument()` has several other users. For data validation, to enable other features, etc. + +But you will see about that later in the docs. diff --git a/mkdocs.yml b/mkdocs.yml index be5ed5f..adcfba9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,6 +24,7 @@ nav: - Tutorial - User Guide - Intro: 'tutorial/index.md' - First Steps: 'tutorial/first-steps.md' - CLI Options: 'tutorial/options.md' + - CLI Arguments: 'tutorial/arguments.md' - Alternatives, Inspiration and Comparisons: 'alternatives.md' - Help Typer - Get Help: 'help-typer.md' - Development - Contributing: 'contributing.md'