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)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|