anci is a lightweight python package that automatically converts decorated, type-annotated functions into basic or hierarchical command-line interfaces.
anci can be installed using pip
pip install anciOr, added to your pyproject.toml using uv
uv add ancifrom anci import Arg, base, cmd, main
@base("ops")
def ops():
"""Base command for 'ops'."""
pass
@cmd("ops", "add")
def ops_add(
x: Arg[int], # A flagged argument with no help text
y: Arg[int, "Second."], # A flagged argument with help text
z: Arg[int, "Third."] = 100 # A flagged argument with help text and a default value
):
"""Print the sum of three numbers."""
print(x + y + z)
if __name__ == "__main__":
main("A CLI tool for basic math operations.")This example demonstrates how to create a simple CLI using only decorators and type annotations:
@anci.base("ops")- Creates a base command group calledopsthat initializes a hierarchy.@anci.cmd("ops", "add")- Registers subcommandaddunderopswith flagged arguments.usage- Usage info for each command (e.g.ops --help) are extracted from function docstrings.anci.Arg- Provides type hints and help text for each parameter.anci.main()- Automatically generates the CLI with help text, argument parsing, and command routing.
On the command-line, the anci command-line program can then be called as
python3 main.py ops add --help
... usage: A CLI tool for basic math operations. ops add [-h] [--x X] [--y Y] [--z Z]
...
... Print the sum of three numbers.
...
... options:
... -h, --help show this help message and exit
... --x X
... --y Y Second.
... --z Z Third (default: 100).
python3 main.py ops add --x 2 --y 4
... 116
python3 main.py ops add --x 2 --y 4 --z 4
... 10Or, if integrated as part of your python CLI package, then
package ops add --x 5 --y 3 --z 10
... 118anci currently provides support for various annotated_types, allowing automatic enforcement of range and boundary checks on integers, containers (such as lists, sets, and tuples), and strings.
from anci import Arg, cmd, main
from anci.typing import Annotated, Gt, MaxLen
@cmd("types")
def check_my_types(
x: Arg[list[str], "A list of strings."],
y: Arg[Annotated[int, Gt(10)], "An integer greater than 10"],
z: Arg[Annotated[set[float], MaxLen(2)], "A set of floats with a max of 2 elements"],
):
"""Print the types of each argument."""
print("type length")
print(type(x), len(x))
print(type(y), 1)
print(type(z), len(z))
if __name__ == "__main__":
main("A CLI for checking types.")This example shows how anci uses type annotations to handle complex types and enforce runtime checks:
x: Arg[list[str], ...]-anciconverts a sequence of letters into list of strings.y: Arg[Annotated[int, Gt(10)], ...]:anciverifies input integer is greater than 10.y: Arg[Annotated[set[float], MaxLen(2)], ...]:anciverifies set only has two elements.
When all arguments satisfy the runtime checks, the command runs without errors:
python main.py types --x a b c --y 11 --z 1
... type length
... <class 'list'> 3
... <class 'int'> 1
... <class 'set'> 1However, since anci enforces range/length constraints directly from type annotations, the following produce errors:
python main.py types --x a b c --y 5 --z 1
... types: error: argument --y: y must be > 10, got 5
python main.py types --x a b c --y 11 --z 1 2 3
... types: error: argument --z: Expected at most 2 elements, got 3anci currently supports the following type annotations:
- Base types:
int,str,float,bool,bytes,pathlib.Path - Container types:
list[...],List[...],tuple[...],Tuple[...],set[...],Set[...] annotated_types(typing.Annotated[...]):Gt,Ge,Lt,Le,Interval,MaxLen,MinLen,Len.
All annotated types, and pathlib.Path, can be directly imported/aliased from anci.typing if desired.
If you want to add your new/custom type annotations to anci, see src/anci/handlers for now or, alternatively, open an issue. Documentation on adding your own custom annotations will be added soon.