A framework-agnostic management command system for Python apps (FastAPI, Flask, Sanic, Starlette, or plain Python) inspired by Django management commands.
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cmd_manager-0.2.0.tar.gz.
File metadata
- Download URL: cmd_manager-0.2.0.tar.gz
- Upload date:
- Size: 8.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8b8c90c30288b5c7b5c6bbcd4d1d9b2c029dbd255dd332de8ed9e93e57b95a27
|
|
| MD5 |
98d8adff9c89cd34af8964c1f20a51c5
|
|
| BLAKE2b-256 |
da362b0c1c916a67dec97374e69fc428e467784a38e48021837e996fb0aa38eb
|
Provenance
The following attestation bundles were made for cmd_manager-0.2.0.tar.gz:
Publisher:
release.yml on mhsiddiqui/cmd-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cmd_manager-0.2.0.tar.gz -
Subject digest:
8b8c90c30288b5c7b5c6bbcd4d1d9b2c029dbd255dd332de8ed9e93e57b95a27 - Sigstore transparency entry: 1577476445
- Sigstore integration time:
-
Permalink:
mhsiddiqui/cmd-manager@ee1f7baeb55b39306aaebff9d8546ab9b47c8e92 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/mhsiddiqui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ee1f7baeb55b39306aaebff9d8546ab9b47c8e92 -
Trigger Event:
release
-
Statement type:
File details
Details for the file cmd_manager-0.2.0-py3-none-any.whl.
File metadata
- Download URL: cmd_manager-0.2.0-py3-none-any.whl
- Upload date:
- Size: 10.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8bb23ccaedfa500e79361cfaede42ba269408944ccab682c2602de9e9d18baab
|
|
| MD5 |
957e9d15ac328e93a4a2190325cd4a82
|
|
| BLAKE2b-256 |
6d5120bfee1cbf19e2c6e30cfc81b314d4fa3098b329e7e1cd1f13fd9d6c161d
|
Provenance
The following attestation bundles were made for cmd_manager-0.2.0-py3-none-any.whl:
Publisher:
release.yml on mhsiddiqui/cmd-manager
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cmd_manager-0.2.0-py3-none-any.whl -
Subject digest:
8bb23ccaedfa500e79361cfaede42ba269408944ccab682c2602de9e9d18baab - Sigstore transparency entry: 1577476554
- Sigstore integration time:
-
Permalink:
mhsiddiqui/cmd-manager@ee1f7baeb55b39306aaebff9d8546ab9b47c8e92 -
Branch / Tag:
refs/tags/0.2.0 - Owner: https://github.com/mhsiddiqui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ee1f7baeb55b39306aaebff9d8546ab9b47c8e92 -
Trigger Event:
release
-
Statement type: