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.