Skip to main content

cliapi

cliapi implements the command design pattern and exports command function definitions to a CLI via argparse and an API via FastAPI. Your code defines the command interface directly in Python; the external interfaces are derived from that specification. Both surfaces remain in sync, exposing the same features.

Install

pip install cliapi

Getting started

Define a registry, decorate your command functions, then expose them.

from cliapi import CliApiRegistry, Resource, Payload

def get_db():
    db = Database.connect()
    yield db
    db.close()

registry = CliApiRegistry(
    prog="myapp",
    description="My application.",
    version="0.1.0",
    context_factory=get_db,
)

command = registry.command


@command(help="List items")
def cmd_items_list(db, *, limit: int = 10):
    return db.list_items(limit=limit)

@command(help="Create an item")
def cmd_items_create(db, *, name: Payload[str] = ""):
    return db.create_item(name=name)

@command(help="Read one item")
def cmd_item_read(db, *, item_id: Resource[str] = ""):
    return db.get_item(item_id)


# CLI entry point
if __name__ == "__main__":
    registry.run()

# ASGI entry point
app = registry.app()

Run via CLI:

python -m myapp items list --limit 5
python -m myapp items create --name "Widget"
python -m myapp item read --item-id abc123

Run via HTTP:

uvicorn myapp:app --reload

GET  /items?limit=5
POST /items          {"name": "Widget"}
GET  /item/abc123

API docs are available at /docs and /redoc.

Command naming

Function names encode the command path. The cmd_ prefix is stripped; remaining underscore-separated segments become the dotted path, which drives both the CLI subcommand tree and the HTTP route.

cmd_items_list      ->  items.list    ->  GET  /items
cmd_items_create    ->  items.create  ->  POST /items
cmd_item_read       ->  item.read     ->  GET  /item/{item_id}
cmd_reports_run_create -> reports.run.create -> POST /reports/run

Up to three levels of nesting are supported.

HTTP method is derived from the final path segment:

read, list, show    ->  GET
create, update, set ->  POST
delete              ->  DELETE

Parameter types

Keyword-only parameters on a command function are introspected to produce CLI flags, query parameters, path segments, and request body fields.

Plain annotation -- query parameter on HTTP, flag on CLI.

def cmd_items_list(db, *, limit: int = 10): ...

CLI:  myapp items list --limit 20
HTTP: GET /items?limit=20

Resource[T] -- path segment on HTTP, flag on CLI.

def cmd_item_read(db, *, item_id: Resource[str] = ""): ...

CLI:  myapp item read --item-id abc123
HTTP: GET /item/abc123

Payload[T] -- POST body field on HTTP, flag on CLI.

def cmd_items_create(db, *, name: Payload[str] = ""): ...

CLI:  myapp items create --name "Widget"
HTTP: POST /items   {"name": "Widget"}

Supported plain types: str, int, float, bool. Bool parameters become store_true flags on the CLI.

Context factory

Every command receives a context object as its first positional argument. The context factory is a zero-argument callable registered on the registry that produces it. It is called once per CLI invocation and once per HTTP request.

A plain function:

def get_db():
    return Database.connect()

registry = CliApiRegistry(..., context_factory=get_db)

A generator -- preferred when setup and teardown are needed:

def get_db():
    db = Database.connect()
    try:
        yield db
    finally:
        db.close()

The generator form maps directly to a FastAPI Depends generator on the HTTP path, and is wrapped with contextlib.contextmanager on the CLI path. Use it for logging, connection lifecycle, transaction management, or any per-request concern.

Global CLI flags and startup

To add flags that appear before the subcommand, supply a setup_parser callback. It receives the ArgumentParser and may mutate it freely.

def setup_parser(parser):
    parser.add_argument("--verbose", "-v", action="store_true")
    parser.add_argument("--config", metavar="PATH")

To act on those flags before the command runs, supply an on_startup callback. It receives the parsed argparse.Namespace.

def on_startup(args):
    if args.verbose:
        logging.basicConfig(level=logging.DEBUG)
    if args.config:
        load_config(args.config)

registry = CliApiRegistry(
    ...
    setup_parser=setup_parser,
    on_startup=on_startup,
)

There is no equivalent of setup_parser or on_startup on the HTTP path. Use FastAPI lifespan events or middleware for application-level concerns on that side.

CLI output

Command functions return a value. On the CLI path that value is passed to the output callback. The default renders JSON to stdout.

To customise:

def render(result, args):
    print(json.dumps(result, indent=2, default=str))

registry = CliApiRegistry(..., output=render)

The callback receives the return value and the parsed Namespace. A common pattern is a --json flag for machine-readable output alongside a human-readable default:

def setup_parser(parser):
    parser.add_argument(
        "--json",
        dest="as_json",
        action="store_true",
        default=False,
        help="Output as JSON.",
    )

def render(result, args):
    if getattr(args, "as_json", False):
        print(json.dumps(result, indent=2, default=str))
    else:
        pprint(result)

registry = CliApiRegistry(
    ...
    setup_parser=setup_parser,
    output=render,
)

The output callback is not invoked on the HTTP path. The FastAPI app returns command results directly as JSON responses.

interactive_only commands

Commands decorated with interactive_only=True appear in the CLI but are excluded from the HTTP API. Use this for commands that require a terminal.

@command(help="Open a shell", interactive_only=True)
def cmd_shell(db):
    ...

CliApiRegistry reference

CliApiRegistry(
    prog: str,
    description: str = "",
    version: str = "0.1.0",
    context_factory: Callable = None,
    setup_parser: Callable = None,
    on_startup: Callable = None,
    output: Callable = None,
    cmd_prefix: str = "cmd",
)

prog -- program name, used as the CLI prog and the FastAPI title. description -- used in CLI help text and the API docs. version -- exposed via --version on the CLI and in the API docs. context_factory -- zero-argument callable or generator, called per invocation. setup_parser -- receives the ArgumentParser, used to add global flags. on_startup -- receives the parsed Namespace, runs before CLI dispatch. output -- receives (result, args), renders CLI output. Default is JSON. cmd_prefix -- function name prefix stripped before path encoding. Default cmd.

Methods:

.command(help="", interactive_only=False)
    Decorator. Registers the function as a command.

.run()
    Parse sys.argv and dispatch. CLI entry point.

.parser() -> ArgumentParser
    Return the configured parser. Cached after first call.

.app() -> FastAPI
    Return the configured FastAPI app. Cached after first call.

.commands() -> dict[str, Callable]
    Return the registered command map keyed by path string.

Metadata

Release files for cliapi 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for cliapi 0.0.1
File Interpreter ABI Platform
cliapi-0.0.1-py3-none-any.whl Python 3 none any Details

Release files / cliapi-0.0.1-py3-none-any.whl

Download URL cliapi-0.0.1-py3-none-any.whl
Size 10.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6fad696627da150b5456284ad7f6e7f6e4643ba128fad426e51db43d6c810061
BLAKE2b-256 checksum
How to use checksums
6e8f9a3a7be413809f9fa9eb2bfe697cb8ead28df96bc58fb4c7761c820749ad
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.5

Release history Release notifications | RSS feed

This release

0.0.1 This release

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page