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
- I despise how there is no
repository
layer that automatically implements all basicCRUD
FAST API
documentation is passing thedb
over the function tree making the method signature much more verbose. This breaks clean code design.- Lack of
@Autowired
or similar functionality. - Instead of a single
DTO
I have to create multiplepydantic
schemas. - Absence of magic like
Spring Data Rest
where I don't even require to writecontroller, 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'
- How can I make it work
- Is it a good design
- What should be a scalable
fast API
design following principles of modularity, scalability, maintainability and clean code. - Why is
Fast API
considered a cool framework for rapid prototyping :(