📝 Add docs for CLI parameter types
This commit is contained in:
parent
355c223bcb
commit
073a72469c
30 changed files with 929 additions and 0 deletions
0
docs/src/parameter_types/datetime/__init__.py
Normal file
0
docs/src/parameter_types/datetime/__init__.py
Normal file
12
docs/src/parameter_types/datetime/tutorial001.py
Normal file
12
docs/src/parameter_types/datetime/tutorial001.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from datetime import datetime
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
def main(birth: datetime):
|
||||
typer.echo(f"Interesting day to be born: {birth}")
|
||||
typer.echo(f"Birth hour: {birth.hour}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
15
docs/src/parameter_types/datetime/tutorial002.py
Normal file
15
docs/src/parameter_types/datetime/tutorial002.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from datetime import datetime
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
def main(
|
||||
launch_date: datetime = typer.Argument(
|
||||
..., formats=["%Y-%m-%d", "%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S", "%m/%d/%Y"]
|
||||
)
|
||||
):
|
||||
typer.echo(f"Launch will be at: {launch_date}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/enum/__init__.py
Normal file
0
docs/src/parameter_types/enum/__init__.py
Normal file
17
docs/src/parameter_types/enum/tutorial001.py
Normal file
17
docs/src/parameter_types/enum/tutorial001.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from enum import Enum
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
class NeuralNetwork(str, Enum):
|
||||
simple = "simple"
|
||||
conv = "conv"
|
||||
lstm = "lstm"
|
||||
|
||||
|
||||
def main(network: NeuralNetwork = NeuralNetwork.simple):
|
||||
typer.echo(f"Training neural network of type: {network.value}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
19
docs/src/parameter_types/enum/tutorial002.py
Normal file
19
docs/src/parameter_types/enum/tutorial002.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from enum import Enum
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
class NeuralNetwork(str, Enum):
|
||||
simple = "simple"
|
||||
conv = "conv"
|
||||
lstm = "lstm"
|
||||
|
||||
|
||||
def main(
|
||||
network: NeuralNetwork = typer.Option(NeuralNetwork.simple, case_sensitive=False)
|
||||
):
|
||||
typer.echo(f"Training neural network of type: {network.value}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/file/__init__.py
Normal file
0
docs/src/parameter_types/file/__init__.py
Normal file
10
docs/src/parameter_types/file/tutorial001.py
Normal file
10
docs/src/parameter_types/file/tutorial001.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(config: typer.FileText = typer.Option(...)):
|
||||
for line in config:
|
||||
typer.echo(f"Config line: {line}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
10
docs/src/parameter_types/file/tutorial002.py
Normal file
10
docs/src/parameter_types/file/tutorial002.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(config: typer.FileTextWrite = typer.Option(...)):
|
||||
config.write("Some config written by the app")
|
||||
typer.echo("Config written")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
13
docs/src/parameter_types/file/tutorial003.py
Normal file
13
docs/src/parameter_types/file/tutorial003.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(file: typer.FileBinaryRead = typer.Option(...)):
|
||||
processed_total = 0
|
||||
for bytes_chunk in file:
|
||||
# Process the bytes in bytes_chunk
|
||||
processed_total += len(bytes_chunk)
|
||||
typer.echo(f"Processed bytes total: {processed_total}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
17
docs/src/parameter_types/file/tutorial004.py
Normal file
17
docs/src/parameter_types/file/tutorial004.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(file: typer.FileBinaryWrite = typer.Option(...)):
|
||||
first_line_str = "some settings\n"
|
||||
# You cannot write str directly to a binary file, you have to encode it to get bytes
|
||||
first_line_bytes = first_line_str.encode("utf-8")
|
||||
# Then you can write the bytes
|
||||
file.write(first_line_bytes)
|
||||
# This is already bytes, it starts with b"
|
||||
second_line = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o"
|
||||
file.write(second_line)
|
||||
typer.echo("Binary file written")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
10
docs/src/parameter_types/file/tutorial005.py
Normal file
10
docs/src/parameter_types/file/tutorial005.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(config: typer.FileText = typer.Option(..., mode="a")):
|
||||
config.write("This is a single line\n")
|
||||
typer.echo("Config line written")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/index/__init__.py
Normal file
0
docs/src/parameter_types/index/__init__.py
Normal file
14
docs/src/parameter_types/index/tutorial001.py
Normal file
14
docs/src/parameter_types/index/tutorial001.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(
|
||||
name: str, age: int = 20, height_meters: float = 1.89, female: bool = True,
|
||||
):
|
||||
typer.echo(f"NAME is {name}, of type: {type(name)}")
|
||||
typer.echo(f"--age is {age}, of type: {type(age)}")
|
||||
typer.echo(f"--height-meters is {height_meters}, of type: {type(height_meters)}")
|
||||
typer.echo(f"--female is {female}, of type: {type(female)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/number/__init__.py
Normal file
0
docs/src/parameter_types/number/__init__.py
Normal file
15
docs/src/parameter_types/number/tutorial001.py
Normal file
15
docs/src/parameter_types/number/tutorial001.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(
|
||||
id: int = typer.Argument(..., min=0, max=1000),
|
||||
age: int = typer.Option(20, min=18),
|
||||
score: float = typer.Option(0, max=100),
|
||||
):
|
||||
typer.echo(f"ID is {id}")
|
||||
typer.echo(f"--age is {age}")
|
||||
typer.echo(f"--score is {score}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
15
docs/src/parameter_types/number/tutorial002.py
Normal file
15
docs/src/parameter_types/number/tutorial002.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
import typer
|
||||
|
||||
|
||||
def main(
|
||||
id: int = typer.Argument(..., min=0, max=1000),
|
||||
rank: int = typer.Option(0, max=10, clamp=True),
|
||||
score: float = typer.Option(0, min=0, max=100, clamp=True),
|
||||
):
|
||||
typer.echo(f"ID is {id}")
|
||||
typer.echo(f"--rank is {rank}")
|
||||
typer.echo(f"--score is {score}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/path/__init__.py
Normal file
0
docs/src/parameter_types/path/__init__.py
Normal file
17
docs/src/parameter_types/path/tutorial001.py
Normal file
17
docs/src/parameter_types/path/tutorial001.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
def main(config: Path = typer.Option(...)):
|
||||
if config.is_file():
|
||||
text = config.read_text()
|
||||
typer.echo(f"Config file contents: {text}")
|
||||
elif config.is_dir():
|
||||
typer.echo("Config is a directory, will use all its config files")
|
||||
elif not config.exists():
|
||||
typer.echo("The config doesn't exist")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
22
docs/src/parameter_types/path/tutorial002.py
Normal file
22
docs/src/parameter_types/path/tutorial002.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
def main(
|
||||
config: Path = typer.Option(
|
||||
...,
|
||||
exists=True,
|
||||
file_okay=True,
|
||||
dir_okay=False,
|
||||
writable=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
)
|
||||
):
|
||||
text = config.read_text()
|
||||
typer.echo(f"Config file contents: {text}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
0
docs/src/parameter_types/uuid/__init__.py
Normal file
0
docs/src/parameter_types/uuid/__init__.py
Normal file
12
docs/src/parameter_types/uuid/tutorial001.py
Normal file
12
docs/src/parameter_types/uuid/tutorial001.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from uuid import UUID
|
||||
|
||||
import typer
|
||||
|
||||
|
||||
def main(user_id: UUID):
|
||||
typer.echo(f"USER_ID is {user_id}")
|
||||
typer.echo(f"UUID version is: {user_id.version}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
typer.run(main)
|
79
docs/tutorial/parameter-types/datetime.md
Normal file
79
docs/tutorial/parameter-types/datetime.md
Normal file
|
@ -0,0 +1,79 @@
|
|||
You can specify a *CLI parameter* as a Python <a href="https://docs.python.org/3/library/datetime.html" target="_blank">`datetime`</a>.
|
||||
|
||||
Your function will receive a standard Python `datetime` object, and again, your editor will give you completion, etc.
|
||||
|
||||
```Python hl_lines="1 6 7 8"
|
||||
{!./src/parameter_types/datetime/tutorial001.py!}
|
||||
```
|
||||
|
||||
By default, it will expect a datetime in ISO format in the input.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --help
|
||||
|
||||
Usage: main.py [OPTIONS] [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]
|
||||
|
||||
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.
|
||||
|
||||
// Pass a datetime
|
||||
$ python main.py 1956-01-31T10:00:00
|
||||
|
||||
Interesting day to be born: 1956-01-31 10:00:00
|
||||
Birth hour: 10
|
||||
|
||||
// An invalid date
|
||||
$ python main.py july-19-1989
|
||||
|
||||
Usage: main.py [OPTIONS] [%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d%H:%M:%S]
|
||||
|
||||
Error: Invalid value for "[%Y-%m-%d|%Y-%m-%dT%H:%M:%S|%Y-%m-%d %H:%M:%S]": invalid datetime format: july-19-1989. (choose from %Y-%m-%d, %Y-%m-%dT%H:%M:%S, %Y-%m-%d %H:%M:%S)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Custom date format
|
||||
|
||||
You can also customize the formats received for the `datetime` with the `formats` parameter.
|
||||
|
||||
`formats` receives a list of strings with the date formats that would be passed to <a href="https://docs.python.org/3/library/datetime.html#datetime.date.strftime" target="_blank">datetime.strptime()</a>.
|
||||
|
||||
For example, let's imagine that you want to accept an ISO formatted datetime, but for some strange reason, you also want to accept a format with:
|
||||
|
||||
* first the month
|
||||
* then the day
|
||||
* then the year
|
||||
* separated with "`/`"
|
||||
|
||||
...It's a crazy example, but let's say you also needed that strange format:
|
||||
|
||||
```Python hl_lines="8"
|
||||
{!./src/parameter_types/datetime/tutorial002.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice the last string in `formats`: `"%m/%d/%Y"`.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// ISO dates work
|
||||
$ python main.py 1969-10-29
|
||||
|
||||
Launch will be at: 1969-10-29 00:00:00
|
||||
|
||||
// But the strange custom format also works
|
||||
$ python main.py 10/29/1969
|
||||
|
||||
Launch will be at: 1969-10-29 00:00:00
|
||||
```
|
||||
|
||||
</div>
|
68
docs/tutorial/parameter-types/enum.md
Normal file
68
docs/tutorial/parameter-types/enum.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
To define a *CLI parameter* that can take a value from a predefined set of values you can use a standard Python <a href="https://docs.python.org/3/library/enum.html" target="_blank">`enum.Enum`</a>:
|
||||
|
||||
```Python hl_lines="1 6 7 8 9 12 13"
|
||||
{!./src/parameter_types/enum/tutorial001.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
Notice that the function parameter `network` will be an `Enum`, not a `str`.
|
||||
|
||||
To get the `str` value in your function's code use `network.value`.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --help
|
||||
|
||||
// Notice the predefined values [simple|conv|lstm]
|
||||
Usage: main.py [OPTIONS]
|
||||
|
||||
Options:
|
||||
--network [simple|conv|lstm]
|
||||
--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 --network conv
|
||||
|
||||
Training neural network of type: conv
|
||||
|
||||
// Invalid value
|
||||
$ python main.py --network capsule
|
||||
|
||||
Usage: main.py [OPTIONS]
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--network": invalid choice: capsule. (choose from simple, conv, lstm)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Case insensitive Enum choices
|
||||
|
||||
You can make an `Enum` (choice) *CLI parameter* be case-insensitive with the `case_sensitive` parameter:
|
||||
|
||||
```Python hl_lines="13"
|
||||
{!./src/parameter_types/enum/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then the values of the `Enum` will be checked no matter if lower case, upper case, or a mix:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Notice the upper case CONV
|
||||
$ python main.py --network CONV
|
||||
|
||||
Training neural network of type: conv
|
||||
|
||||
// A mix also works
|
||||
$ python main.py --network LsTm
|
||||
|
||||
Training neural network of type: lstm
|
||||
```
|
||||
|
||||
</div>
|
248
docs/tutorial/parameter-types/file.md
Normal file
248
docs/tutorial/parameter-types/file.md
Normal file
|
@ -0,0 +1,248 @@
|
|||
Apart from `Path` *CLI parameters* you can also declare some types of "files".
|
||||
|
||||
!!! tip
|
||||
In most of the cases you are probably fine just using `Path`.
|
||||
|
||||
You can read and write data with `Path` the same way.
|
||||
|
||||
The difference is that these types will give you a Python <a href="https://docs.python.org/3/glossary.html#term-file-object" target="_blank">file-like object</a> instead of a Python <a href="https://docs.python.org/3/library/pathlib.html#basic-use" target="_blank">Path</a>.
|
||||
|
||||
A "file-like object" is the same type of object returned by `open()` as in:
|
||||
|
||||
```Python
|
||||
with open('file.txt') as f:
|
||||
# Here f is the file-like object
|
||||
read_data = f.read()
|
||||
print(read_data)
|
||||
```
|
||||
|
||||
But in some special use cases you might want to use these special types. For example if you are migrating an existing application.
|
||||
|
||||
## `FileText` reading
|
||||
|
||||
`typer.FileText` gives you a file-like object for reading text, you will get `str` data from it.
|
||||
|
||||
This means that even if your file has text written in a non-english language, e.g. a `text.txt` file with:
|
||||
|
||||
```
|
||||
la cigüeña trae al niño
|
||||
```
|
||||
|
||||
You will have a `str` with the text inside, e.g.:
|
||||
|
||||
```Python
|
||||
content = "la cigüeña trae al niño"
|
||||
```
|
||||
|
||||
instead of having `bytes`, e.g.:
|
||||
|
||||
```Python
|
||||
content = b"la cig\xc3\xbce\xc3\xb1a trae al ni\xc3\xb1o"
|
||||
```
|
||||
|
||||
You will get all the correct editor support, attributes, methods, etc for the file-like object:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!./src/parameter_types/file/tutorial001.py!}
|
||||
```
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Create a quick text config
|
||||
$ echo "some settings" > config.txt
|
||||
|
||||
// Add another line to the config to test it
|
||||
$ echo "some more settings" >> config.txt
|
||||
|
||||
// Now run your program
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config line: some settings
|
||||
|
||||
Config line: some more settings
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## `FileTextWrite`
|
||||
|
||||
For writing text, you can use `typer.FileTextWrite`:
|
||||
|
||||
```Python hl_lines="4 5"
|
||||
{!./src/parameter_types/file/tutorial002.py!}
|
||||
```
|
||||
|
||||
This would be for writing human text, like:
|
||||
|
||||
```
|
||||
some settings
|
||||
la cigüeña trae al niño
|
||||
```
|
||||
|
||||
...not to write binary `bytes`.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --config text.txt
|
||||
|
||||
Config written
|
||||
|
||||
// Check the contents of the file
|
||||
$ cat text.txt
|
||||
|
||||
Some config written by the app
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
!!! info "Technical Details"
|
||||
`typer.FileTextWrite` is a just a convenience class.
|
||||
|
||||
It's the same as using `typer.FileText` and setting `mode="w"`. You will learn about `mode` later below.
|
||||
|
||||
## `FileBinaryRead`
|
||||
|
||||
To read binary data you can use `typer.FileBinaryRead`.
|
||||
|
||||
You will receive `bytes` from it.
|
||||
|
||||
It's useful for reading binary files like images:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!./src/parameter_types/file/tutorial003.py!}
|
||||
```
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --file lena.jpg
|
||||
|
||||
Processed bytes total: 512
|
||||
Processed bytes total: 1024
|
||||
Processed bytes total: 1536
|
||||
Processed bytes total: 2048
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## `FileBinaryWrite`
|
||||
|
||||
To write binary data you can use `typer.FileBinaryWrite`.
|
||||
|
||||
You would write `bytes` to it.
|
||||
|
||||
It's useful for writing binary files like images.
|
||||
|
||||
Have in mind that you have to pass `bytes` to its `.write()` method, not `str`.
|
||||
|
||||
If you have a `str`, you have to encode it first to get `bytes`.
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!./src/parameter_types/file/tutorial004.py!}
|
||||
```
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --file binary.dat
|
||||
|
||||
Binary file written
|
||||
|
||||
// Check the binary file was created
|
||||
$ ls ./binary.dat
|
||||
|
||||
./binary.dat
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## File *CLI parameter* configurations
|
||||
|
||||
You can use several configuration parameters for these types (classes) in `typer.Option()` and `typer.Argument()`:
|
||||
|
||||
* `mode`: controls the "<a href="https://docs.python.org/3/library/functions.html#open" target="_blank">mode</a>" to open the file with.
|
||||
* It's automatically set for you by using the classes above.
|
||||
* Read more about it below.
|
||||
* `encoding`: to force a specific encoding, e.g. `"utf-8"`.
|
||||
* `lazy`: delay <abbr title="input and output, reading and writing files">I/O</abbr> operations. Automatic by default.
|
||||
* By default, when writing files, Click will generate a file-like object that is not yet the actual file. Once you start writing, it will go, open the file and start writing to it, but not before. This is mainly useful to avoid creating the file until you start writing to it. It's normally safe to leave this automatic. But you can overwrite it setting `lazy=False`. By default, it's `lazy=True` for writing and `lazy=False` for reading.
|
||||
* `atomic`: if true, all writes will actually go to a temporal file and then moved to the final destination after completing. This is useful with files modified frequently by several users/programs.
|
||||
|
||||
## Advanced `mode`
|
||||
|
||||
By default, **Typer** will configure the <a href="https://docs.python.org/3/library/functions.html#open" target="_blank">`mode`</a> for you:
|
||||
|
||||
* `typer.FileText`: `mode="r"`, to read text.
|
||||
* `typer.FileTextWrite`: `mode="w"`, to write text.
|
||||
* `typer.FileBinaryRead`: `mode="rb"`, to read binary data.
|
||||
* `typer.FileBinaryWrite`: `mode="wb"`, to write binary data.
|
||||
|
||||
### Note about `FileTextWrite`
|
||||
|
||||
`typer.FileTextWrite` is actually just a convenience class. It's the same as using `typer.FileText` with `mode="w"`.
|
||||
|
||||
But it's probably shorter and more intuitive as you can get it with autocompletion in your editor by just starting to type `typer.File`... just like the other classes.
|
||||
|
||||
### Customize `mode`
|
||||
|
||||
You can override the `mode` from the defaults above.
|
||||
|
||||
For example, you could use `mode="a"` to write "appending" to the same file:
|
||||
|
||||
```Python hl_lines="4"
|
||||
{!./src/parameter_types/file/tutorial005.py!}
|
||||
```
|
||||
|
||||
!!! tip
|
||||
As you are manually setting `mode="a"`, you can use `typer.FileText` or `typer.FileTextWrite`, both will work.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config line written
|
||||
|
||||
// Run your program a couple more times to see how it appends instead of overwriting
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config line written
|
||||
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config line written
|
||||
|
||||
// Check the contents of the file, it should have each of the 3 lines appended
|
||||
$ cat config.txt
|
||||
|
||||
This is a single line
|
||||
This is a single line
|
||||
This is a single line
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## About the different types
|
||||
|
||||
!!! info
|
||||
These are technical details about why the different types/classes provided by **Typer**.
|
||||
|
||||
But you don't need this information to be able to use them. You can skip it.
|
||||
|
||||
**Typer** provides you these different types (classes) because they inherit directly from the actual Python implementation that will be provided underneath for each case.
|
||||
|
||||
This way your editor will give you the right type checks and completion for each type.
|
||||
|
||||
Even if you use `lazy`. When you use `lazy` Click creates a especial object to delay writes, and serves as a "proxy" to the actual file that will be written. But this especial proxy object doesn't expose the attributes and methods needed for type checks and completion in the editor. If you access those attributes or call the methods, the "proxy" lazy object will call them in the final object and it will all work. But you wouldn't get autocompletion for them.
|
||||
|
||||
But because these **Typer** classes inherit from the actual implementation that will be provided underneath (not the lazy object), you will get all the autocompletion and type checks in the editor.
|
65
docs/tutorial/parameter-types/index.md
Normal file
65
docs/tutorial/parameter-types/index.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
You can use several data types for the *CLI options* and *CLI arguments*, and you can add data validation requirements too.
|
||||
|
||||
## Data conversion
|
||||
|
||||
When you declare a *CLI parameter* with some type **Typer** will convert the data received in the command line to that data type.
|
||||
|
||||
For example:
|
||||
|
||||
```Python hl_lines="5"
|
||||
{!./src/parameter_types/index/tutorial001.py!}
|
||||
```
|
||||
|
||||
In this example, the value received for the *CLI argument* `NAME` will be treated as `str`.
|
||||
|
||||
The value for the *CLI option* `--age` will be converted to an `int` and `--height-meters` will be converted to a `float`.
|
||||
|
||||
And as `female` is a `bool` *CLI option*, **Typer** will convert it to a "flag" `--female` and the counterpart `--no-female`.
|
||||
|
||||
And here's how it looks like:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --help
|
||||
|
||||
// Notice how --age is an INTEGER and --height-meters is a FLOAT
|
||||
Usage: main.py [OPTIONS] NAME
|
||||
|
||||
Options:
|
||||
--age INTEGER
|
||||
--height-meters FLOAT
|
||||
--female / --no-female
|
||||
--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.
|
||||
|
||||
// Call it with CLI parameters
|
||||
$ python main.py Camila --age 15 --height-meters 1.70 --female
|
||||
|
||||
// All the data has the correct Python type
|
||||
NAME is Camila, of type: class 'str'
|
||||
--age is 15, of type: class 'int'
|
||||
--height-meters is 1.7, of type: class 'float'
|
||||
--female is True, of type: class 'bool'
|
||||
|
||||
// And if you pass an incorrect type
|
||||
$ python main.py Camila --age 15.3
|
||||
|
||||
Usage: main.py [OPTIONS] NAME
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--age": 15.3 is not a valid integer
|
||||
|
||||
// Because 15.3 is not an INTEGER (it's a float)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Watch next
|
||||
|
||||
See more about specific types and validations in the next sections...
|
||||
|
||||
|
||||
!!! info "Technical Details"
|
||||
All the types you will see in the next sections are handled underneath by <a href="https://click.palletsprojects.com/en/7.x/parameters/#parameter-types" target="_blank">Click's Parameter Types</a>.
|
100
docs/tutorial/parameter-types/number.md
Normal file
100
docs/tutorial/parameter-types/number.md
Normal file
|
@ -0,0 +1,100 @@
|
|||
You can define numeric validations with `max` and `min` values for `int` and `float` *CLI parameters*:
|
||||
|
||||
```Python hl_lines="5 6 7"
|
||||
{!./src/parameter_types/number/tutorial001.py!}
|
||||
```
|
||||
|
||||
*CLI arguments* and *CLI options* can both use these validations.
|
||||
|
||||
You can specify `min`, `max` or both.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --help
|
||||
|
||||
// Notice the extra RANGE in the help text for --age and --score
|
||||
Usage: main.py [OPTIONS] ID
|
||||
|
||||
Options:
|
||||
--age INTEGER RANGE
|
||||
--score FLOAT RANGE
|
||||
--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.
|
||||
|
||||
// Pass all the CLI parameters
|
||||
$ python main.py 5 --age 20 --score 90
|
||||
|
||||
ID is 5
|
||||
--age is 20
|
||||
--score is 90.0
|
||||
|
||||
// Pass an invalid ID
|
||||
$ python main.py 1002
|
||||
|
||||
Usage: main.py [OPTIONS] ID
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "ID": 1002 is not in the valid range of 0 to 1000.
|
||||
|
||||
// Pass an invalid age
|
||||
$ python main.py 5 --age 15
|
||||
|
||||
Usage: main.py [OPTIONS] ID
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--age": 15 is smaller than the minimum valid value 18.
|
||||
|
||||
// Pass an invalid score
|
||||
$ python main.py 5 --age 20 --score 100.5
|
||||
|
||||
Usage: main.py [OPTIONS] ID
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--score": 100.5 is bigger than the maximum valid value 100.
|
||||
|
||||
// But as we didn't specify a minimum score, this is accepted
|
||||
$ python main.py 5 --age 20 --score -5
|
||||
|
||||
ID is 5
|
||||
--age is 20
|
||||
--score is -5.0
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Clamping numbers
|
||||
|
||||
You might want to, instead of showing an error, use the closest minimum or maximum valid values.
|
||||
|
||||
You can do it with the `clamp` parameter:
|
||||
|
||||
```Python hl_lines="5 6 7"
|
||||
{!./src/parameter_types/number/tutorial002.py!}
|
||||
```
|
||||
|
||||
And then, when you pass data that is out of the valid range, it will be "clamped", the closest valid value will be used:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// ID doesn't have clamp, so it shows an error
|
||||
$ python main.py 1002
|
||||
|
||||
Usage: main.py [OPTIONS] ID
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "ID": 1002 is not in the valid range of 0 to 1000.
|
||||
|
||||
// But --rank and --score use clamp
|
||||
$ python main.py 5 --rank 11 --score -5
|
||||
|
||||
ID is 5
|
||||
--rank is 10
|
||||
--score is 0
|
||||
```
|
||||
|
||||
</div>
|
95
docs/tutorial/parameter-types/path.md
Normal file
95
docs/tutorial/parameter-types/path.md
Normal file
|
@ -0,0 +1,95 @@
|
|||
You can declare a *CLI parameter* to be a standard Python <a href="https://docs.python.org/3/library/pathlib.html#basic-use" target="_blank">`pathlib.Path`</a>.
|
||||
|
||||
This is what you would do for directory paths, file paths, etc:
|
||||
|
||||
```Python hl_lines="1 6"
|
||||
{!./src/parameter_types/path/tutorial001.py!}
|
||||
```
|
||||
|
||||
And again, as you receive a standard Python `Path` object the same as the type annotation, your editor will give you autocompletion for all its attributes and methods.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --config config.txt
|
||||
|
||||
The config doesn't exist
|
||||
|
||||
// Now create a quick config
|
||||
$ echo "some settings" > config.txt
|
||||
|
||||
// And try again
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config file contents: some settings
|
||||
|
||||
// And with a directory
|
||||
$ python main.py --config ./
|
||||
|
||||
Config is a directory, will use all its config files
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
## Path validations
|
||||
|
||||
You can perform several validations for `Path` *CLI parameters*:
|
||||
|
||||
* `exists`: if set to true, the file or directory needs to exist for this value to be valid. If this is not required and a file does indeed not exist, then all further checks are silently skipped.
|
||||
* `file_okay`: controls if a file is a possible value.
|
||||
* `dir_okay`: controls if a directory is a possible value.
|
||||
* `writable`: if true, a writable check is performed.
|
||||
* `readable`: if true, a readable check is performed.
|
||||
* `resolve_path`: if this is true, then the path is fully resolved before the value is passed onwards. This means that it’s absolute and symlinks are resolved. It will not expand a tilde-prefix, as this is supposed to be done by the shell only.
|
||||
|
||||
!!! tip
|
||||
All these parameters come directly from <a href="https://click.palletsprojects.com/en/7.x/parameters/#parameter-types" target="_blank">Click</a>.
|
||||
|
||||
For example:
|
||||
|
||||
```Python hl_lines="9 10 11 12 13 14"
|
||||
{!./src/parameter_types/path/tutorial002.py!}
|
||||
```
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Usage: main.py [OPTIONS]
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--config": File "config.txt" does not exist.
|
||||
|
||||
// Now create a quick config
|
||||
$ echo "some settings" > config.txt
|
||||
|
||||
// And try again
|
||||
$ python main.py --config config.txt
|
||||
|
||||
Config file contents: some settings
|
||||
|
||||
// And with a directory
|
||||
$ python main.py --config ./
|
||||
|
||||
Usage: main.py [OPTIONS]
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "--config": File "./" is a directory.
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
### Advanced `Path` configurations
|
||||
|
||||
!!! warning "Advanced Details"
|
||||
You probably won't need these configurations at first, you may want to skip it.
|
||||
|
||||
They are used for more advanced use cases.
|
||||
|
||||
* `allow_dash`: If this is set to True, a single dash to indicate standard streams is permitted.
|
||||
* `path_type`: optionally a string type that should be used to represent the path. The default is None which means the return value will be either bytes or unicode depending on what makes most sense given the input data Click deals with.
|
48
docs/tutorial/parameter-types/uuid.md
Normal file
48
docs/tutorial/parameter-types/uuid.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
!!! info
|
||||
A UUID is a <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier" target="_blank">"Universally Unique Identifier"</a>.
|
||||
|
||||
It's a standard format for identifiers, like passport numbers, but for anything, not just people in countries.
|
||||
|
||||
They look like this:
|
||||
|
||||
```
|
||||
d48edaa6-871a-4082-a196-4daab372d4a1
|
||||
```
|
||||
|
||||
The way they are generated makes them sufficiently long and random that you could assume that every UUID generated is unique. Even if it was generated by a different application, database, or system.
|
||||
|
||||
So, if your system uses UUIDs to identify your data, you could mix it with the data from some other system that also uses UUIDs with some confidence that their IDs (UUIDs) won't clash with yours.
|
||||
|
||||
This wouldn't be true if you just used `int`s as identifiers, as most databases do.
|
||||
|
||||
|
||||
|
||||
You can declare a *CLI parameter* as a UUID:
|
||||
|
||||
```Python hl_lines="1 6 7 8"
|
||||
{!./src/parameter_types/uuid/tutorial001.py!}
|
||||
```
|
||||
|
||||
Your Python code will receive a standard Python <a href="https://docs.python.org/3.8/library/uuid.html" target="_blank">`UUID`</a> object with all its attributes and methods, and as you are annotating your function parameter with that type, you will have type checks, autocompletion in your editor, etc.
|
||||
|
||||
Check it:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
// Pass a valid UUID v4
|
||||
$ python main.py d48edaa6-871a-4082-a196-4daab372d4a1
|
||||
|
||||
USER_ID is d48edaa6-871a-4082-a196-4daab372d4a1
|
||||
UUID version is: 4
|
||||
|
||||
// An invalid value
|
||||
$ python main.py 7479706572-72756c6573
|
||||
|
||||
Usage: main.py [OPTIONS] USER_ID
|
||||
Try "main.py --help" for help.
|
||||
|
||||
Error: Invalid value for "USER_ID": 7479706572-72756c6573 is not a valid UUID value
|
||||
```
|
||||
|
||||
</div>
|
|
@ -26,6 +26,14 @@ nav:
|
|||
- CLI Options: 'tutorial/options.md'
|
||||
- CLI Arguments: 'tutorial/arguments.md'
|
||||
- Commands: 'tutorial/commands.md'
|
||||
- CLI Parameter Types:
|
||||
- CLI Parameter Types Intro: 'tutorial/parameter-types/index.md'
|
||||
- Number: 'tutorial/parameter-types/number.md'
|
||||
- UUID: 'tutorial/parameter-types/uuid.md'
|
||||
- DateTime: 'tutorial/parameter-types/datetime.md'
|
||||
- Enum - Choices: 'tutorial/parameter-types/enum.md'
|
||||
- Path: 'tutorial/parameter-types/path.md'
|
||||
- File: 'tutorial/parameter-types/file.md'
|
||||
- Alternatives, Inspiration and Comparisons: 'alternatives.md'
|
||||
- Help Typer - Get Help: 'help-typer.md'
|
||||
- Development - Contributing: 'contributing.md'
|
||||
|
|
Loading…
Reference in a new issue