Skip to content

oir/startle

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Give your code a start. ⚡👀

tests codecov Supported Python Versions PyPI Version Docs

Warning

Startle is alpha and should be considered unstable as its interface is fluid 😅, consider pinning to a version.

Startle lets you transform a python function (or functions) into a command line entry point, e.g:

wc.py:

from pathlib import Path
from typing import Literal

from startle import start


def word_count(
    fname: Path, /, kind: Literal["word", "char"] = "word", *, verbose: bool = False
) -> None:
    """
    Count the number of words or characters in a file.

    Args:
        fname: The file to count.
        kind: Whether to count words or characters.
        verbose: Whether to print additional info.
    """

    text = open(fname).read()
    count = len(text.split()) if kind == "word" else len(text)

    print(f"{count} {kind}s in {fname}" if verbose else count)


start(word_count)

When you invoke start(), it will construct an argparser (based on type hints and docstring), parse the arguments, and invoke word_count.

You can also invoke start() with a list of functions instead of a single function. In this case, functions are made available as commands with their own arguments and options in your CLI. See here.


Startle also allows you to transform a class (possibly a dataclass) into a command line parser:

import random
from dataclasses import dataclass
from typing import Literal

from startle import parse


@dataclass
class Config:
    """
    Configuration for the dice program.

    Attributes:
        sides: The number of sides on the dice.
        count: The number of dice to throw.
        kind: Whether to throw a single die or a pair of dice.
    """

    sides: int = 6
    count: int = 1
    kind: Literal["single", "pair"] = "single"


def throw_dice(cfg: Config) -> None:
    """
    Throw the dice according to the configuration.
    """
    if cfg.kind == "single":
        for _ in range(cfg.count):
            print(random.randint(1, cfg.sides))
    else:
        for _ in range(cfg.count):
            print(random.randint(1, cfg.sides), random.randint(1, cfg.sides))


if __name__ == "__main__":
    cfg = parse(Config, brief="A program to throw dice.")
    throw_dice(cfg)

Then dice.py can be executed like:



Startle is inspired by Typer, Fire, and HFArgumentParser, but aims to be non-intrusive, to have stronger type support, and to have saner defaults. Thus, some decisions are done differently:

  • Use of positional-only or keyword-only argument separators (/, *, see PEP 570, 3102) are naturally translated into positional arguments or options. See above example (wc.py).
  • Like Typer and unlike Fire, type hints strictly determine how the individual arguments are parsed and typed.
  • Short forms (e.g. -k, -v above) are automatically provided based on the initial of the argument.
  • Variable length arguments are more intuitively handled. You can use --things a b c (in addition to --things=a --things=b --things=c). See example.
  • Like Typer and unlike Fire, help is simply printed and not displayed in pager mode by default, so you can keep referring to it as you type your command.
  • Like Fire and unlike Typer, docstrings determine the description of each argument in the help text, instead of having to individually add extra type annotations. This allows for a very non-intrusive design, you can adopt (or un-adopt) Startle with no changes to your functions.
  • *args but also **kwargs are supported, to parse unknown arguments as well as unknown options (--unk-key unk-val). See example.

See all examples, or the documentation for more.