Skip to main content

Unified CLI and HTTP API based on argparse and fastapi.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cliapi-0.0.1-py3-none-any.whl (10.6 kB view details)

Uploaded Python 3

File details

Details for the file cliapi-0.0.1-py3-none-any.whl.

File metadata

  • Download URL: cliapi-0.0.1-py3-none-any.whl
  • Upload date:
  • Size: 10.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for cliapi-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6fad696627da150b5456284ad7f6e7f6e4643ba128fad426e51db43d6c810061
MD5 cdde24e964ba3ae64cb07cb640c4e6e0
BLAKE2b-256 6e8f9a3a7be413809f9fa9eb2bfe697cb8ead28df96bc58fb4c7761c820749ad

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page