FilterSet¶
FilterSet is the core building block of fastapi-filters. It provides a declarative, class-based way to
define filters that integrate directly with FastAPI's dependency injection.
Basic Usage¶
Define a FilterSet by subclassing it and annotating fields with FilterField[<type>]:
from fastapi_filters import FilterField, FilterSet
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
is_active: FilterField[bool]
Use it in an endpoint with Depends():
from fastapi import FastAPI
app = FastAPI()
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# filters.filter_values returns only non-empty filters
return {"filters": filters.filter_values}
The library auto-generates query parameters based on each field's type. For the UserFilters above,
the following query parameters become available:
name[eq],name[ne],name[like],name[ilike],name[in], ...age[eq],age[ne],age[gt],age[ge],age[lt],age[le],age[in], ...is_active[eq],is_active[ne]
Accessing Filter Values¶
The filter_values property returns a dict of only the filters that were provided in the request:
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# Example: GET /users?name[eq]=John&age[gt]=25
# filters.filter_values == {
# "name": {FilterOperator.eq: "John"},
# "age": {FilterOperator.gt: 25},
# }
print(filters.filter_values)
You can also access individual fields directly:
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# Each field is a dict of {operator: value}
# Empty dict if no filter was provided for that field
print(filters.name) # e.g. {FilterOperator.eq: "John"}
print(filters.age) # e.g. {FilterOperator.gt: 25}
Use bool(filters) to check if any filters were provided:
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
if not filters:
return {"message": "No filters applied"}
...
Inheritance¶
FilterSet supports inheritance. Child classes inherit all parent fields and can add new ones:
class BaseFilters(FilterSet):
created_at: FilterField[datetime]
updated_at: FilterField[datetime]
class UserFilters(BaseFilters):
name: FilterField[str]
age: FilterField[int]
# UserFilters has: created_at, updated_at, name, age
Subset and Extract¶
subset()¶
Returns a new FilterSet containing only the specified fields:
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# Get only name-related filters
name_filters = filters.subset("name")
# or using the field descriptor
name_filters = filters.subset(UserFilters.name)
extract()¶
Extracts the specified fields from the filter set, removing them from the original. This is useful when you need to apply filters in different stages:
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# Extract name filters (removes them from `filters`)
name_filters = filters.extract("name")
# `filters` no longer contains name filters
# `name_filters` contains only name filters
extract() also accepts FilterSet subclasses to extract all fields from that set:
class TimestampFilters(FilterSet):
created_at: FilterField[datetime]
updated_at: FilterField[datetime]
class UserFilters(TimestampFilters):
name: FilterField[str]
age: FilterField[int]
@app.get("/users")
async def get_users(filters: UserFilters = Depends()):
# Extract all timestamp fields at once
ts_filters = filters.extract(TimestampFilters)
Creating from FilterOps¶
You can construct a FilterSet programmatically using from_ops():
filters = UserFilters.from_ops(
UserFilters.name == "John",
UserFilters.age > 25,
)
# Equivalent to: GET /users?name[eq]=John&age[gt]=25
This is especially useful in tests:
async def test_user_filters():
filters = UserFilters.from_ops(
UserFilters.is_active == True,
UserFilters.age >= 18,
)
stmt = apply_filters(select(User), filters)
...
Custom Hooks¶
You can override two class methods to customize how FilterSet generates query parameters.
__filter_field_adapt_type__¶
Override the type used for a specific field/operator combination in query parameters:
from typing import Any
from fastapi_filters import FilterField, FilterOperator, FilterSet
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
@classmethod
def __filter_field_adapt_type__(cls, field, tp, op) -> Any | None:
# Use a custom type for age's "in" operator
if field.name == "age" and op == FilterOperator.in_:
return list[int]
return None # use default behavior
__filter_field_generate_alias__¶
Override the query parameter alias for a specific field/operator:
class UserFilters(FilterSet):
name: FilterField[str]
@classmethod
def __filter_field_generate_alias__(cls, name, op, alias) -> str | None:
# Use double-underscore notation instead of brackets
return f"{name}__{op.name}"
init_filter_set Hook¶
Override init_filter_set() to run custom logic after the filter set is initialized:
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
def init_filter_set(self) -> None:
# Runs after all fields are populated
if self.name and self.age:
print("Both name and age filters are active")
create_filters_from_set¶
For cases where you need an explicit dependency function (e.g., when using Depends() directly),
use create_filters_from_set:
from fastapi import Depends
from fastapi_filters import FilterField, FilterSet, create_filters_from_set
class UserFilters(FilterSet):
name: FilterField[str]
age: FilterField[int]
@app.get("/users")
async def get_users(
filters: UserFilters = Depends(create_filters_from_set(UserFilters)),
):
...
Tip
In most cases you don't need create_filters_from_set -- simply declaring filters: UserFilters
as a parameter is enough. The explicit form is useful when you need to pass the dependency to
dependencies=[...] on a router or endpoint.