Example of use
The batcomputer_cli folder and the batcomputer.py file are an example of use of the framework, with a Batman Theme.
To display the example script’s help, run:
[python|python3] tests/batcomputer.py
or:
[python|python3] tests/batcomputer.py -h
or:
[python|python3] tests/batcomputer.py --help
Run the script with Python 3 command is optional.
Another examples of use of the framework:
Docky: Run Docker commands with Python.
Dev: Development scripts to run less and shorter development related commands.
How to use
The idea of use of this framework is to copy the cly
package to
your project and use it. A suggestion would be to have a scripts
folder
and stores your scripts like this:
scripts/
├── cly/
├── script_name_cli/
└── script_name.py
Then, to call it, you would run scripts/script_name.py
.
Do not forget that to run a Python script without the Python3 command, it is needed to add a shebang, at the beginning of the file, and grant run permission to it. An example of Python shebang:
#!/usr/bin/env python3
To grant run permission to a file in your Linux’s system, run:
chmod +x file_name
You can also add the CLI to your project scripts. For example, in your
pyproject.toml
, you can add something like:
[tool.poetry.scripts]
script_name = "scripts.script_name_cli.__main__:CLI"
Then, you can call it with just script_name
, after installing the project.
Understanding the example
To understand what the framework does, let’s check out how would be the example implementation with pure argparse:
import argparse
import functools
import sys
from typing import Any, Callable
from . import __version__
from .commands.identify import identify
from .commands.list_aliases import list_aliases
def decorate_kwargs(func: Callable[..., Any]) -> Callable[..., Any]:
@functools.wraps(func)
def wrap(**kwargs: Any) -> Any:
for kwarg in set(kwargs.keys()) - set(func.__code__.co_varnames):
kwargs.pop(kwarg)
return func(**kwargs)
return wrap
COMMANDS = {
"id": decorate_kwargs(identify),
"ls": decorate_kwargs(list_aliases),
}
parser = argparse.ArgumentParser(
prog=sys.argv[0],
description="Run Batcomputer analysis on selected areas.",
epilog="Wayne Enterprises \N{office building}",
allow_abbrev=False,
# formatter_class=CustomFormatter, Defined in cly package
)
parser._positionals.title = "Arguments"
parser._optionals.title = "Options"
parser._actions[0].help = "Show script's help message."
parser.add_argument(
"-v",
"--version",
action="version",
version=f"Batcomputer version {__version__}",
help="Show script's version.",
)
parser.add_argument(
"-o",
"--oracle",
action="store_true",
help="Use Oracle's help to get more data.",
)
subparser = parser.add_subparsers(
dest="commands",
metavar="command",
title="Commands",
prog=sys.argv[0],
required=True,
)
identify_command = subparser.add_parser(
"id",
help="Identify the person behind each alias.",
)
identify_command.add_argument(
dest="aliases",
metavar="aliases",
nargs="+",
help="One or more alias to be identified, separated by spaces.",
)
subparser.add_parser(
"ls",
help="List all aliases in Batcomputer.",
)
def CLI() -> None:
namespace = parser.parse_args(sys.argv[1:] or ["--help"])
if namespace.commands:
COMMANDS[namespace.commands](**dict(namespace._get_kwargs()))
You can copy this content into tests/batcomputer_cli/__main__.py
and run the
example to check it out.
Now let’s check the example further:
from cly import config
from . import __version__
from .commands.identify import identify
from .commands.list_aliases import list_aliases
CLI_CONFIG = {
"name": "Batcomputer",
"description": "Run Batcomputer analysis on selected areas.",
"epilog": "Wayne Enterprises \N{office building}",
"version": __version__,
}
CLI = config.ConfiguredParser(CLI_CONFIG)
CLI.parser.add_argument(
"-o",
"--oracle",
action="store_true",
help="Use Oracle's help to get more data.",
)
identify_command = CLI.create_command(identify, alias="id")
identify_command.add_argument(dest="aliases", metavar="aliases", nargs="+")
CLI.create_command(list_aliases, alias="ls")
When we call the ConfiguredParser
class, it creates a
argparse.ArgumentParser
, doing everything the parser calls do in the pure
argparse example, except adding the oracle
argument.
When we call the create_command
function, it creates a subparser, if one
does not already exists, and adds the command to it. If you notice, in the
pure argparse example, the parser configuration is lost in the subparser. This
does not happens with the framework, because it passes the same configurations
to the subparser. With CLY?!, you do not need to pass the help for the
commands’ arguments, since it is parsed from the command’s docstring.
Finally, CLY?! implements a __call__
method for parsing the user
inputs. The pure argparse example have 77 lines of code, against 25 lines of code of
when using the framework.