use annotation to create click app.


Keywords
click, cli
License
MIT
Install
pip install click-anno==0.1.4

Documentation

click-anno

GitHub Build Status PyPI

use annotation to create click app.

Compare with click api

Basic Arguments

# click
import click

@click.command()
@click.argument('filename')
def touch(filename):
    click.echo(filename)

# click_anno
import click
import click_anno

@click_anno.command
def touch(filename):
    click.echo(filename)

Variadic Arguments

# click
import click

@click.command()
@click.argument('src', nargs=-1)
@click.argument('dst', nargs=1)
def copy(src, dst):
    for fn in src:
        click.echo('move %s to folder %s' % (fn, dst))

# click_anno
import click
import click_anno

@click_anno.command
def copy(src: tuple, dst):
    for fn in src:
        click.echo('move %s to folder %s' % (fn, dst))

Basic Value Options

# click
import click

@click.command()
@click.option('--n', default=1)
def dots(n):
    click.echo('.' * n)

# click_anno
import click
import click_anno

@click_anno.command
def dots(n=1):
    click.echo('.' * n)

Required Value Options

# click
import click

@click.command()
@click.option('--n', required=True, type=int)
def dots(n):
    click.echo('.' * n)

# click_anno
import click
import click_anno

@click_anno.command
def dots(*, n: int):
    click.echo('.' * n)

Multi Value Options

# click
import click

@click.command()
@click.option('--pos', nargs=2, type=float)
def findme(pos):
    click.echo('%s / %s' % pos)

# click_anno
from typing import Tuple
import click
import click_anno

@click_anno.command
def findme(*, pos: Tuple[float, float]):
    click.echo('%s / %s' % pos)

Tuples as Multi Value Options

# click
import click

@click.command()
@click.option('--item', type=(str, int))
def putitem(item):
    click.echo('name=%s id=%d' % item)

# click_anno
from typing import Tuple
import click
import click_anno

@click_anno.command
def putitem(*, item: (str, int)):
    click.echo('name=%s id=%d' % item)

Boolean Flags

# click
import click

@click.command()
@click.option('--shout', is_flag=True)
def info(shout):
    click.echo(f'{shout!r}')

# click_anno
import click
from click_anno import command
from click_anno.types import flag

@command
def func(shout: flag):
    click.echo(f'{shout!r}')

Inject Context

# click
@command()
@click.pass_context
def sync(ctx): # `ctx` must be the 1st arg
    click.echo(str(type(ctx)))

# click_anno
@command
def sync(a, ctx: click.Context, b): # `ctx` can be any location
    click.echo(str(type(ctx)))

Group

# click
@click.group()
def cli():
    click.echo('Running')

@cli.command()
def sync():
    click.echo('Syncing')

# click_anno
@click_app
class App:
    def __init__(self):
        click.echo('Running')

    def sync(self):
        click.echo('Syncing')

Extensions

Enum

Enum is NOT Choice.

# click
# does not support

# click_anno
import click
from click_anno import command
from enum import Enum, auto

class HashTypes(Enum):
    md5 = auto()
    sha1 = auto()

@command
def digest(hash_type: HashTypes):
    assert isinstance(hash_type, HashTypes)
    click.echo(hash_type)

Alias

# click
# does not support

# click_anno
@click_app
class App:
    def sync(self):
        click.echo('Syncing')

    alias = sync

show default in argument

by default, click.argument did not accept show_default option.

click_anno was modify this.

@command
def func(a=10, *_):
    pass

# with --help
# Usage: func [OPTIONS] [A=10]
# ...

Arguments vs Options

click only has two kinds of parameters:

  • Options
  • Arguments - work similarly to options but are positional.

By default in python, arguments like *args and options like **kwargs.

In example func(a, b=1, *args, d, e=2), a b args are arguments, d e are options.

If you don't want the args *args, rename it to *_. click_anno will ignore all args named _

In example func(a, b=1), *args did not exists. so a is argument, b is option.

Auto Inject Arguments

by default, you can inject click.Context by annotation:

@command
def inject_context(a, ctx: click.Context, b): # `ctx` can be any location
    click.echo(str(type(ctx)))

or impl the Injectable:

from click_anno import Injectable

class Custom(Injectable):
    @classmethod
    def __inject__(cls):
        return cls()

@command
def inject_it(obj: Custom):
    assert isinstance(obj, Custom)

or call inject function:

from click_anno import inject

class Custom: pass

inject(Custom, lambda: Custom())

@command
def inject_it(obj: Custom):
    assert isinstance(obj, Custom)

or if you want to inject from click.Context.ensure_object() or click.Context.find_object(), you can use:

from click_anno import find, ensure

@command
def inject_it(f: find(A), e: ensure(B)):
    ...