FastAPIwee icon indicating copy to clipboard operation
FastAPIwee copied to clipboard

Feature: filters, ordering, pagination

Open Ignisor opened this issue 3 years ago • 5 comments

Implement support for query filters, ordering and pagination

Ignisor avatar Jul 09 '21 12:07 Ignisor

expect this!

sosofun avatar Dec 13 '21 10:12 sosofun

I implemented this in my own project (by subclassing ListFastAPIView). is it ok if I just post the code here instead of making a PR?

YoilyL avatar Dec 29 '23 02:12 YoilyL

@YoilyL, hi. Sure, would be nice)

HermanHensetskyi avatar Jan 02 '24 11:01 HermanHensetskyi

from fastapi import Depends, Query
from fastapiwee.crud import viewsets
from fastapiwee.pwpd import PwPdModel
from pydantic import BaseModel, Field, create_model



def list_serializer(model):
    pd_model = PwPdModel.make_serializer(model)
    fields: dict[str, Any]  = {name: (Optional[list[field.type_]], Field(Query([field.default]))) if field.default else (Optional[list[field.type_]], Field(Query(None))) for name, field in pd_model.__fields__.items()}
    model2 = create_model("ListPkModel",**fields) 
    return model2

class ListParams(BaseModel):
    filters: BaseModel
    sort: tuple[str, str]
    pagination: tuple[int, int]

class ListFastAPIView(viewsets.ListFastAPIView):
    _params: ListParams
    def __call__(self):
        sort_col, sort_dir = self._params.sort
        order_by = getattr( getattr(self.MODEL, sort_col), sort_dir)()
        query = self._get_query().order_by(order_by).paginate(*self._params.pagination)
        for key, val in self._params.filters.dict(exclude_defaults=True, exclude_none=True).items():
            query = query.where(getattr(self.MODEL, key).in_(val))
        return list(query)
    def _get_api_route_params(self) -> dict:
        model = list_serializer(self.MODEL) 
        fields =  tuple(field.safe_name for field in self.MODEL._meta.sorted_fields)
        def data_dependency(
                filters: model = Depends(),
                sort_col: Literal[fields] = Query(self.MODEL._meta.primary_key.safe_name, title='Sort Column'),
                sort_dir: Literal['asc', 'desc'] = Query('desc', title='Sort Direction'),
                page: int = Query(default=1, ge=1),
                per_page: int = Query(default=10, ge=1),
            ):
            self._params = ListParams(
                filters=filters,
                sort=(sort_col,sort_dir),
                pagination=(page,per_page)
            )

        params = super()._get_api_route_params()
        
        params.update({
            'dependencies': [Depends(data_dependency)],
        })

        return params

YoilyL avatar Jan 02 '24 23:01 YoilyL

This allows filtering by one value as well as multiple values. NOTE: using multiple arguments for the same parameter will work like OR, not AND. example: ?text=test will return a list where text is 'test', ?text=test&text=test2 will return a list where text is 'test' OR 'test2'

here's a script to quickly test (and view the docs):

import uvicorn
import peewee as pw
from fastapi import FastAPI

DB = pw.SqliteDatabase('testdb')


class TestModel(pw.Model):
    id = pw.AutoField()
    text = pw.TextField()
    number = pw.IntegerField(null=True)

    class Meta:
        database = DB

TestModel.create_table()


app = FastAPI()

ListFastAPIView.make_model_view(TestModel)().add_to_app(app)
uvicorn.run(app)

YoilyL avatar Jan 02 '24 23:01 YoilyL