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?
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/
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]))
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?
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/
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]))