Yapx

The next generation of Python's Argparse.

GitHub tag (with filter) GitHub last commit (branch) GitHub Repo stars License

-->

Yapx: Yeah, Another Argparse Extension

Yapx is a Python library for building CLI tools, built atop Python's ubiquitous Argparse library. Yapx leverages Python type-hints to bring a wealth of features and much simplification to the art of building tools, including:

  • ✅ Add arguments directly from a function or dataclass
  • ✅ Native type-casting and validation of CLI arguments based on type-hints
  • ✅ Accept unknown arguments using *args and **kwargs
  • ✅ Define parameters and subcommands, even nested subcommands
  • ✅ Define feature-flag parameters (like --dev / --test / --prod)
  • ✅ Define counting parameters (like -vvv)
  • ✅ Automatic helpful arguments, including --help, --help-all, --version
  • ✅ Set default values from environment variables

Yapx: Install + Extra Features

Yapx is on PyPi. Install it with:

pip install 'yapx[extras]'

Yapx has no third-party requirements, but more capability is unlocked with:

  • yapx[pydantic]: enables support for additional types and validation
  • yapx[shtab]: enables the ability to export shell-completion scripts
  • yapx[rich]: enables prettier help and error messages
  • yapx[yaml]: enables building parsers from spec in yaml files
  • yapx[tui]: enables CLI-to-TUI support
  • yapx[extras]: enables each of the above

Yapx: Argparse Equivalents

Type-Hint Argparse
x: str = yapx.arg(pos=True) add_argument("x")
x: str add_argument("-x")
x: int add_argument("-x", type=int)
x = "foo" add_argument("-x", default="foo")
x = 69 add_argument("-x", type=int, default=69)
x: List[str] add_argument("-x", action="append")
x: List[str] = yapx.unbounded_arg() add_argument("-x", nargs="+")

Yapx: Example 1/6 ❄️

Yapx ArgumentParser inherits from the native ArgumentParser. Everything you know about Argparse is applicable in Yapx.

hello.py $ python hello.py --help
import yapx

parser = yapx.ArgumentParser()

parser.add_argument("--name")

args = parser.parse_args()

print("Hello", args.name)
Helpful Parameters:
  --help, -h         Show this help message.

Optional Parameters:
  --name NAME

...

Yapx: Example 2/6 💧

Yapx can populate an ArgumentParser with arguments from a dataclass.

hello.py $ python hello.py --help
import yapx
from dataclasses import dataclass

@dataclass
class EmployeeModel:
    name: str
    age: int
    salary: float = -1
    manager: bool = False

parser = yapx.ArgumentParser()
parser.add_arguments(EmployeeModel)
args = parser.parse_args()
EmployeeModel(**args.to_dict())
Helpful Parameters:
  --help, -h   Show this help message.

Required Parameters:
  --name <value>
  --age <#>

Optional Parameters:
  --salary <#>      | Default: -1.0
  --manager, --no-manager

...

Yapx: Example 3/6 🏖️

Yapx can also populate an ArgumentParser with arguments from a function.

hello.py $ python hello.py --help
import yapx

def set_employee(
    *args,
    name: str,
    age: int,
    salary: float = -1,
    manager: bool = False,
):
    ...

parser = yapx.ArgumentParser()
parser.add_arguments(submit_employee)
args = parser.parse_args()
set_employee(**args.to_dict())
Helpful Parameters:
  --help, -h   Show this help message.

Required Parameters:
  --name <value>
  --age <#>

Optional Parameters:
  --salary <#>      | Default: -1.0
  --manager, --no-manager
  <...>             Any extra command-line values.

...

Yapx: Example 4/6 ⛅

Use yapx.run() to automatically...
(1) build the parser, (2) parse args, and (3) run the appropriate function(s).

hello.py $ python hello.py --help
import yapx

def set_employee(
    *args,
    name: str,
    age: int,
    salary: float = -1,
    manager: bool = False,
):
    ...

yapx.run(set_employee)
Helpful Parameters:
  --help, -h   Show this help message.

Required Parameters:
  --name <value>
  --age <#>

Optional Parameters:
  --salary <#>      | Default: -1.0
  --manager, --no-manager
  <...>             Any extra command-line values.

...

Yapx: Example 5/6 ☀️

yapx.run() allows any number of subcommands.

hello.py $ python hello.py --help
import yapx

def root_command(*args, **kwargs):
    ...

def a1():
    ...

def a2():
    ...

def a3():
    ...

yapx.run(root_command, [a1, a2, a3])
Helpful Parameters:
  --help, -h   Show this help message.

Optional Parameters:
  <key=value ...>   Any extra command-line key-value pairs.
  <...>             Any extra command-line values.

Commands:
  <COMMAND>
    a1
    a2
    a3

...

Yapx: Example 6/6 🔥

yapx.run() even allows nesting of subcommands.

hello.py $ python hello.py --tui
import yapx

...

yapx.run(
    root_command,
    {
        a1: {b1: {c1: [x1, y1, z1]}},
        a2: {b2: {c2: [x2, y2, z2]}},
        a3: {b3: {c3: [x3, y3, z3]}},
    },
)

...

Yapx: Command Chaining ⛓️

Referencing the previous example, when the script is invoked with...

hello.py a1 b1 c1 x1

... then the function a1() is called first, then b1(), c1(), and finally x1().

Additionally, if a function returns a generator, such as...

def a1():
    # setup ...
    yield
    # teardown ...
...

... then the function is resumed after the initial call; e.g.:

a1() -> b1() -> c1() -> x1() -> next(c1) -> next(b1) -> next(a1)

End.

You can find more examples in the Yapx project page, within Jupyter notebooks and reference documentation: f2dv.com/r/yapx

Brought to you by...