Quantcast
Channel: Active questions tagged python - Stack Overflow
Viewing all articles
Browse latest Browse all 17217

FastApi Class based Domain Driven Design

$
0
0

I am coming from a thought process of Java world. I moved to fastapi thinking it would be faster for rapid prototyping but I am getting stuck more and more in this quagmire. I am finding it much more difficult and cumbersome. The functionality which is out of the box implemented in spring-boot is simply not there. Still, I tried layering my fastapi app for a modular and maintainable design below are the sample files, but it doesn't seem to work.

Javacontroller -> service -> repository -> entity, DTO

Fast APIroutes -> service -> crud -> model, schemas

  1. I despise how there is no repository layer that automatically implements all basic CRUD
  2. FAST API documentation is passing the db over the function tree making the method signature much more verbose. This breaks clean code design.
  3. Lack of @Autowired or similar functionality.
  4. Instead of a single DTO I have to create multiple pydantic schemas.
  5. Absence of magic like Spring Data Rest where I don't even require to write controller, service

plant_model.py

from sqlalchemy import Column, Integer, String, Textfrom app.database.postgres import Baseclass Plant(Base):    __tablename__ = "plant"    id = Column(Integer, primary_key=True, index=True)    name = Column(String, index=True)    type = Column(String)    description = Column(Text)

plant_schemas.py

from pydantic import BaseModelclass PlantBase(BaseModel):    name: str    type: strclass PlantCreate(PlantBase):    passclass PlantInDB(PlantBase):    id: intclass PlantPublic(PlantInDB):    pass

plant_crud.py

from typing import Listfrom fastapi import APIRouter, Depends, HTTPExceptionfrom sqlalchemy.orm import Sessionfrom app.database.postgres import get_dbfrom .plant_schemas import PlantCreate, PlantInDBfrom .plant_models import Plantclass PlantCrud:    def __init__(self, db: Session = Depends(get_db)) -> None:        self.db = db    def create_plant(self, plantCreate: PlantCreate):        db_plant = Plant(plantCreate.model_dump())        self.db.add(db_plant)        self.db.commit()            return db_plant    def get_plants(self, skip: int = 0, limit: int = 100):        return self.db.query(Plant).offset(skip).limit(limit).all()

plant_service.py

from fastapi import Dependsfrom app.api.plant.plant_crud import PlantCrudfrom app.api.plant.plant_schemas import PlantCreateclass PlantService:    def __init__(self, plantCrud: PlantCrud = Depends(PlantCrud)) -> None:        self.plantCrud = plantCrud    def create_plant(self, plantCreate: PlantCreate):        return self.plantCrud.create_plant(plantCreate)    def get_plants(self, skip: int = 0, limit: int = 100):        return self.plantCrud.get_plants(skip, limit)

plant_routes.py

from app.api.plant.plant_models import Plantfrom app.api.plant.plant_schemas import PlantCreate, PlantInDBfrom app.api.plant.plant_service import PlantServicefrom fastapi import Depends, APIRouter, HTTPExceptionfrom fastapi import APIRouter, Dependsrouter = APIRouter()class PlantRoutes:    def __init__(self, plantService: PlantService = Depends(PlantService)) -> None:        self.plant_service = plantService    def get_plants(self):        return self.plant_service.get_plants()    def create_plant(self, plantCreate: PlantCreate):        return self.plant_service.create_plant(plantCreate)plantRoutes = PlantRoutes()router.add_api_route("/plants", plantRoutes.get_plants, methods=["GET"], response_model=list[PlantInDB])router.add_api_route("/plants", plantRoutes.create_plant, methods=["POST"], response_model=PlantInDB, status_code=201)

main.py

# app/__init__.pyfrom fastapi import FastAPIimport osimport sysfrom fastapi.middleware.cors import CORSMiddlewarecurrent_script_dir = os.path.dirname(os.path.realpath(__file__))# Add the parent directory (project directory) to sys.pathsys.path.append(os.path.dirname(current_script_dir))my_app = FastAPI()origins = ["*"]my_app.add_middleware(   CORSMiddleware,   allow_origins=origins,   allow_credentials=True,   allow_methods=["*"],   allow_headers=["*"],)from app.api.plant.plant_routes import router as plant_routermy_app.include_router(plant_router, prefix="/plant", tags=["plant"])if __name__ == "__main__":    import uvicorn    from main import my_app    uvicorn.run("main:my_app", host="127.0.0.1", reload=True)

This almost works but doesn't work

INFO:     127.0.0.1:35684 - "POST /plant/plants HTTP/1.1" 500 Internal Server ErrorERROR:    Exception in ASGI applicationTraceback (most recent call last):  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 408, in run_asgi    result = await app(  # type: ignore[func-returns-value]  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 84, in __call__    return await self.app(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__    await super().__call__(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/applications.py", line 116, in __call__    await self.middleware_stack(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__    raise exc  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__    await self.app(scope, receive, _send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 91, in __call__    await self.simple_response(scope, receive, send, request_headers=headers)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/middleware/cors.py", line 146, in simple_response    await self.app(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in __call__    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 55, in wrapped_app    raise exc  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 44, in wrapped_app    await app(scope, receive, sender)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/routing.py", line 746, in __call__    await route.handle(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/routing.py", line 288, in handle    await self.app(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/routing.py", line 75, in app    await wrap_app_handling_exceptions(app, request)(scope, receive, send)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 55, in wrapped_app    raise exc  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/_exception_handler.py", line 44, in wrapped_app    await app(scope, receive, sender)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/routing.py", line 70, in app    response = await func(request)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/fastapi/routing.py", line 299, in app    raise e  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/fastapi/routing.py", line 294, in app    raw_response = await run_endpoint_function(  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/fastapi/routing.py", line 193, in run_endpoint_function    return await run_in_threadpool(dependant.call, **values)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/starlette/concurrency.py", line 35, in run_in_threadpool    return await anyio.to_thread.run_sync(func, *args)  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/anyio/to_thread.py", line 56, in run_sync    return await get_async_backend().run_sync_in_worker_thread(  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 2134, in run_sync_in_worker_thread    return await future  File "/home/garg10may/coding/pipa/backend/venv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 851, in run    result = context.run(func, *args)  File "/home/garg10may/coding/pipa/backend/app/api/plant/plant_routes.py", line 18, in create_plant    return self.plant_service.create_plant(plantCreate)AttributeError: 'Depends' object has no attribute 'create_plant'
  1. How can I make it work
  2. Is it a good design
  3. What should be a scalable fast API design following principles of modularity, scalability, maintainability and clean code.
  4. Why is Fast API considered a cool framework for rapid prototyping :(

Viewing all articles
Browse latest Browse all 17217

Latest Images

Trending Articles



Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>