Package: fastapi-view-mixins
What is it?Package that contains mixins with built-in limit offset and cursor pagination. Destined to build an
API. Easy to use.Integrated withSqlAlchemyPymongoMotor - asyncioList of available mixins:APIListMixinAPIDetailMixinAPIUpdateMixinAPICreateMixinAPIDestroyMixinList of available pagination strategies:PaginationLimitOffset / AsyncPaginationLimitOffset - defaultPaginationCursor / AsyncPaginationCursorBuilt on:fastapi==0.79.0pytest==7.1.2pytest-asyncio==0.19.0SQLAlchemy==1.4.39aiosqlite==0.17.0 # testing purposepymongo==4.2.0motor==3.0.0mongomock==4.1.2 # testing purposemongomock-motor==0.0.12 # testing purposeBase usage:Motor List view:# views.pyfromfastapi_views.ext.motor.mixinsimportAPIListMixinclassFooListView(APIListMixin):model='foo'# collection name# urls.pyfromfastapiimport(APIRouter,Depends)foo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/all',response_model=list[SomePydanticModel])asyncdefget_foo(db:AsyncIOMotorDatabase=Depends(get_motor_db),# required):documents=[]cur=FooListView().get_all(db)# cursor is returned, default limit 100, skip 0asyncfordocumentincur:document.pop('_id')# if you want to return _id you have to work around it by yourselfdocuments.append(document)returndocumentsYou can also override mixin attrs to make it a bit shorter# urls.pyfromfastapiimport(APIRouter,Depends)fromfastapi_viewsimportutilsfromfastapi_views.views.mixinsimportBaseAPIListMixinfoo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/all',response_model=list[SomePydanticModel])asyncdefget_foo(db:AsyncIOMotorDatabase=Depends(get_motor_db)):cur=FooListView(attrs={'model':'foo'}).get_all(db)...Motor list view using pagination:# views.pyfromfastapi_views.ext.motor.mixinsimportAPIListMixinclassFooListView(APIListMixin):model='foo'paginate_by=15# urls.pyfromfastapiimport(APIRouter,Depends)fromfastapi_views.pagination.schemaimportLimitOffsetPagefoo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/all',response_model=LimitOffsetPage.include(SomePydanticModel))asyncdefget_foo(request:Request,db:Database=Depends(get_motor_db)):returnawaitFooListView().get_all_with_pagination(db,request)result_schema_example={"count":0,"total":0,"total_pages":0,"page_limit":0,"next_page":"string","previous_page":"string","last_page":"string","results":[{"name":"string","age":0}]}List view cursor pagination example:# views.pyfromfastapi_views.ext.motor.mixinsimportAPIListMixinfromfastapi_views.ext.motor.pagination.coreimportAsyncPaginationCursorfromsqlalchemyimportselect,and_fromsqlalchemy.ormimportjoinedload,Loadfromsqlalchemy.sqlimportSelectclassFooListView(APIListMixin):model='foo'paginate_by=10pagination_strategy=AsyncPaginationCursor# Motor exampledefget_statement(self):"""As default returns {}."""return{'name':'John','age':{'$gt':20}}defget_pagination_kwargs(self):"""As default returns {'model': self.model,'ordering': ['id'],'cursor_prefixes': ['next__', 'prev__']}"""kw=super().get_pagination_kwargs()kw['ordering']=['-created']returnkw# SQLAlchemy exampledefget_statement(self)->Select:"""As default returns select(self.model)."""statement=select(self.model).options(Load(self.model).load_only(self.model.title,self.model.published),).options(joinedload(self.model.fk_related,innerjoin=True).load_only(Fk_related_model.username,Fk_related_model.email)).where(and_(self.model.id>0,self.model.id<100))returnstatement# urls.pyfromfastapiimport(APIRouter,Depends)fromfastapi_views.pagination.schemaimportCursorPagefoo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/all',response_model=CursorPage.include(SomePydanticModel))asyncdefget_foo(request:Request,db:Database=Depends(get_motor_db)):returnawaitFooListView().get_all_with_pagination(db,request)result_schema_example={"count":0,"page_limit":0,"first_page":"string","next_page":"string","previous_page":"string","results":[{"name":"string","age":0}]}Detail/Update/Delete view:Defaultfield_namefor detail/update/delete view is set toid / _id. To override do as below# views.pyclassFooDetailUpdateDeleteView(RealtedMixin):...pk_field='field_name'Some examples of sqlAlchemy usageList view:# views.pyfromfastapi_views.ext.sql_alchemy.mixinsimportAPIListMixinclassFooListView(APIListMixin):model=Foo# urls.pyfromfastapiimport(APIRouter,Depends)fromfastapi_viewsimportutilsfoo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/all',response_model=list[SomePydanticModel])asyncdefget_foo(session:Session=Depends(db_session),):returnutils.scalars(FooListView().get_all(session=session,limit=50))Detail view:# views.pyfromfastapi_views.ext.sql_alchemy.mixinsimportAPIDetailMixinclassFooDetailView(APIDetailMixin):model=Foodefget_statement(self)->Select:statement=select(Foo).options(Load(Foo).load_only(Foo.title,Foo.published),).options(joinedload(Foo.user,innerjoin=True).load_only(FooFK.username,FooFK.email)).where(getattr(Foo,self.pk_field)==self.kwargs['pk'])returnstatementdefget_detail(self,*args,**kwargs):returnsuper().get_detail(session=self.kwargs['db'])# urls.pyfromfastapiimport(APIRouter,Depends)foo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/{pk}')asyncdefget_foo(pk:int,db=Depends(db_session.get_session)):returnFooDetailView(pk=pk,db=db).get_detail()Update view:# views.pyfromfastapi_views.ext.sql_alchemy.mixinsimportAPIUpdateMixinclassFooUpdateView(APIUpdateMixin):model=Foo# urls.pyfromfastapiimport(APIRouter,Depends)foo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/{pk}')asyncdefget_foo(pk:int,session:Session=Depends(db_session)):returnFooUpdateView().update_one(field_value=pk,session=session,data={})Delete view:# views.pyfromfastapi_views.ext.sql_alchemy.mixinsimportAPIDestroyMixinclassFooDeleteView(APIDestroyMixin):model=Foo# urls.pyfromfastapiimport(APIRouter,Depends)foo_router=APIRouter(prefix='/foo',tags=['Foo'])@foo_router.get('/{slug}')asyncdefget_foo(slug:str,session:Session=Depends(db_session)):returnFooDeleteView().delete(session=session,field_value=slug)Download onhttps://pypi.org/project/fastapi-view-mixins/