1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
# 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` CLI program works.
<div class="termy">
```console
// If you just type
$ ls
// ls will "list" the files and directories in the current directory
typer tests README.md LICENSE
// But it also receives an optional CLI argument
$ ls ./tests/
// And then ls will list the files and directories inside of that directory from the CLI argument
__init__.py test_tutorial
```
</div>
## An alternative *CLI argument* declaration
In the [First Steps](../first-steps.md#add-a-cli-argument){.internal-link target=_blank} you saw how to add a *CLI argument*:
{* docs_src/first_steps/tutorial002.py hl[4] *}
Now let's see an alternative way to create the same *CLI argument*:
{* docs_src/arguments/optional/tutorial001_an.py hl[5] *}
/// info
Typer added support for `Annotated` (and started recommending it) in version 0.9.0.
If you have an older version, you would get errors when trying to use `Annotated`.
Make sure you upgrade the Typer version to at least 0.9.0 before using `Annotated`.
///
Before, you had this function parameter:
```Python
name: str
```
And now we wrap it with `Annotated`:
```Python
name: Annotated[str]
```
Both of these versions mean the same thing, `Annotated` is part of standard Python and is there for this.
But the second version using `Annotated` allows us to pass additional metadata that can be used by **Typer**:
```Python
name: Annotated[str, typer.Argument()]
```
Now we are being explicit that `name` is a *CLI argument*. It's still a `str` and it's still required (it doesn't have a default value).
All we did there achieves the same thing as before, a **required** *CLI argument*:
<div class="termy">
```console
$ python main.py
Usage: main.py [OPTIONS] NAME
Try "main.py --help" for help.
Error: Missing argument 'NAME'.
```
</div>
It's still not very useful, but it works correctly.
And being able to declare a **required** *CLI argument* using
```Python
name: Annotated[str, typer.Argument()]
```
...that works exactly the same as
```Python
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 make sure to provide a "default" value, for example `"World"`:
{* docs_src/arguments/optional/tutorial002_an.py hl[5] *}
Now we have:
```Python
name: Annotated[str, typer.Argument()] = "World"
```
Because we are using `typer.Argument()` **Typer** will know that this is a *CLI argument* (no matter if *required* or *optional*).
Check the help:
<div class="termy">
```console
// First check the help
$ python main.py --help
Usage: main.py [OPTIONS] [NAME]
Arguments:
[NAME]
Options:
--help Show this message and exit.
```
</div>
/// tip
Notice that `NAME` is still a *CLI argument*, it's shown up there in the "`Usage: main.py` ...".
Also notice that now `[NAME]` has brackets ("`[`" and "`]`") around (before it was just `NAME`) to denote that it's **optional**, not **required**.
///
Now run it and test it:
<div class="termy">
```console
// With no CLI argument
$ python main.py
Hello World!
// With one optional CLI argument
$ python main.py Camila
Hello Camila
```
</div>
/// tip
Notice that "`Camila`" here is an optional *CLI argument*, not a *CLI option*, because we didn't use something like "`--name Camila`", we just passed "`Camila`" directly to the program.
///
## Alternative (old) `typer.Argument()` as the default value
**Typer** also supports another older alternative syntax for declaring *CLI arguments* with additional metadata.
Instead of using `Annotated`, you can use `typer.Argument()` as the default value:
{* docs_src/arguments/optional/tutorial001.py hl[4] *}
/// tip
Prefer to use the `Annotated` version if possible.
///
Before, because `name` didn't have any default value it would be a **required parameter** for the Python function, in Python terms.
When using `typer.Argument()` as the default value **Typer** does the same and makes it a **required** *CLI argument*.
We changed it to:
```Python
name: str = 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).
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, `typer.Argument()` receives a first parameter `default` that serves the same purpose of defining that default value, or making it required.
Not passing any value to the `default` argument is the same as marking it as required. But you can also explicitly mark it as *required* by passing `...` as the `default` argument, passed to `typer.Argument(default=...)`.
```Python
name: str = typer.Argument(default=...)
```
/// info
If you hadn't seen that `...` before: it is a special single value, it is <a href="https://docs.python.org/3/library/constants.html#Ellipsis" class="external-link" target="_blank">part of Python and is called "Ellipsis"</a>.
///
{* docs_src/arguments/optional/tutorial003.py hl[4] *}
And the same way, you can make it optional by passing a different `default` value, for example `None`:
{* docs_src/arguments/optional/tutorial002.py hl[6] *}
Because the first parameter passed to `typer.Argument(default=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`.
The `default` argument is the first one, so it's possible that you see code that passes the value without explicitly using `default=`, like:
```Python
name: str = typer.Argument(...)
```
...or like:
```Python
name: str = typer.Argument(None)
```
...but again, try to use `Annotated` if possible, that way your code in terms of Python will mean the same thing as with **Typer** and you won't have to remember any of these details.
|