Skip to main content

How to use class based views in FastAPI?

I am trying to use class based views in my FastApi project to reduce redundancy of code. Basically I need CRUD functionality for all of my models and therefor would have to write the same routes over and over again. I created a small example project to display my progress so far, but I ran into some issues.

I know there is this Fastapi-utils but as far as I understand only reduces the number of Dependencies to call and is no longer maintained properly (last commit was March 2020).

I have some arbitrary pydantic Schema/Model. The SQLAlchemy models and DB connection are irrelevant for now.

from typing import Optional
from pydantic import BaseModel

class ObjBase(BaseModel):
    name: Optional[str]

class ObjCreate(ObjBase):
    pass

class ObjUpdate(ObjBase):
    pass

class Obj(ObjBase):
    id: int

A BaseService class is used to implement DB access. To simplify this there is no DB access right now and only get (by id) and list (all) is implemented.

from typing import Any, Generic, List, Optional, Type, TypeVar
from pydantic import BaseModel


SchemaType = TypeVar("SchemaType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)

class BaseService(Generic[SchemaType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, model: Type[SchemaType]):
        self.model = model

    def get(self, id: Any) -> Any:
        return {"id": id}

    def list(self, skip: int = 0, limit: int = 100) -> Any:
        return [
            {"id": 1},
            {"id": 2},
        ]

This BaseService can then be inherited by a ObjService class providing these base functions for the previously defined pydantic Obj Model.

from schemas.obj import Obj, ObjCreate, ObjUpdate
from .base import BaseService

class ObjService(BaseService[Obj, ObjCreate, ObjUpdate]):
    def __init__(self):
        super(ObjService, self).__init__(Obj)

In the init.py file in this directory a function is provided to get an ObjService instance.

from fastapi import Depends
from .obj import ObjService

def get_obj_service() -> ObjService:
    return ObjService()

So far everything is working. I can inject the Service Class into the relevant FastApi routes. But all routes need to be written for each model and CRUD function. Making it tedious when providing the same API endpoints for multiple models/schemas. Therefor my thought was to use something similar to the logic behind the BaseService by providing a BaseRouter which defines these routes and inherit from that class for each model.

The BaseRouter class:

from typing import Generic, Type, TypeVar
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from services.base import BaseService

SchemaType = TypeVar("SchemaType", bound=BaseModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)

class BaseRouter(Generic[SchemaType, CreateSchemaType, UpdateSchemaType]):
    def __init__(self, schema: Type[SchemaType], prefix: str, service: BaseService):
        self.schema = schema
        self.service = service
        
        self.router = APIRouter(
            prefix=prefix
        )

        self.router.add_api_route("/", self.list, methods=['GET'])
        self.router.add_api_route("/{id}", self.get, methods=['GET'])


    def get(self, id):
        return self.service.get(id)

    def list(self):
        return self.service.list()

The ObjRouter class:

from schemas.obj import Obj, ObjCreate, ObjUpdate
from .base import BaseRouter
from services.base import BaseService

class ObjRouter(BaseRouter[Obj, ObjCreate, ObjUpdate]):
    def __init__(self, prefix: str, service: BaseService):
        super(ObjRouter, self).__init__(Obj, prefix, service)

The init.py file in that directory

from fastapi import Depends
from services import get_obj_service
from services.obj import ObjService
from .obj import ObjRouter

def get_obj_router(service: ObjService = Depends(get_obj_service())) -> ObjRouter:
    return ObjRouter("/obj", service).router

In my main.py file this router is added to the FastApi App.

from fastapi import Depends, FastAPI
from routes import get_obj_router

app = FastAPI()

app.include_router(get_obj_router())

When starting the app the routes Get "/obj" and Get "/obj/id" show up in my Swagger Docs for the project. But when testing one of the endpoints I am getting an AttributeError: 'Depends' object has no attribute 'list'

As far as I understand Depends can only be used in FastApi functions or functions that are dependecies themselves. Therefor I tried altering the app.include_router line in my main.py by this

app.include_router(Depends(get_obj_router()))

But it again throws an AttributeError: 'Depends' object has no attribute 'routes'.

Long story short question: What am I doing wrong? Is this even possible in FastApi or do I need to stick to defining the same CRUD Api Endpoints over and over again?

The reason I want to use the Dependenvy Injection capabilities of FastApi is that later I will use the following function call in my Service classes to inject the DB session and automatically close it after the request:

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

As far as I understand this is only possible when the highest call in the dependency hierachy (Route depends on Service depends on get_db) is done by a FastApi Route.

PS: This is my first question on StackOverflow, please be gentle.



source https://stackoverflow.com/questions/75249150/how-to-use-class-based-views-in-fastapi

Comments

Popular posts from this blog

How to show number of registered users in Laravel based on usertype?

i'm trying to display data from the database in the admin dashboard i used this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count(); echo $users; ?> and i have successfully get the correct data from the database but what if i want to display a specific data for example in this user table there is "usertype" that specify if the user is normal user or admin i want to user the same code above but to display a specific usertype i tried this: <?php use Illuminate\Support\Facades\DB; $users = DB::table('users')->count()->WHERE usertype =admin; echo $users; ?> but it didn't work, what am i doing wrong? source https://stackoverflow.com/questions/68199726/how-to-show-number-of-registered-users-in-laravel-based-on-usertype

Why is my reports service not connecting?

I am trying to pull some data from a Postgres database using Node.js and node-postures but I can't figure out why my service isn't connecting. my routes/index.js file: const express = require('express'); const router = express.Router(); const ordersCountController = require('../controllers/ordersCountController'); const ordersController = require('../controllers/ordersController'); const weeklyReportsController = require('../controllers/weeklyReportsController'); router.get('/orders_count', ordersCountController); router.get('/orders', ordersController); router.get('/weekly_reports', weeklyReportsController); module.exports = router; My controllers/weeklyReportsController.js file: const weeklyReportsService = require('../services/weeklyReportsService'); const weeklyReportsController = async (req, res) => { try { const data = await weeklyReportsService; res.json({data}) console...

How to split a rinex file if I need 24 hours data

Trying to divide rinex file using the command gfzrnx but getting this error. While doing that getting this error msg 'gfzrnx' is not recognized as an internal or external command Trying to split rinex file using the command gfzrnx. also install'gfzrnx'. my doubt is I need to run this program in 'gfzrnx' or in 'cmdprompt'. I am expecting a rinex file with 24 hrs or 1 day data.I Have 48 hrs data in RINEX format. Please help me to solve this issue. source https://stackoverflow.com/questions/75385367/how-to-split-a-rinex-file-if-i-need-24-hours-data