How to enable filtering on all fields of a model in FastAPI

Question:

In Django with the restframework, you can do this:

class Item(models.Model):
    id = models.IntegerField()
    name = models.CharField(max_length=32)
    another_attribute = models.CharField(max_length=32)
    ...
    (more attributes)
    ...
    yet_another_attribute = models.CharField(max_length=32)

class ItemViewSet(viewsets.ReadOnlyModelViewSet):
    permission_classes = [IsAuthenticated]
    serializer_class = ItemSerializer
    filterset_fields = '__all__' # <- this enables filtering on all fields
    queryset = Item.objects.all()

If I want to allow filtering, filterset_fields = '__all__' would allow me to do something like api/item/?(attribute)=(value) and allow me to filter on any attribute

I’m going through the tutorial (https://fastapi.tiangolo.com/tutorial/sql-databases/#crud-utils) and it looks like there is a lot of manual filtering involved:

from fastapi_sqlalchemy import db

class Item(BaseModel):
    id: int
    name: str
    another_attribute: str
    ...
    (more attributes)
    ...
    yet_another_attribute: str

# is it necessary to manually include all the fields I want to filter on as optional query parameters?
@app.get("/items/")
async def read_item(
    db: Session,
    id: Optional[int] = None,
    name: Optional[str] = None,
    another_attribute: Optional[str] = None,
    ...
    (more attributes)
    ...
    yet_another_attribute: Optional[str] = None
):
    # and then I'd need to check if the query parameter has been specified, and if so, filter it.
    queryset = db.session.query(Item)
    if id:
        queryset = queryset.filter(Item.id == id)
    if name:
        queryset = queryset.filter(Item.name == name)
    if another_attribute:
        queryset = queryset.filter(Item.another_attribute == another_attribute)
    ...
    (repeat above pattern for more attributes)
    ...
    if yet_another_attribute:
        queryset = queryset.filter(Item.yet_another_attribute == yet_another_attribute)

What is the preferred way of implementing the above behaviour? Are there any packages that will save me from having to do a lot of manual filtering that will give me the same behaviour as conveniently as the Django Rest Framework viewsets?

Or is manually including all the fields I want to filter on as optional query parameters, then checking for each parameter and then filtering if present the only way?

Asked By: A G

||

Answers:

Definitely, it’s described in the docs.
Try this, ellipsis marking the field as required.

id: Optional[int] = Header(...)  # Header, path or any another place 

See https://fastapi.tiangolo.com/tutorial/query-params-str-validations/

Answered By: Давид Шико

It is possible but not yet perfect:

from fastapi.params import Depends

@app.get("/items/")
async def read_item(item: Item = Depends()):
    pass

See FastAPI documentation for details.

The downside is that the parameters are required as maybe specified in the Item class. It is possible to write a subclass with all optional parameters (e.g. like described here). It is working for instances of the class but FastAPI does not seem to reflect those in the API docs. If anyone has a solution to that I’d be happy to learn.

Alternatively you can have multiple models as described here. But I don’t like this approach.

To answer your 2nd question you can access all generic parameters like this:

@app.get("/items/")
async def read_item(
    db: Session,
    id: Optional[int] = None,
    name: Optional[str] = None,
    another_attribute: Optional[str] = None,
    ...
    (more attributes)
    ...
    yet_another_attribute: Optional[str] = None
):
    params = locals().copy()
    ...
    for attr in [x for x in params if params[x] is not None]:
        query = query.filter(getattr(db_model.Item, attr).like(params[attr]))
Answered By: HeyMan
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.