Fastapi view mixins
Project description
What is it?
Package that contains mixins with built-in limit offset and cursor pagination. Destined to build an API. Easy to use.
Integrated with
- SqlAlchemy
- Pymongo
- Motor - asyncio
List of available mixins:
- APIListMixin
- APIDetailMixin
- APIUpdateMixin
- APICreateMixin
- APIDestroyMixin
List of available pagination strategies:
- PaginationLimitOffset / AsyncPaginationLimitOffset - default
- PaginationCursor / AsyncPaginationCursor
Built on:
- fastapi==0.79.0
- pytest==7.1.2
- pytest-asyncio==0.19.0
- SQLAlchemy==1.4.39
- aiosqlite==0.17.0 # testing purpose
- pymongo==4.2.0
- motor==3.0.0
- mongomock==4.1.2 # testing purpose
- mongomock-motor==0.0.12 # testing purpose
Base usage:
Motor List view:
# views.py
from fastapi_views.ext.motor.mixins import APIListMixin
class FooListView(APIListMixin):
model = 'foo' # collection name
# urls.py
from fastapi import (APIRouter, Depends)
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/all', response_model=list[SomePydanticModel])
async def get_foo(
db: AsyncIOMotorDatabase = Depends(get_motor_db), # required
):
documents = []
cur = FooListView().get_all(db) # cursor is returned, default limit 100, skip 0
async for document in cur:
document.pop('_id') # if you want to return _id you have to work around it by yourself
documents.append(document)
return documents
You can also override mixin attrs to make it a bit shorter
# urls.py
from fastapi import (APIRouter, Depends)
from fastapi_views import utils
from fastapi_views.views.mixins import BaseAPIListMixin
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/all', response_model=list[SomePydanticModel])
async def get_foo(
db: AsyncIOMotorDatabase = Depends(get_motor_db)
):
cur = FooListView(attrs={'model': 'foo'}).get_all(db)
...
Motor list view using pagination:
# views.py
from fastapi_views.ext.motor.mixins import APIListMixin
class FooListView(APIListMixin):
model = 'foo'
paginate_by = 15
# urls.py
from fastapi import (APIRouter, Depends)
from fastapi_views.pagination.schema import LimitOffsetPage
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/all', response_model=LimitOffsetPage.include(SomePydanticModel))
async def get_foo(request: Request, db: Database = Depends(get_motor_db)):
return await FooListView().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.py
from fastapi_views.ext.motor.mixins import APIListMixin
from fastapi_views.ext.motor.pagination.core import AsyncPaginationCursor
from sqlalchemy import select, and_
from sqlalchemy.orm import joinedload, Load
from sqlalchemy.sql import Select
class FooListView(APIListMixin):
model = 'foo'
paginate_by = 10
pagination_strategy = AsyncPaginationCursor
# Motor example
def get_statement(self):
"""As default returns {}."""
return {'name': 'John', 'age': {'$gt': 20}}
def get_pagination_kwargs(self):
"""
As default returns {
'model': self.model,
'ordering': ['id'],
'cursor_prefixes': ['next__', 'prev__']
}
"""
kw = super().get_pagination_kwargs()
kw['ordering'] = ['-created']
return kw
# SQLAlchemy example
def get_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))
return statement
# urls.py
from fastapi import (APIRouter, Depends)
from fastapi_views.pagination.schema import CursorPage
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/all', response_model=CursorPage.include(SomePydanticModel))
async def get_foo(request: Request, db: Database = Depends(get_motor_db)):
return await FooListView().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:
Default field_name for detail/update/delete view is set to id / _id. To override do as below
# views.py
class FooDetailUpdateDeleteView(RealtedMixin):
...
pk_field = 'field_name'
Some examples of sqlAlchemy usage
List view:
# views.py
from fastapi_views.ext.sql_alchemy.mixins import APIListMixin
class FooListView(APIListMixin):
model = Foo
# urls.py
from fastapi import (APIRouter, Depends)
from fastapi_views import utils
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/all', response_model=list[SomePydanticModel])
async def get_foo(
session: Session = Depends(db_session),
):
return utils.scalars(FooListView().get_all(session=session, limit=50))
Detail view:
# views.py
from fastapi_views.ext.sql_alchemy.mixins import APIDetailMixin
class FooDetailView(APIDetailMixin):
model = Foo
def get_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'])
return statement
def get_detail(self, *args, **kwargs):
return super().get_detail(session=self.kwargs['db'])
# urls.py
from fastapi import (APIRouter, Depends)
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/{pk}')
async def get_foo(pk: int, db = Depends(db_session.get_session)):
return FooDetailView(pk=pk, db=db).get_detail()
Update view:
# views.py
from fastapi_views.ext.sql_alchemy.mixins import APIUpdateMixin
class FooUpdateView(APIUpdateMixin):
model = Foo
# urls.py
from fastapi import (APIRouter, Depends)
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/{pk}')
async def get_foo(pk: int, session: Session = Depends(db_session)):
return FooUpdateView().update_one(field_value=pk, session=session, data={})
Delete view:
# views.py
from fastapi_views.ext.sql_alchemy.mixins import APIDestroyMixin
class FooDeleteView(APIDestroyMixin):
model = Foo
# urls.py
from fastapi import (APIRouter, Depends)
foo_router = APIRouter(
prefix='/foo',
tags=['Foo']
)
@foo_router.get('/{slug}')
async def get_foo(slug: str, session: Session = Depends(db_session)):
return FooDeleteView().delete(session=session, field_value=slug)
Download on https://pypi.org/project/fastapi-view-mixins/
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
fastapi-view-mixins-0.1.1.tar.gz
(61.3 kB
view details)
Built Distribution
File details
Details for the file fastapi-view-mixins-0.1.1.tar.gz
.
File metadata
- Download URL: fastapi-view-mixins-0.1.1.tar.gz
- Upload date:
- Size: 61.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9177992a5d68032ea5f205ec9cc550befc83058d74c958f5b24b7d98db02d299 |
|
MD5 | 019e89c9b809f2e5d7b66d55a239c0e9 |
|
BLAKE2b-256 | 82f3f652b0b56cff4fe8f54c6780e57e2f11b61b87d0bff08cf80fa65604cd8e |
File details
Details for the file fastapi_view_mixins-0.1.1-py3-none-any.whl
.
File metadata
- Download URL: fastapi_view_mixins-0.1.1-py3-none-any.whl
- Upload date:
- Size: 83.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.1 CPython/3.9.5
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 930b7ad09f50bcfd3591ef541f59b32ca8bb04b7ec7992c499fba468f111df29 |
|
MD5 | 4892d67e0384f6507e9225efb6f0adf6 |
|
BLAKE2b-256 | 2b4087d717171ec6eaa6400c197ed99b5bac9b1cc6aeb4482a817387d26a5a2a |