Skip to main content

No project description provided

Project description

clii

Generate argument parsers from Python 3 function annotations with minimal boilerplate.

#!/usr/bin/env python3.8
from clii import App, Arg
from pathlib import Path
from subprocess import run

cli = App()

@cli.cmd
def add(a: int, b: int = 3):
    print(a + b)

@cli.cmd
def subtract(path: Path):
    run(f'rm -rf {path}')

if __name__ == '__main__':
    cli.run() 

Okay, you and I both know the last thing that anyone needs is another way to generate command line interfaces. The idea of adding an additional dependency to your project just so you can learn yet another only-slightly-more-ergonomic-than-stdlib interface for parsing args is right up there with rewriting all your Makefiles in whatever flavor-of-the-week Javascript-based build system. I get it.

Yes, instead of writing this library I should probably do something actually useful like try to find a life partner or see how much grain alcohol I can drink within the span of an X-Files episode, but each time I'm typing out some overly verbose argparse incantation that I had to look up on docs.python.org for the sixteenth time in a year, one of the few remaining shreds of childlike wonder for computing left in my over-caffeinated heart gets crosslegged and sets itself on fire.

Click is the equivalent of calling in an architect to fix your kitchen sink. It's a lot of code and the interface is wordy and unintuitive. Docopt is neat but it's slow, a novelty, also a ton of code, and I have to read 3 examples each time before I use it. Argparse is an alright builtin, and the noble progenitor of this library, but it's overly verbose and the common task of wiring up subparsers that call functions is a pain.

Don't immolate your childlike wonder. Use function annotations. Use this stupid library.


  • No dependencies. This library has no dependencies and is a single file. You wanna vendor it? Vendor it.

  • Short implementation. Take 10 minutes, skim the implementation, convince yourself I'm not exfiltrating your id_rsa, then vendor this puppy and never think about anything again.

  • Nothing to learn. if you know how to use Python function annotations, you already know 98% of this library.

  • Optimized for the common case. Check out test_bad_git.py. I know what you want to do (create a subpar reproduction of git), and I've made it concise.

Installation

# Requires Python >=3.7
python3 -m pip install --user clii

Substantial usage example

#!/usr/bin/env python3.8
"""
A really lame version of git.
"""

from pathlib import Path
import typing as t

from clii import App, Arg


cli = App(description=__doc__)
cli.add_arg('--verbose', '-v', action='store_true', default=False)


@cli.cmd
def clone(url: str, target: Path, branch: t.Optional[str] = None):
    """Clone the branch so you can melt your computer."""
    branch = f' -b {branch}' if branch else ''

    # We can reference global args since all parsed arguments are attached
    # to `cli.args` after parsing.
    if cli.args.verbose:
        print(f'git clone{branch} {url} {target}')


@cli.cmd
def push(remote: str, branch: str, force: bool = False):
    force_flag = ' -f' if force else ''

    if cli.args.verbose:
        print(f'git push{force_flag} {remote} {branch}')


@cli.cmd
def commit(all: Arg('-a', bool) = False,
           message: Arg('-m', str) = None):
    # Arguments are --all, -a and --message, -m
    print(all)
    print(message)


@cli.cmd
def add(*files, updated: Arg('-u', bool) = False):
    # `files` will be a variadic positional arg, while --updated/-u is a bool
    # flag.
    if cli.args.verbose:
        print(f"adding files: {files}")



if __name__ == '__main__':
    cli.run() 

which then gets you

% ./test_bad_git.py --help
usage: test_bad_git.py [-h] [--verbose] {clone,push,commit,add} ...

A really lame version of git.

positional arguments:
  {clone,push,commit,add}

optional arguments:
  -h, --help            show this help message and exit
  --verbose, -v

% ./test_bad_git.py clone --help
usage: test_bad_git.py clone [-h] [--branch BRANCH] url target

Clone the branch so you can melt your computer.

positional arguments:
  url
  target

optional arguments:
  -h, --help       show this help message and exit
  --branch BRANCH  default: None

Usage notes

Help text from docstrings

clii will pull argument help text from docstrings that are formatted like so:

import clii
cli = clii.App()

@cli.cmd
def foo(bar: str):
    """
    Args:
      bar: some kind of helpful docstring.
    """

cli.run()

Specifically, the docstring is searched for " [parameter name]:" - if that pattern is found, the contents after the colon are used as help text.

store_true and store_false inference

Arguments that are declared type bool and given a default value are inferred as being store_true or store_false depending upon their default value; it is inferred that if the flag is given on the commandline, the reverse of the default is desired.

For example, in

@cli.cmd
def commit(force: bool = False):
    ...

if --force is given, the function will be called with force=True, but otherwise force will stay False.


If you like using this library, consider sending me some magic internet money:

bc1qlj36t63qlgkywg83gwslcu3ehl76h2k2d6hcv2

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

clii-0.2.0.tar.gz (4.6 kB view details)

Uploaded Source

Built Distribution

clii-0.2.0-py3-none-any.whl (3.6 kB view details)

Uploaded Python 3

File details

Details for the file clii-0.2.0.tar.gz.

File metadata

  • Download URL: clii-0.2.0.tar.gz
  • Upload date:
  • Size: 4.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.4.0 requests-toolbelt/0.8.0 tqdm/4.45.0 CPython/3.8.0

File hashes

Hashes for clii-0.2.0.tar.gz
Algorithm Hash digest
SHA256 96e2acd23904a3d79b25bdb4c05a2a70f30f52ddc0f50bd5cc72af8ecccc5599
MD5 f60ed21f106c52b6d7440698be34e904
BLAKE2b-256 a1b1bf5595933c43bf83f6d66b274b9f0f41e67f6b3bf5c2b7fa735105c79db5

See more details on using hashes here.

File details

Details for the file clii-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: clii-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 3.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.1.1 pkginfo/1.5.0.1 requests/2.23.0 setuptools/46.4.0 requests-toolbelt/0.8.0 tqdm/4.45.0 CPython/3.8.0

File hashes

Hashes for clii-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e32ff4812f2505d64ba419f14bfcf1450f0320fa946f8fb2cf075a9917d4b6b0
MD5 5a9a279db51898da48ad614a33b89120
BLAKE2b-256 8e39ad0b25e4eece65ad5dfc7b0ae69e15b9a5c32774800a576a6920be2f31d5

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page