Skip to main content

ctrlstack

A Python library for creating unified controller interfaces that can be exposed as both CLI applications and FastAPI web services.

Define your business logic once in a Controller class, and automatically generate both command-line tools and REST APIs from the same codebase.

Features

  • Single Source of Truth: Define methods once, get CLI and API automatically
  • Type Safety: Full Pydantic integration for request/response validation
  • Async Support: Native async/await support for both CLI and web endpoints
  • Flexible Routing: Organize methods into logical groups
  • Remote Controllers: Client-side proxies for seamless remote API calls
  • Authentication: Built-in API key authentication support

Quick Start

Installation

pip install ctrlstack

Basic Example

from ctrlstack import Controller, ctrl_cmd_method, ctrl_query_method
from ctrlstack.server import create_controller_server
from ctrlstack.cli import create_controller_cli

class MyController(Controller):
    @ctrl_query_method
    def get_status(self) -> str:
        return "Service is running"
    
    @ctrl_cmd_method  
    async def send_message(self, message: str) -> str:
        return f"Received: {message}"

# Create FastAPI app
server_app = create_controller_server(MyController())

# Create CLI app  
cli_app = create_controller_cli(MyController())

if __name__ == "__main__":
    cli_app()

This creates:

CLI Usage:

python app.py get-status
# Output: Service is running

python app.py send-message "Hello World"  
# Output: Received: Hello World

API Endpoints:

  • GET /query/get_status → Returns status
  • POST /cmd/send_message → Accepts JSON body with message

Remote Controller

Access your API as if it were a local controller:

from ctrlstack.remote_controller import get_remote_controller

# Create remote controller client
remote_ctrl = get_remote_controller(
    MyController, 
    url="http://localhost:8000",
    api_key="your-api-key"
)

# Use exactly like local controller
status = await remote_ctrl.get_status()
result = await remote_ctrl.send_message(Message(text="Hello from remote!"))

Dynamic Controller Building

from ctrlstack.controller_app import ControllerApp

capp = ControllerApp()

@capp.register_query()
async def health_check() -> dict:
    return {"status": "healthy", "timestamp": "2024-01-01T00:00:00Z"}

@capp.register_cmd()
async def process_data(data: dict) -> str:
    return f"Processed {len(data)} items"

# Get FastAPI server
server_app = capp.get_server_app(api_keys=["secret-key"])

# Get CLI app
cli_app = capp.get_cli_app()

Method Types

  • @ctrl_query_method: GET endpoints, read-only operations
  • @ctrl_cmd_method: POST endpoints, state-changing operations
  • @ctrl_method(type, group): Custom method type and group

Advanced Features

Custom Routing Groups

from ctrlstack import ControllerMethodType, ctrl_method

class AdminController(Controller):
    @ctrl_method(ControllerMethodType.COMMAND, "admin")
    def reset_database(self) -> str:
        return "Database reset"
    
    @ctrl_method(ControllerMethodType.QUERY, "admin")  
    def get_metrics(self) -> dict:
        return {"users": 100, "posts": 500}

# Creates routes: /admin/reset_database, /admin/get_metrics

Authentication

# Server with API key authentication
app = create_controller_server(
    MyController(), 
    api_keys=["secret-key-1", "secret-key-2"]
)

# Clients must include: X-API-Key: secret-key-1

Development

Prerequisites

  • Install uv.
  • Install direnv to automatically load the project virtual environment when entering it.
    • Mac: brew install direnv
    • Linux: curl -sfL https://direnv.net/install.sh | bash

Setting up the environment

Run the following:

# In the root of the repo folder
uv sync --all-extras # Installs the virtual environment at './.venv'
direnv allow # Allows the automatic running of the script './.envrc'
nbl install-hooks # Installs a git hooks that ensures that notebooks are added properly

You are now set up to develop the codebase.

Further instructions:

  • To export notebooks run nbl export.
  • To clean notebooks run nbl clean.
  • To see other available commands run just nbl.
  • To add a new dependency run uv add package-name. See the the uv documentation for more details.
  • You need to git add all 'twinned' notebooks for the commit to be validated by the git-hook. For example, if you add nbs/my-nb.ipynb, you must also add pts/my-nb.pct.py.
  • To render the documentation, run nbl render-docs. To preview it run nbl preview-docs
  • To upgrade all dependencies run uv sync --upgrade --all-extras

Metadata

Release files for ctrlstack 0.1.3

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

Source distribution (sdist)

Source distribution for ctrlstack 0.1.3
File Size Uploaded Provenance Actions
ctrlstack-0.1.3.tar.gz 11.0 kB Without provenance Details for ctrlstack-0.1.3.tar.gz

Built distribution (wheel)

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

Total release size: 24.3 kB

Release files / ctrlstack-0.1.3.tar.gz

Download URL ctrlstack-0.1.3.tar.gz
Size 11.0 kB
Tags Source
SHA-256 checksum
How to use checksums
266dacc90d67c8eb7202ef0c3202198937335dcffdd4062bbd8f9f16963e9928
BLAKE2b-256 checksum
How to use checksums
8de7488c5e705817ae87dda0aa34ae5b0ed704e8bcf04037a91d1ee62dab893d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.11.11

Release files / ctrlstack-0.1.3-py3-none-any.whl

Download URL ctrlstack-0.1.3-py3-none-any.whl
Size 13.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea3fa4750eb69567d63d056cbfbf3b62b8ca78b87cdfd5a1b393fc7270d48545
BLAKE2b-256 checksum
How to use checksums
61615d25d03e212c76e41ea9fd57086f89e708e217cfa1733b50114a3831579b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.11.11

Security for ctrlstack 0.1.3

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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