Skip to content

Introduction

When it comes to arguably, the most important things to know are:

  • @arguably.command makes a function appear on the CLI
  • arguably.run() parses the CLI arguments and calls the decorated functions

Hello, world!

Here's a simple "Hello, world!" script:

import arguably

@arguably.command
def hello(name):
    print(f"Hello, {name}!")

if __name__ == "__main__":
    arguably.run()

user@machine:~$ python3 hello-1.py Python
Hello, Python!
user@machine:~$ python3 hello-1.py -h
usage: hello-1.py [-h] name

positional arguments:
  name        (type: str)

options:
  -h, --help  show this help message and exit

Because name is a required argument for the hello() function, it's also a required argument on the CLI. If the script is run without giving a name, it prints a message stating that the argument is required:

user@machine:~$ python3 hello-1.py
usage: hello-1.py [-h] name
hello-1.py: error: the following arguments are required: name

Note

If desired, async functions are also supported.

import arguably
import asyncio

@arguably.command
async def hello(name):
    await asyncio.sleep(1)
    print(f"Hello, {name}!")

if __name__ == "__main__":
    arguably.run()
user@machine:~$ python3 hello-async-1.py Python
Hello, Python!

Optional arguments

To make an argument optional, give it a default value.

@arguably.command
def hello(name="world"):
    print(f"Hello, {name}!")

user@machine:~$ python3 hello-2.py
Hello, world!
user@machine:~$ python3 hello-2.py Python
Hello, Python!
user@machine:~$ python3 hello-2.py -h
usage: hello-2.py [-h] [name]

positional arguments:
  name        (type: str, default: world)

options:
  -h, --help  show this help message and exit

Adding an [--option]

To make an --option instead of a positional argument, use keyword-only arguments. These are the arguments that appear after the * in the parameter list.

@arguably.command
def hello(*, name="world"):
    print(f"Hello, {name}!")
>>> @arguably.command
... def hello(*, name="world"):
...     print(f"Hello, {name}!")
...
>>> hello()
Hello, world!
>>> hello("Python")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: hello() takes 0 positional arguments but 1 was given
>>> hello(name="Python")
Hello, Python!

user@machine:~$ python3 hello-3.py -h
usage: hello-3.py [-h] [--name NAME]

options:
  -h, --help   show this help message and exit
  --name NAME  (type: str, default: world)
user@machine:~$ python3 hello-3.py --name Python
Hello, Python!

Flexible number of args

To take in a variable number of positional arguments, use *args:

import arguably

@arguably.command
def hello(*from_, name="world"):
    print(f"Hello, {name}!")
    print(f"From: {', '.join(from_)}")

if __name__ == "__main__":
    arguably.run()

user@machine:~$ python3 hello-4.py -h
usage: hello-4.py [-h] [--name NAME] [from ...]

positional arguments:
  from         (type: str)

options:
  -h, --help   show this help message and exit
  --name NAME  (type: str, default: world)
user@machine:~$ python3 hello-4.py Graham John Terry Eric Terry Michael --name Python
Hello, Python!
From: Graham, John, Terry, Eric, Terry, Michael

To require at least one input to *args, use arguably.arg.required().

Adding help messages

To add help messages to parameters, add a docstring. It can be any of the major formats: reStructuredText (Sphinx), Google, Numpydoc, or Epydoc. We'll use Google's style for this example.

@arguably.command
def hello(*from_, name="world"):
    """
    this will say hello to someone

    Args:
        from_: greetings are sent from these people
        name: is who this will greet
    """
    print(f"Hello, {name}!")
    print(f"From: {', '.join(from_)}")
user@machine:~$ python3 hello-5.py -h
usage: hello-5.py [-h] [--name NAME] [from ...]

this will say hello to someone

positional arguments:
  from         greetings are sent from these people (type: str)

options:
  -h, --help   show this help message and exit
  --name NAME  is who this will greet (type: str, default: world)

Option names

By default, any --options will have a long name which is a normalized version of their Python name. Options do not have a short name by default.

Option names can be controlled by prefixing their description with a value in square brackets []:

  • [-t]-t is the short name
  • [--to]--to is the long name
  • [-t/--to]-t is the short name and --to is the long name
  • [-t/]-t is the short name, the long name is removed.
@arguably.command
def hello(*, from_="me", name="world"):
    """
    this will say hello to someone

    Args:
        from_: [-f/] the sender of these greetings
        name: [-t/--to] the receiver of these greetings
    """
    print(f"Hello, {name}!")
    print(f"From: {from_}")
user@machine:~$ python3 etc/scripts/hello-6.py -h
usage: hello-6.py [-h] [-f FROM] [-t TO]

this will say hello to someone

options:
  -h, --help   show this help message and exit
  -f FROM      the sender of these greetings (type: str, default: me)
  -t, --to TO  the receiver of these greetings (type: str, default: world)

Metavars

A metavar is what gets printed in the usage string to represent the user-provided value. More explanation for that here.

By default, the metavar for any argument is the uppercase version of its name. To change the metavar, wrap any word in its description in curly braces {}. Tuples can specify one value or a number of comma-separated values equal to the tuple length.

  • {who}WHO is the metavar
  • {x,y,z}X, Y, and Z are the metavars for a tuple of length 3
@arguably.command
def hello(*, from_="me", name="world"):
    """
    this will say hello to someone

    Args:
        from_: [-f/] the {sender} of these greetings
        name: [-t/--to] the {receiver} of these greetings
    """
    print(f"Hello, {name}!")
    print(f"From: {from_}")
user@machine:~$ python3 etc/scripts/hello-7.py -h
usage: hello-7.py [-h] [-f SENDER] [-t RECEIVER]

this will say hello to someone

options:
  -h, --help         show this help message and exit
  -f SENDER          the sender of these greetings (type: str, default: me)
  -t, --to RECEIVER  the receiver of these greetings (type: str, default: world)

Compare the last line with how it was before:

Before:  -t, --to TO  the receiver of these greetings (type: str, default: world)
After:   -t, --to RECEIVER  the receiver of these greetings (type: str, default: world)

Summary

arguably looks at all decorated functions and maps their arguments from Python to the CLI.

@arguably.command
def some_function(required, not_required=2, *others: int, option: float = 3.14):
    ...
user@machine:~$ ./intro.py -h
usage: intro.py [-h] [-x OPTION] required [not-required] [others ...]
...
This Python ... ... becomes this on the CLI.
positional args, no default required positional CLI args, required required
positional args, with default not_required=2 positional CLI args, optional [not-required]
positional args, variadic *others any extra positional CLI args [others ...]
keyword-only arguments option command-line options [-x OPTION]

Docstrings are used for command and argument help messages. They can also:

  • Change option names:
    • Set the short name with [-n]
    • Change the long name with [--name]
    • Set the short and long names with [-n/--name]
    • Set the short name and remove the long name with [-n/]
  • Change the metavar of an argument to SOMETHING by wrapping a word in curly braces: {something}