Dependency or Service Classes
Find exceptions raised in class methods or callable class instances
- If your application is using classes for dependency-injection, or other classes for handling CRUD operations or business logic, you can specify these classes for inclusion in the OpenAPI spec.
- If any method owned by an instance of these dependency/service classes is called within a route (either directly or indirectly), every
HTTPException
raised within that method will be detected and added to the OpenAPI
spec.
- Every
HTTPException
that might be raised within an instance method, classmethod
, or staticmethod
of a dependency class or a service class will be detected.
- The
dependencyClasses
and serviceClasses
arguments passed to the custom_openapi
function can either be a tuple of classes or a singleton base class from which all other service/dependency classes are derived.
- Any
HTTPException
raised within a callable Dependency instance will also be detected.
Dependency Classes
- A "dependency class" is defined here as any class that has a method called by the FastAPI dependency injection system (passed to the FastAPI
Depends
class, either in a path operation function or in any callable nested within a path operation).
- A common pattern would be to use a Base dependency class from which all other dependency classes inherit. The base class can then be passed to the
custom_openapi
function. The below example illustrates how to include every HTTPException
raised within any class inheriting from a base AppDeps
dependency class:
from fastapi import Depends, FastAPI, HTTPException, status
class AppDeps:
pass
user = {"name": "Person", "id": 2}
class UserDeps(AppDeps):
def __init__(self, user_id: int | None = None):
self.user_id = user_id
@staticmethod
def require_auth():
if not user:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Unauthorized"
)
return user
def instance_method(self, id):
if not id == user["id"]:
raise HTTPException(status_code=407, detail="Some error")
return user
app = FastAPI()
@app.get("/me")
def get_user(*, user_in, current_user=Depends(UserDeps.get_current_user)):
return current_user
user_dep = UserDeps(3)
@app.delete("/me")
def delete_user(
*,
user_id: int,
some_dep: dict = Depends(user_dep.instance_method),
):
return user_id
app.openapi = custom_openapi(app, dependencyClasses=(AppDeps,))
- In the above (contrived) example, any HTTPException
rasied by a UserDeps
method will be added to the API docs. The dependencyClasses
parameter of the custom_openapi
function accepts a tuple of classes as an argument. These classes will be searched for exception responses. In this case, we are passing only the base class from which UserDeps
inherits, but any number of classes can be specified.
Service Classes
- A common pattern in FastAPI apps is to decouple business logic from endpoint functions by using some orchestration layer or "service". The term "service" is somewhat ambiguous. It could also incorporate some CRUD (Create Read Update Delete) / DAL (Data Access Layer) operations involving an ORM such as SQLAlchemy. For our intents and purposes, in the context of
fastapi-docx
, it doesn't matter how you structure your app. A service
here can refer to any class that has a method called within a route, which might raise an exception response that you want to document.
- Similarly to dependency classes, "service" classes can be passed to the
custom_openapi
function via the serviceClasses
paramater:
from typing import Any
from fastapi import Depends, FastAPI, HTTPException, status
class AppService:
pass
class UserService(AppService):
@staticmethod
def get_user(user_id: int) -> dict[str, Any]:
user = {"name": "Saran", "id": user_id}
if not user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail="Bad request"
)
return user
def create_user(self, user: dict[str, Any]) -> dict[str, Any]:
if not user:
raise HTTPException(status_code=444, detail="Conection closed")
return {"name": "Saran", "id": 2}
def do_something(self, user: dict[str, Any]) -> dict[str, Any]:
if not user:
raise HTTPException(status_code=400, detail="Bad request")
return {"name": "Saran", "id": 2}
@classmethod
def get_authenticated(cls, user_id: int) -> dict[str, Any]:
return {"name": "Saran", "id": user_id}
app = FastAPI()
@app.get("/")
def get_user(user_in):
return UserService.get_user(user_in)
@app.post("/")
def create_user(user_in):
user = UserService().create_user(user_in)
user_serv = UserService()
result = user_serv.do_something(user)
if not result:
raise HTTPException(
status_code=420,
detail="Too many requests"
)
return result
app.openapi = custom_openapi(app, serviceClasses=(AppService,))