Extended Example
Now we are going to build something a bit more fancy. We will have some
subcommands, think git status/commit/merge...
, and showcase a few more of the
features of Platitudes.
First the full listing
from enum import Enum
from pathlib import Path
from typing import Annotated
from uuid import UUID
import platitudes as pl
app = pl.Platitudes(description="Manage users")
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
@app.command()
def build_profile(
name: Annotated[str, pl.Argument(help="User name")],
age: int,
photo_file: Annotated[
Path, pl.Argument(exists=True)
],
favorite_color: Color = Color.RED,
):
print(f"User's is named {name} and they love {favorite_color}")
print(f"Age: {age}")
print(f"Picture stored at: {photo_file}")
assert isinstance(photo_file, Path)
assert isinstance(favorite_color, Color)
@app.command()
def delete_profile(user: UUID):
print(f"Deleting user with UUID: {user}")
assert isinstance(user, UUID)
if __name__ == "__main__":
app()
First of and differently of the example on the intro we instantiate a Platitudes
object onto which we will register the different subcommands and finally run the CLI
by just calling it. To register new subcommands all that needs to be done is to apply
the command
decorator to them:
# Optionally pass a description
app = pl.Platitudes(description="Manage users")
@app.command()
def build_profile(...): ...
@app.command()
def delete_profile(...): ...
if __name__ == "__main__":
app()
Then we will be able to access the subcommands as:
Note
The app.command
decorator doesn't change the decorated function in any
way meaning that outside of the context of the CLI you can use it just as well
as you did before. This is also why we hide the call to app()
under the if.
Now lets inspect the function signature for the build_profile
command.
@app.command()
def build_profile(
name: Annotated[str, pl.Argument(help="User name")],
age: int,
photo_file: Annotated[Path, pl.Argument(exists=True)],
favorite_color: Color = Color.RED,
): ...
Looking at it we see that only the last argument, favorite_color
has a
default value so we will end up with 3 postional arguments and an optional one
for it.
From the top we have the following arguments:
name
: A string that has beenAnnotated
with apl.Argument
. This is the mechanism used in Platitudes to provide additional information about arguments. In this case we are just providing a string that will be shown when the CLI help is presented.age
: A bareint
without any bells and whistle.photo_file
: An annotatedpathlib.Path
in this case the annotations is asking Platitudes to verify that the passed file exists and otherwise raise an error and terminate execution.
Since these first 3 arguments have no default values they will be read from the CLI based exclusively on their position. Note that they will be received by the underlying function as the type implied by the type hint.
The final argument is an optional one, as implied by the presence of a default
argument. When calling the CLI we can leave it out or pass it a value by
preceding it with it's name and two dashes, --
. That is --favorite-color 1
.
Optional arguments always use kebap-case
Underscore, _
are turned into -
when creating the options so they are always in
kebap case.
would not be 1
but the an actual Enum value. This is equivalent to the
Interestingly this last argument is an Enum of ints. And the received value
choice
parameter in
argparse. We could have
also used an Enum of strings in which case instead of a number we would have
passed in one of the enumerated strings.
We also added another command to the CLI, delete_profile
. This command
demonstrates the use of the
UUID functionality. If the
passed parameter is not a valid UUID parsing will fail and an exception will be
raised. In addittion to this, and like with all other parameters, the passed
value to the function will be an actual UUID from the standard library.
Running it!
To run the CLI just call the program from a terminal. Lets start by showing the help strings for the app as well as for the commands.
❯ python extended_example.py
usage: extended_example.py [-h] {build_profile,delete_profile} ...
Manage users
positional arguments:
{build_profile,delete_profile}
options:
-h, --help show this help message and exit
❯ python extended_example.py build_profile --help
usage: extended_example.py build_profile [-h] [--favorite-color {0,1,2}] name age photo-file
Build a user's profile.
positional arguments:
name User name
age
photo-file
options:
-h, --help show this help message and exit
--favorite-color {0,1,2}
- (default: Color.RED)
❯ python extended_example.py delete_profile --help
usage: extended_example.py delete_profile [-h] user
Delete a user's profile
positional arguments:
user
options:
-h, --help show this help message and exit
See how the docstrings were used in the help itself! Finally, time to create a new profiles:
❯ python extended_example.py build_profile GB 42 pic.jpeg 42
error: Invalid value for 'photo-file': Path pic.jpeg does not exist.
usage: extended_example.py build_profile [-h] [--favorite-color {0,1,2}] name age photo-file
Build a user's profile.
positional arguments:
name User name
age
photo-file
options:
-h, --help show this help message and exit
--favorite-color {0,1,2}
- (default: Color.RED)
Whoops pic file wasn't there!
❯ touch pic.jpeg
❯ python example/extended_example.py build_profile GB 42 pic.jpeg
User's is named GB and they love Color.RED
Age: 42
Picture stored at: pic.jpeg
Victory 🎉! This should be enough to get you started. Next you can have a look at the support types documentation to learn more about the capabilities of Platitudes.