| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
A simple solution for organizing your FastAPI endpoints
fastapi-controllers offers a simple solution for organizing your API endpoints into controller classes, embracing the concept of class-based views.
pip install fastapi-controllersfrom fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi_controllers import create_router, get, websocket
class ExampleController:
@get("/example")
async def get_example(self) -> dict[str, str]:
return {"message": "Hello"}
@websocket("/ws")
async def ws_example(self, ws: WebSocket) -> None:
await ws.accept()
try:
while True:
data = await ws.receive_text()
await ws.send_text(f"Received: {data}")
except WebSocketDisconnect:
pass
app = FastAPI()
app.include_router(create_router(ExampleController))A controller is a plain class whose endpoints are methods decorated with route decorators. create_router creates a FastAPI APIRouter and populates it with the controller's routes. Include it in the application in the usual way, with app.include_router. create_router raises TypeError if the class has no routes.
Save the example as example.py and serve it with an ASGI server, for example the FastAPI CLI:
pip install "fastapi[standard]"
fastapi dev example.pyEvery decorator, and create_router, takes exactly the arguments of its FastAPI counterpart. There is no new naming convention to learn:
Route decorators apply to plain methods only. A route decorator on a staticmethod, a classmethod or another wrapper object raises TypeError.
from fastapi_controllers import create_router, router
from fastapi_controllers import delete, get, head, options, patch, post, put, trace, websocketThe router class decorator sets the APIRouter arguments of a controller. create_router takes the same arguments; a value passed to create_router replaces the value set by router.
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
from fastapi_controllers import create_router, get, post, router
security = HTTPBasic()
async def authorized_user(
credentials: Annotated[HTTPBasicCredentials, Depends(security)],
) -> None: ...
class ExampleRequest(BaseModel):
name: str
class ExampleResponse(BaseModel):
message: str
@router(prefix="/example", tags=["example"], dependencies=[Depends(authorized_user)])
class ExampleController:
@get("")
async def get_example(self) -> ExampleResponse:
return ExampleResponse(message="Hello")
@post("")
async def post_example(self, data: ExampleRequest) -> ExampleResponse:
return ExampleResponse(message=f"Hello, {data.name}!")
app = FastAPI()
app.include_router(
create_router(
ExampleController,
deprecated=True,
responses={404: {"description": "Not found"}},
)
)Warning: An argument replaces the earlier value as a whole. Lists are not combined. For example, create_router(ExampleController, dependencies=[...]) removes the authorized_user dependency. To add a dependency, pass it to app.include_router, which keeps the router's own dependencies.
Define instance-scoped attributes in the controller's __init__. They give every endpoint easy access to common dependencies. FastAPI resolves the __init__ dependencies and builds a fresh controller instance for each request.
from typing import Annotated
from fastapi import Depends, FastAPI
from fastapi_controllers import create_router, get, router
class DbSession:
@property
def status(self) -> str:
return "CONNECTED"
async def get_db_session() -> DbSession:
return DbSession()
@router(prefix="/example")
class ExampleController:
def __init__(self, session: Annotated[DbSession, Depends(get_db_session)]) -> None:
self.session = session
@get("")
async def get_status(self) -> dict[str, str]:
return {"status": self.session.status}
app = FastAPI()
app.include_router(create_router(ExampleController))FastAPI serves a request from the first registered route that matches it. create_router registers routes in this order:
A subclass inherits the routes of its base controllers. To override a route, redefine the method and decorate it again. The override keeps the original route's position. create_router raises TypeError if a subclass redefines a route method without a route decorator.
A subclass also inherits the router settings of its base controllers. The settings merge argument by argument: a subclass's router value replaces its base's, and a create_router argument replaces both.
from fastapi_controllers import get, router
@router(prefix="/base", tags=["items"])
class BaseController:
@get("/me")
async def me(self) -> str:
return "me"
@get("/{item_id}")
async def item(self, item_id: int) -> int:
return item_id
@router(prefix="/child") # keeps tags=["items"]
class ChildController(BaseController):
@get("/me")
async def me(self) -> str:
return "child"Releases before 1.0.0 may break the API. Each break comes with a migration step in this section.
Warning: The Controller class is gone, and controllers no longer read the prefix, tags and dependencies class attributes. They are now ordinary class attributes, so nothing raises an error when they are set. Router dependencies defined there, such as authentication, stop running, and their routes become public. Move these attributes to @router(...) (step 3) before you deploy the upgrade. create_router raises TypeError only for a controller that still defines __router_params__.
This command finds the code that steps 2, 3 and 5 change:
grep -rnE '\bController\b|\.create_router\(|__router_params__|fastapi_controllers\.(controllers|definitions|helpers|routing)|\.route_(args|kwargs|meta)' --include='*.py' .Steps 4 and 6 need a review of each controller.
fastapi-controllers now needs Python 3.11 or later and FastAPI 0.124.0 or later. Version 0.3.x needed Python 3.8 or later and any FastAPI.
Remove the Controller base class from every controller. Replace every ExampleController.create_router() call with create_router(ExampleController), and import create_router from fastapi_controllers.
Move the prefix, tags and dependencies class attributes, and every __router_params__ entry, into the router class decorator. It takes every APIRouter argument. In 0.3.x, a __router_params__ value overrode the class attribute of the same name. If a controller sets both, keep the __router_params__ value.
In 0.3.x, subclasses shared and changed their base's __router_params__, so a subclass could silently use another controller's prefix. Check the paths of every subclass after you migrate.
Before (0.3.x):
from fastapi import Depends, FastAPI
from fastapi_controllers import Controller, get
async def authorized_user() -> None: ...
class ExampleController(Controller):
prefix = "/example"
__router_params__ = {"dependencies": [Depends(authorized_user)], "deprecated": True}
@get("")
async def get_example(self) -> str:
return "Hello"
app = FastAPI()
app.include_router(ExampleController.create_router())After:
from fastapi import Depends, FastAPI
from fastapi_controllers import create_router, get, router
async def authorized_user() -> None: ...
@router(prefix="/example", dependencies=[Depends(authorized_user)], deprecated=True)
class ExampleController:
@get("")
async def get_example(self) -> str:
return "Hello"
app = FastAPI()
app.include_router(create_router(ExampleController))The first parameter of every decorated method now receives the controller instance, whatever its name. In 0.3.x, only a parameter named self received it, so a method without self got its first parameter from the request. Add self as the first parameter of every decorated method.
Before (0.3.x):
from fastapi import WebSocket
from fastapi_controllers import Controller, websocket
class ExampleController(Controller):
@websocket("/ws")
async def ws_example(websocket: WebSocket) -> None:
await websocket.accept()
await websocket.close()After:
from fastapi import WebSocket
from fastapi_controllers import websocket
class ExampleController:
@websocket("/ws")
async def ws_example(self, websocket: WebSocket) -> None:
await websocket.accept()
await websocket.close()The decorators now return the method unchanged. ExampleController.get_example is a plain function, not a Route object, so .endpoint, .route_args, .route_kwargs and .route_meta no longer exist. To inspect the routes, read create_router(ExampleController).routes.
The modules fastapi_controllers.controllers, fastapi_controllers.definitions, fastapi_controllers.helpers and fastapi_controllers.routing no longer exist. Import everything from fastapi_controllers.
In 0.3.x, routes were registered in alphabetical order of their method names. They now follow the order in Route order and inheritance. Where one path can match another, such as /{item_id} and /me, define the more specific route first.
In 0.3.x, a subclass method that overrode a route without a route decorator silently removed the route. create_router now raises TypeError for it. Decorate the override again.
| Back | FazBrowse Home | New Git URL |