Skip to main content

Minimal class-based routing extension for FastAPI

Project description

fastapi-cbx

Code is disciplined. Reality is natural selection. One scenario, one route.

A precise complement to the FastAPI ecosystem, unifying layered routing patterns and strict resource lifecycle management into a clean, production‑ready solution.

Originally proposed and contributed to FastAPI as PR #15392.

CI Codecov

Overview

fastapi-cbx introduces three orthogonal, scenario‑driven routing patterns — FR, CBV, and CBR — each designed for a clear, distinct category of business logic. Built with non‑invasive composition, it fully aligns with FastAPI’s native decorator style and enforces strict resource lifecycle placement rules: heavyweight global resources initialized in __init__, lightweight request‑scoped resources managed via Depends.

Features

  • Adds two scenario-driven class-based routing patterns to native FastAPI
  • Ultra-lightweight: ~110 lines of clean code
  • 100% test coverage
  • No monkey patching, no metaprogramming, no hidden magic
  • Fully backward compatible, no breaking changes
  • Zero runtime overhead
  • Native typing & dependency injection support
  • Predictable, transparent behavior
  • Minimal API surface, easy to maintain

Installation

pip install fastapi-cbx

Quick Start (30 Seconds)

fastapi-cbx provides CBR (Class-Based Route), an original, intuitive, high-performance class-based routing utility for FastAPI.

Concept-focused, zero business logic

Install

pip install fastapi-cbx fastapi uvicorn

Minimal Example (main.py)

import logging
from typing import Dict
from fastapi import FastAPI, APIRouter, Response, status
from cbx import cbr
from pydantic import BaseModel


@cbr(router=APIRouter(prefix="/cbr"))
class CBR:

    logger = logging.getLogger(__qualname__)

    class CBRModel(BaseModel):
        key: str = "CBR"
        value: str = "Class-based route for complex business logic with multiple endpoints and method-level dependencies"

    def __init__(self, **kwargs: Dict[str, str]):
        self.heavies = {
            "name": "fastapi-cbx",
            "description": "Minimal class-based routing extension for FastAPI"
        }
        self.heavies.update(kwargs)

    @cbr.get("/welcome", summary="Welcome to fastapi-cbx")
    @staticmethod
    async def welcome(response: Response) -> str:  # Also supports sync methods
        response.status_code = status.HTTP_200_OK
        response.set_cookie("token", "fastapi-cbx")
        return "Welcome to fastapi-cbx"

    @cbr.get("/heavies", summary="Get heavies by key")
    async def get_heavies(self, key: str) -> CBRModel:  # Also supports sync & @classmethod
        self.logger.info(f"GET {key}")
        return self.CBRModel(key=key, value=self.heavies.get(key, "One scenario, one route"))


app = FastAPI()
app.include_router(CBR(version="1.0.0").router)

Run

uvicorn main:app --host 0.0.0.0 --port 8000

API Document

http://localhost:8000/docs

Hierarchical Routing Patterns

FR(Function Route): Stateless functional route for simple standalone endpoints.

@app.get("/cbx")
async def func():
    return {"message": "Hello CBX"}

CBV (Class-Based View):Class-based view for CRUD operations with singleton global dependency injection.

@cbv(router=APIRouter(prefix='/cbv'))
class CBV:

    logger = logging.getLogger(__qualname__)

    class CBVModel(BaseModel):
        key: str = "cbv"
        value: str = "Class-based view for CRUD operations with singleton global dependency injection"

    def __init__(self, **kwargs: Dict[str, str]):
        self.heavies = {
            "name": "fastapi-cbx",
            "description": "Minimal class-based routing extension for FastAPI",
            "requires-python": ">=3.8",
        }
        self.heavies.update(kwargs)

    @staticmethod
    async def head(response: Response) -> None:
        await asyncio.sleep(1)
        response.status_code = status.HTTP_200_OK
        response.set_cookie("token", "fastapi-cbx")

    def get(self, key: str) -> CBVModel:
        self.logger.info(f"GET {key}")
        time.sleep(1)
        return self.CBVModel(key=key, value=self.heavies.get(key, "One scenario, one route"))

    @staticmethod
    def session(token: str = Cookie(default="", alias="token")) -> str:
        if token != "fastapi-cbx":
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
        return token

    def post(self, body: CBVModel, token: str = Depends(session)) -> None:
        self.logger.info(f"POST {body.key} {body.value} {token}")
        time.sleep(1)
        self.heavies[body.key] = body.value

    @classmethod
    def put(cls, body: CBVModel, token: str = Depends(session)) -> None:
        cls.logger.info(f"PUT {body.key} {body.value} {token}")
        time.sleep(1)
        cls.CBVModel.model_fields["key"].default = body.key
        cls.CBVModel.model_fields["value"].default = body.value

CBR (Class-Based Route): Class-based route for complex business logic with multiple endpoints and method-level dependencies.

@cbr(router=APIRouter(prefix="/cbr"))
class CBR:

    logger = logging.getLogger(__qualname__)

    class CBRModel(BaseModel):
        key: str = "CBR"
        value: str = "Class-based route for complex business logic with multiple endpoints and method-level dependencies"

    def __init__(self, **kwargs: Dict[str, str]):
        self.heavies = {
            "name": "fastapi-cbx",
            "description": "Minimal class-based routing extension for FastAPI"
        }
        self.heavies.update(kwargs)

    @cbr.get("/welcome", summary="Welcome to fastapi-cbx")
    @staticmethod
    async def welcome(response: Response) -> str:
        response.status_code = status.HTTP_200_OK
        response.set_cookie("token", "fastapi-cbx")
        return "Welcome to fastapi-cbx"

    @cbr.get("/heavies", summary="Get heavies by key")
    async def get_heavies(self, key: str) -> CBRModel:
        self.logger.info(f"GET {key}")
        return self.CBRModel(key=key, value=self.heavies.get(key, "One scenario, one route"))

    @staticmethod
    def session(token: str = Cookie(default="", alias="token")) -> str:
        if token != "fastapi-cbx":
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
        return token

    @cbr.post("/heavies", summary="Set heavies")
    async def set_heavies(self, body: CBRModel, token: str = Depends(session)) -> None:
        self.logger.info(f"POST {body.key} {body.value} {token}")
        await asyncio.sleep(1)
        self.heavies[body.key] = body.value

    @cbr.get("/default", summary="Get default heavies")
    @classmethod
    async def get_default(cls) -> CBRModel:
        cls.logger.info(f"GET default")
        return cls.CBRModel()

    @cbr.get("/sync", summary="Sync Support")
    @classmethod
    def sync(cls) -> CBRModel:
        time.sleep(1)
        return cls.CBRModel(key="sync", value="ok")

Resource Lifecycle Placement Rules

To build clean, performant, and maintainable Python web services (especially for ML / LLM / inference services), follow this strict but simple rule:


Heavyweight Global Resources

Use case:

  • Database connections & ORM engines
  • Shared clients (Redis, object storage, third-party SDKs)
  • LLM / transformer models
  • Inference engines (vLLM, TensorRT-LLM)
  • Any expensive-to-initialize component

Where to initialize:
Class __init__ method

Why:

  • Initialized exactly once at application startup
  • Reused across all requests
  • No redundant memory or CPU overhead
  • Avoids global variables
  • Natural domain encapsulation

Example:

@cbr(router=APIRouter(prefix="/cbr"))
class CBR:

    def __init__(self, **kwargs: Dict[str, str]):
        self.heavies = {
            "name": "fastapi-cbx",
            "description": "Minimal class-based routing extension for FastAPI"
        }
        self.heavies.update(kwargs)

Lightweight Request-Scoped Resources

Use case:

  • Current logged-in user
  • Request session
  • Auth / token validation
  • Request ID / tracing context
  • Per-request temporary state

Where to initialize:
FastAPI Depends

Why:

  • Created once per request
  • Automatically isolated between requests
  • Clean dependency injection
  • Low mental overhead

Example:

@cbr(router=APIRouter(prefix="/cbr"))
class CBR:
    ...

    @cbr.post("/heavies", summary="Set heavies")
    async def set_heavies(self, body: CBRModel, token: str = Depends(session)) -> None:
        self.logger.info(f"POST {body.key} {body.value} {token}")
        await asyncio.sleep(1)
        self.heavies[body.key] = body.value

Notes & Warnings

  1. Focus on Routing Only
    This project focuses solely on routing extension for FastAPI. It does not handle business logic, data validation (beyond FastAPI's native capabilities), or other non-routing related functionalities.

  2. Resource Contention & Concurrency Safety
    The handling of contention over shared resources (e.g., database connections, SDK clients and other global shared instances) shall be undertaken by developers. fastapi-cbx does not embed built-in safeguards or isolation controls for concurrent access, whether in synchronous or asynchronous execution scenarios.

  3. Avoid __annotations__['cbx_router'] Conflict
    The library uses the __annotations__['cbx_router'] attribute internally to bind routers. Do not manually modify or use this attribute in your code to avoid conflicts and unexpected behavior.

  4. No Inheritance Support
    As a lightweight encapsulation for REST APIs, fastapi-cbx does not consider or support class inheritance scenarios for CBV/CBR. Each route class should be independent and self-contained.

  5. Class Initialization Requirement
    Classes decorated with @cbv(router=APIRouter(prefix='/cbv')) or @cbr(router=APIRouter(prefix="/cbr")) must be initialized before including the router.

  6. Support & Feedback
    Feel free to open issues or submit PRs on GitHub. If you find this library useful, please give it a star to support the project.

Project details


Download files

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

Source Distribution

fastapi_cbx-0.9.9.tar.gz (6.3 kB view details)

Uploaded Source

Built Distribution

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

fastapi_cbx-0.9.9-py3-none-any.whl (6.3 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_cbx-0.9.9.tar.gz.

File metadata

  • Download URL: fastapi_cbx-0.9.9.tar.gz
  • Upload date:
  • Size: 6.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_cbx-0.9.9.tar.gz
Algorithm Hash digest
SHA256 58cf34c39a1e3ecfe2d78daef56ae79361c61c6d2d260b12c4a00d0fd1a4e9a1
MD5 4f8f98fbd1a93e0ceb2df898eb3cc51b
BLAKE2b-256 0bf0517e5a013c3f3d997d76a9d53e3795c329daa827defe12df99a20e931f81

See more details on using hashes here.

File details

Details for the file fastapi_cbx-0.9.9-py3-none-any.whl.

File metadata

  • Download URL: fastapi_cbx-0.9.9-py3-none-any.whl
  • Upload date:
  • Size: 6.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for fastapi_cbx-0.9.9-py3-none-any.whl
Algorithm Hash digest
SHA256 9fa435a32e50ac7c400c1db164897d0eb0e073f3cec812cc03017dfb9a4a4f87
MD5 9456694d8228c8c4dba1a79e5bc54a13
BLAKE2b-256 6bacaa81ca4eb0faa2ee9dace49b91320dee41aac4aec1b2735bf44b9b3ea065

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