Cli Manager
A Python package that enables you to create and manage custom management commands, similar to Django's management system, for any Python application — FastAPI, Flask, Sanic, Starlette, or plain Python. This package uses Python's click to define, register, and execute commands for your application dynamically. Both synchronous and asynchronous commands are supported.
Features
- Dynamic Command Registration: Automatically discover and register commands located in specific directories (recursively, optionally).
- Class-Based Commands: Easily define reusable commands by subclassing
BaseCommand(sync) orAsyncBaseCommand(async). - Decorator API: Register plain functions (sync or
async) directly with@system.command(). - Custom Arguments: Commands can specify their own arguments and options via
Argument.positional(...)/Argument.option(...). - Lifecycle Hooks:
setup()andteardown(exc)run before and after every command, even on exceptions. - Aliases, Help & Hidden Commands: Class-level
aliases,help,short_help,hiddenattributes for ergonomic CLIs. - Plugin Discovery: Register commands advertised by installed packages through
cmd_manager.commandsentry points. - Framework-agnostic: Drop into any Python app — FastAPI, Flask, Sanic, Starlette, or a plain script — and thread your app/context into commands via constructor args.
Installation
Install the package via pip:
pip install cmd-manager
Usage
1. Define a synchronous command
# src/scripts/mycommand.py
from cmd_manager import Argument, BaseCommand
class Command(BaseCommand):
"""Print the arguments passed in."""
arguments = (
Argument.positional("arg1"),
Argument.option("--n", type=int, default=1),
)
def run(self, *args, **kwargs):
print(f"Running with args: {args}, kwargs: {kwargs}")
Argument.positional wraps click.argument; Argument.option wraps click.option. Every click keyword (type, prompt, required, is_flag, multiple, ...) is forwarded verbatim.
2. Define an asynchronous command
# src/scripts/fetch.py
import click
from cmd_manager import Argument, AsyncBaseCommand
class Command(AsyncBaseCommand):
"""Fetch a URL asynchronously."""
arguments = (Argument.option("--url", required=True),)
async def setup(self):
self.session = await open_session()
async def run(self, *args, **kwargs):
body = await self.session.get(kwargs["url"])
click.echo(body)
async def teardown(self, exc=None):
await self.session.close()
The system detects async commands automatically and runs them on an event loop — setup, run, and teardown are all awaited.
3. Register and run
# cli_runner.py
from cmd_manager import ManagementCommandSystem
system = ManagementCommandSystem()
system.register(package="src.scripts")
cli = system.create_cli()
if __name__ == "__main__":
cli()
Then:
python cli_runner.py mycommand arg1_value --n 3
python cli_runner.py fetch --url https://example.com
python cli_runner.py list # built-in: prints every registered command
4. Recursive discovery & sub-packages
system.register(package="src.scripts", recursive=True)
src/scripts/users/create.py becomes the command users-create.
5. Prefixing third-party command packages
system.register(prefix="ext-", package="external_package.scripts")
Per-package constructor args (useful when the host app and a plugin expect different DI payloads). app can be any framework instance (FastAPI, Flask, Sanic, Starlette, ...) or any arbitrary object — ManagementCommandSystem is framework-agnostic and just forwards constructor args to each command:
system = ManagementCommandSystem(app=app) # app is any object you want injected
system.register(package="external_package.scripts",
init_kwargs={"app": app, "config": plugin_cfg})
6. Decorator API for one-off commands
from cmd_manager import Argument
@system.command("greet", arguments=[Argument.option("--name", default="world")])
def greet(name):
"""Say hello."""
click.echo(f"hello {name}")
@system.command() # name defaults to "fetch-once" (kebab-cased function name)
async def fetch_once(url):
...
7. Plugin discovery via entry points
A plugin package declares the command in its pyproject.toml:
[project.entry-points."cmd_manager.commands"]
greet = "my_plugin.scripts.greet:Command"
The host app then loads every installed plugin command with one call:
system.register_entry_points()
8. Command metadata
Any BaseCommand / AsyncBaseCommand subclass can set class-level attributes:
class Command(BaseCommand):
"""Long help text comes from the docstring by default."""
name = "do-thing" # override the discovered name
short_help = "Do a thing." # one-liner shown in `--help`
aliases = ("dt",) # alternate names (registered as hidden subcommands)
hidden = False # hide from `--help` listings
Example
See the example/ folder; run it with:
python example_runner.py whats_my_name
python example_runner.py list
Authors
Contributing
Contributions are always welcome! Please read CONTRIBUTING.md and adhere to the project's code of conduct.
Feedback and Support
Please open an issue and follow the template, so the community can help you.
Metadata
Release files for cmd-manager 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cmd_manager-0.2.0.tar.gz | 8.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cmd_manager-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.8 kB
Release files / cmd_manager-0.2.0.tar.gz
| Download URL | cmd_manager-0.2.0.tar.gz |
|---|---|
| Size | 8.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8b8c90c30288b5c7b5c6bbcd4d1d9b2c029dbd255dd332de8ed9e93e57b95a27
|
|
BLAKE2b-256 checksum How to use checksums |
da362b0c1c916a67dec97374e69fc428e467784a38e48021837e996fb0aa38eb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency logRelease files / cmd_manager-0.2.0-py3-none-any.whl
| Download URL | cmd_manager-0.2.0-py3-none-any.whl |
|---|---|
| Size | 10.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8bb23ccaedfa500e79361cfaede42ba269408944ccab682c2602de9e9d18baab
|
|
BLAKE2b-256 checksum How to use checksums |
6d5120bfee1cbf19e2c6e30cfc81b314d4fa3098b329e7e1cd1f13fd9d6c161d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 20, 2026.
Transparency log