In the previous accompanying article, "ThePython development framework based on SqlAlchemy+Pydantic+FastApiIn the introduction to the framework of the overall content, the main idea is to abstract the way the interface to achieve code reuse and improve development efficiency. This article in-depth introduction to the FastApi route processing part of the content , through the base class inheritance , we can simplify the router ( or called Web API controller ) of the basic interface function of the writing , direct reuse of the base class can be. For subclasses of some special rewrite operations , or to add custom routing interfaces , but also introduced separately how to deal with .
1, the development of the framework's routing processing base class
As I described earlier, for the FastAPI routing object class in Python, we have also made an abstraction, with the relationship shown below.
The green part of the BaseController is the base class controller (or base class routing class), and the yellow part is the class we actually do business with, which is the subclass routing object.
The base class routing class is defined as shown below and implements such personalized feature handling by accepting some generic parameters.
class BaseController(Generic[ModelType, PrimaryKeyType, PageDtoType, DtoType]): """ Base class controllers that define common interfaces and routes """ def __init__( self, crud: BaseCrud[ModelType, PrimaryKeyType, PageDtoType, DtoType], pagedto_class: Type[PageDtoType], dto_class: Type[DtoType], router: APIRouter, ): = crud = router self.dto_class = dto_class # Used to convert ORM objects to Pydantic objects self.pagedto_class = pagedto_class # Used to convert request parameters to PageDto objects
Since the route instance is also passed in from the outside, we end up using a total route instance object
We end up summarizing all the routing information in a general Api routing class () as shown below.
api_router = APIRouter() api_router.include_router(, prefix="/api/customer", tags=["Customer"]) api_router.include_router(, prefix="/api/product", tags=["Product"]) api_router.include_router(, prefix="/api/dicttype", tags=["DictType"]) api_router.include_router(, prefix="/api/dictdata", tags=["DictData"]) api_router.include_router(, prefix="/api/user", tags=["User"]) api_router.include_router(, prefix="/api/ou", tags=["OU"]) api_router.include_router(, prefix="/api/role", tags=["Role"]) .....
Finally, we can just register and access them in the FastApi portal
# Add api total routing from api. import api_router # API def register_app(): # FastAPI app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, summary=settings.APP_NAME, description=, # docs_url=settings.DOCS_URL, # redoc_url=settings.REDOCS_URL, # openapi_url=settings.OPENAPI_URL, # default_response_class=AjaxResponse, lifespan=register_init, ) app.include_router(api_router)
This way all the routing information is all summarized and can appear in fastApi's Swagger documentation interface.
2. Subclass routing to base class function rewriting and adding new interfaces
The previous subsections described the access handling of the overall routing and some of the API interfaces that it has by default, but we haven't covered the inheritance information between them here.
For a single business object, for example for customer information, it has all the base class API interfaces by default.
How are they related and how is it possible to inherit the relevant interfaces of the base class by default?
## app\api\v1\endpoints\ # Create routes to handle custom interfaces router = APIRouter() # Using a base class controller, you can inherit the interfaces of regular CRUD and automatically generate routing, dependency injection, database connections, etc. --Build method 2 controller = BaseController[Customer, str, CustomerPageDto, CustomerDto]( customer_crud, pagedto_class=CustomerPageDto, dto_class=CustomerDto, router=router, ) controller.init_router() # Initialize routes for interfaces such as regular CRUD
Above is the corresponding Customer table API controller (routing class) definition, this is mainly directly using the base class to construct an object, and use the object's base class function to initialize the routing address (default with all the base class API interface).
If we still need to add some special API interfaces, then we can use it to add them directly after the router object is created, as follows
@( "/by-name", response_model=AjaxResponse[CustomerDto | None], summary="Get records by name", dependencies=[DependsJwtAuth], ) async def get_by_name( name: Annotated[str | None, Query()], request: Request, db: AsyncSession = Depends(get_db), ): item = await customer_crud.get_by_name(db, name) item = CustomerDto.model_validate(item) return AjaxResponse(item)
This way, after running the FastAPI application, you can see the corresponding interface information added to Swagger's documentation, as shown below.
If we want to rewrite some of the processing methods of get defined by the controller base class, such as the Get method for user information, we need to add some additional information about organizations and roles to the record in addition to obtaining the corresponding user information, then we can inherit from the base class and rewrite the Get method to achieve this, the following is to rewrite the processing code of the controller base class.
##app\api\v1\endpoints\ # Inherit from the base class and override certain functions of the base class - build method 1 class UserController(BaseController[User, int, UserPageDto, GetCurrentUserInfoDetail]): def __init__(self): super().__init__( user_crud, pagedto_class=UserPageDto, dto_class=UserDto, router=router, ) # Rewrite base class functions to add custom interface handling async def get(cls, id: int, db: AsyncSession = Depends(get_db)): item = await user_crud.get(db, id) if not item: raise ItemNotFoundException() dto = GetCurrentUserInfoDetail.model_validate(item) # Add a list of character names roles = await role_crud.get_roles_by_user(db, id) = [ for role in roles] # Add a list of character names # Add a list of organization names ous = await ou_crud.get_ous_by_user(db, id) = [ for ou in ous] # Add a list of organization names = ( ("super-administrator") > 0 ) # Add a field for whether or not you are a super administrator = ( or ("janitors") > 0 ) # Add if administrator field return AjaxResponse(dto) controller = UserController() controller.init_router() # Initialize routes for interfaces such as regular CRUD
So, in summary, we see that if we are adding some interfaces, we can just use the base class controller's instance object to handle it (simple), and if we need to override some base class interface processing functions, then we inherit the base class to build something like that and provide the override function, and then instantiate the subclass object to initialize the route processing.
Then just introduce it by unifying it in the general route handling Python class.
##app\api\v1\ from fastapi import APIRouter from api. import (customer, product,dicttype, dictdata, tablenumber, systemparams, systemparamsdir,) from api. import ( login,user,ou,role, loginlog, operationlog, systemtype, blackip, function, menu, roledata, fieldpermit, fielddomain)
************** api_router = APIRouter() api_router.include_router(, prefix="/api/customer", tags=["Customer"]) api_router.include_router(, prefix="/api/product", tags=["Product"]) api_router.include_router(, prefix="/api/dicttype", tags=["DictType"]) api_router.include_router(, prefix="/api/dictdata", tags=["DictData"]) api_router.include_router(, prefix="/api/user", tags=["User"]) api_router.include_router(, prefix="/api/ou", tags=["OU"]) api_router.include_router(, prefix="/api/role", tags=["Role"]) ..............
We then abstract the relevant processing path logic in the Main function.
import uvicorn from pathlib import Pathfrom import settings from core.register_app import register_app app = register_app()if __name__ == "__main__": try: config = ( app=f"{Path(__file__).stem}:app", reload=True, host=settings.SERVER_IP, port=settings.SERVER_PORT, log_config="app/uvicorn_config.json", # Log Configuration ) server = (config) () except Exception as e: raise e
It is sufficient to unify all the related logic in the register_app function to simplify the complexity of handling the entries.
##app\core\register_app.py
def register_app(): # FastAPI app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, summary=settings.APP_NAME, description=, lifespan=register_init, ) # log (computing) register_logger() # Static files register_static_file(app) # middleware register_middleware(app) # routing (in computer networks) register_router(app) # Global Exception Handling register_exception(app) return app