Filterify
filterify is a pydantic-based library to handle filters from the query params.
Documentation: https://filterify.boardpack.org
Source Code: https://github.com/boardpack/filterify
Requirements¶
Python 3.8+
filterify has the next dependencies:
Installation¶
$ pip install filterify
---> 100%
First steps¶
To start to work with filterify, you just need to have some Pydantic model you want to have as filters.
Let's define simple Address
and Shipment
models. Then just pass the Shipment
model to the Filterify
constructor
and you will get a callable object to parse query params. By default, the parser returns a dictionary structure with
the parsing results.
from pydantic import BaseModel
from filterify import Filterify
class Address(BaseModel):
street: str
city: str
country: str
class Shipment(BaseModel):
name: str
sender: Address
recipient: Address
weight: float
model_filter = Filterify(Shipment)
print(model_filter('name=shoes&sender__country=US&recipient__country__ne=CA'))
# [
# {
# 'field': [
# 'name'
# ],
# 'value': 'shoes',
# 'operation': 'eq'
# },
# {
# 'field': [
# 'sender',
# 'country'
# ],
# 'value': 'US',
# 'operation': 'eq'
# },
# {
# 'field': [
# 'recipient',
# 'country'
# ],
# 'value': 'CA',
# 'operation': 'ne'
# }
# ]
Filterify supports nested models and uses __
as a delimiter for the nested models and operations. If you want to
change it, pass the needed delimiter
to the constructor as it's shown in the next example.
from pydantic import BaseModel
from filterify import Filterify
class Address(BaseModel):
country: str
class Shipment(BaseModel):
sender: Address
model_filter = Filterify(Shipment, delimiter='$')
print(model_filter('sender$country$ne=US'))
# [
# {
# 'field': [
# 'sender',
# 'country'
# ],
# 'value': 'US',
# 'operation': 'ne'
# }
# ]
Also, by default unknown fields are ignored, but you can change this behavior by passing False
to the constructor
parameter ignore_unknown_name
.
from pydantic import BaseModel
from filterify import Filterify
class User(BaseModel):
name: str
model_filter = Filterify(User, ignore_unknown_name=False)
print(model_filter('sender=US'))
# filterify.exceptions.UnknownFieldError: Filter name is not presented in the model: sender
Supported types¶
Currently, filterify supports a small number of types, it will be extended in the future:
Field type | Available lookups |
---|---|
str | eq , ne , contains , not_contains |
int | eq , ne , gt , lt , gte , lte |
float | eq , ne , gt , lt , gte , lte |
bool | eq , ne |
list | eq , ne |
In the case of an unknown type or lookup, it will be raised UnknownTypeError
and UnknownOperationError
exceptions.
Ordering option¶
You can add an ordering
field that accepts all model field names. Currently, it's used a django-like style when desc
is passed as -field_name
.
from pydantic import BaseModel
from filterify import Filterify
class Address(BaseModel):
country: str
class Shipment(BaseModel):
name: str
sender: Address
model_filter = Filterify(Shipment, ordering=True)
print(model_filter('ordering=unknown_field'))
# raises standard pydantic ValidationError with the next message:
# unexpected value; permitted: 'name', '-name', 'sender__country', '-sender__country'
If you want to change the accepted field name list, you can pass a list instead of the True
value.
from pydantic import BaseModel
from filterify import Filterify
class Address(BaseModel):
country: str
class Shipment(BaseModel):
name: str
sender: Address
model_filter = Filterify(Shipment, ordering=['name'])
print(model_filter('ordering=unknown_field'))
# raises standard pydantic ValidationError with the next message:
# unexpected value; permitted: 'name', '-name'
Usage with FastAPI¶
Most validation work is done by pydantic, so filterify can be easily used with FastAPI.
The internal validation model is wrapped by fastapi.Depends
and exposed by the as_dependency
method.
import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel
from filterify import Filterify
class Address(BaseModel):
street: str
city: str
country: str
class Shipment(BaseModel):
name: str
sender: Address
recipient: Address
weight: float
length: float
height: float
shipment_filter = Filterify(Shipment)
app = FastAPI()
@app.get('/shipments', dependencies=[shipment_filter.as_dependency()])
def shipments():
return []
@app.get('/another_shipments')
def another_shipments(filters=shipment_filter.as_dependency()):
print(filters)
return []
if __name__ == '__main__':
uvicorn.run(app)
Acknowledgments¶
Special thanks to Sebastián RamÃrez and his FastAPI project, some scripts and documentation structure and parts were used from there.
License¶
This project is licensed under the terms of the MIT license.