FastAPI filters with standard query parameter syntax (comma-separated values)
Project description
Introduction
fastapi_filters_standard is a fork of fastapi-filters providing filtering and sorting features for FastAPI applications.
Additional features in this fork
Compared to the original fastapi-filters, this fork adds:
- Standard query parameter syntax using
field__operation. - Nested filtering (
state__fa_name__icontains=tehran). - Nested sorting (
sort=+state__fa_name,-name). - Comma-separated values for multi-value filters.
- Raw Mode for forwarding filters without parsing.
- SQLAlchemy relationship-aware filtering and sorting.
- Django-inspired lookup names.
Installation
pip install fastapi_filters_standard
What's Different?
This fork changes several behaviors compared to the original package.
Standard query syntax
Instead of:
field[gte]=18
use:
field__gte=18
Nested lookups
Nested relationships can be filtered using Django-style syntax.
GET /cities?state__fa_name__icontains=tehran
Multi-value filters
List values are passed as comma-separated values.
GET /users?id__in=1,2,3
instead of repeating the same query parameter.
Raw Mode
Filters can optionally be returned exactly as received from the request without parsing.
Quickstart
Filters can be defined either manually or automatically from Pydantic models.
from typing import List
from fastapi import FastAPI, Depends
from pydantic import BaseModel, Field
from fastapi_filters_standard import (
create_filters,
create_filters_from_model,
create_sorting_from_model,
FilterValues,
RawFilterValues,
SortingValues,
)
app = FastAPI()
class UserOut(BaseModel):
name: str = Field(..., example="Steve")
surname: str = Field(..., example="Rogers")
age: int = Field(..., example=102)
# Manual filters
@app.get("/users/manual")
async def get_users_manual_filters(
filters: FilterValues = Depends(create_filters(name=str, surname=str, age=int)),
) -> List[UserOut]:
pass
# Automatic filters from Pydantic model
@app.get("/users/auto")
async def get_users_auto_filters(
filters: FilterValues = Depends(create_filters_from_model(UserOut)),
) -> List[UserOut]:
pass
# Raw Mode example
@app.get("/users/raw")
async def get_users_raw_filters(
filters: RawFilterValues = Depends(
create_filters_from_model(UserOut, raw_mode=True)
),
) -> RawFilterValues:
pass
Nested Filters Example
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from sqlalchemy import select
from db.models import City
from fastapi_filters_standard import create_filters_from_model, FilterValues
from fastapi_filters_standard.ext.sqlalchemy import apply_filters
app = FastAPI()
class StateRead(BaseModel):
fa_name: str
class CityRead(BaseModel):
id: int
fa_name: str
state: StateRead
@app.get("/cities")
async def list_cities(
filters: FilterValues = Depends(
create_filters_from_model(
CityRead,
nested=True,
max_depth=1, # allow filtering one level deep (City -> State)
)
),
):
"""
Example usage:
GET /cities?state__fa_name__icontains=تهران
This produces:
filters = {'state__fa_name': {'icontains': 'تهران'}}
"""
query = apply_filters(select(City), filters, model=City)
result = await db.scalars(query)
return result.all()
This allows filtering cities based on fields of the related state object.
Nested Sorting Example
class FastAPI: pass
from fastapi import FastAPI, Depends
from pydantic import BaseModel
from sqlalchemy import select
from db.models import City
from fastapi_filters_standard import (
SortingValues,
create_sorting_from_model,
)
from fastapi_filters_standard.ext.sqlalchemy import apply_sorting
app = FastAPI()
class StateRead(BaseModel):
fa_name: str
class CityRead(BaseModel):
id: int
fa_name: str
state: StateRead
@app.get("/cities")
async def list_cities(
sorting: SortingValues = Depends(
create_sorting_from_model(
CityRead,
nested=True,
max_depth=1,
)
),
):
"""
Example usage:
GET /cities?sort=+state__fa_name,-fa_name
"""
query = apply_sorting(
select(City),
sorting,
model=City,
nested=True,
)
result = await db.scalars(query)
return result.all()
This allows ordering by fields of related models using the same double-underscore syntax as nested filters.
Examples:
GET /cities?sort=+state__fa_name
GET /cities?sort=-state__fa_name,+fa_name
GET /cities?sort=+country__name,-state__fa_name,+created_at
SQLAlchemy Integration
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from fastapi_filters_standard.ext.sqlalchemy import apply_filters
@app.get("/users")
async def get_users(
db: AsyncSession = Depends(get_db),
filters: FilterValues = Depends(create_filters_from_model(UserOut)),
) -> List[UserOut]:
query = apply_filters(select(User), filters)
result = await db.scalars(query)
return result.all()
Raw Mode Example with pytest
import pytest
from fastapi import FastAPI, Depends
from fastapi.testclient import TestClient
from pydantic import BaseModel
from fastapi_filters_standard import create_filters_from_model, RawFilterValues
@pytest.mark.asyncio
async def test_raw_mode():
app = FastAPI()
class UserModel(BaseModel):
username: str
age: int
@app.get("/")
async def route(
filters: RawFilterValues = Depends(create_filters_from_model(UserModel, raw_mode=True))
) -> RawFilterValues:
return filters
client = TestClient(app)
res = client.get("/", params={"username__contains": "09001", "age__gte": "18"})
assert res.status_code == 200
assert res.json() == {"username__contains": "09001", "age__gte": "18"}
Features of this fork
-
Standardized query parameter syntax (
field__operation) -
Standardized operation names
-
Comma-separated list support for multi-value filters
-
Full support for filtering and sorting
-
- Nested filters and sorting are supported, allowing filtering and ordering on related objects.
Examples:
GET /cities?state__fa_name__icontains=تهران GET /cities?sort=+state__fa_name,-country__name
-
Raw Mode: receive filters exactly as sent, for forwarding to external services
-
Compatible with FastAPI and SQLAlchemy async
-
Backwards compatible with most
fastapi-filtersfeatures
Supported Lookup Operators
| Lookup | Description | Example |
|---|---|---|
exact |
Exact match (default lookup) | ?name=John |
ne |
Not equal | ?age__ne=18 |
gt |
Greater than | ?age__gt=18 |
gte |
Greater than or equal | ?age__gte=18 |
lt |
Less than | ?age__lt=65 |
lte |
Less than or equal | ?age__lte=65 |
contains |
Contains (String, ARRAY, JSON) | ?tags__contains=python |
icontains |
Case-insensitive contains | ?name__icontains=john |
not_contains |
Does not contain | ?tags__not_contains=python |
not_icontains |
Does not Case-insensitive contain | ?tags__not_contains=python |
startswith |
Starts with | ?name__startswith=Jo |
not_startswith |
Does not start with | ?name__not_startswith=Jo |
istartswith |
Case-insensitive starts with | ?name__istartswith=jo |
not_istartswith |
Case-insensitive does not start with | ?name__not_istartswith=jo |
endswith |
Ends with | ?name__endswith=son |
not_endswith |
Does not end with | ?name__not_endswith=son |
iendswith |
Case-insensitive ends with | ?name__iendswith=son |
not_iendswith |
Case-insensitive does not end with | ?name__not_iendswith=son |
in |
Value in a list | ?id__in=1,2,3 |
not_in |
Value not in a list | ?id__not_in=1,2,3 |
isnull |
Check for NULL |
?deleted_at__isnull=true |
overlap |
PostgreSQL overlap | ?tags__overlap=python,django |
not_overlap |
Negated PostgreSQL overlap | ?tags__not_overlap=python,django |
range |
Between two values | ?age__range=18,30 |
date |
Match the date part of a datetime | ?created_at__date=2026-07-14 |
year |
Match year | ?created_at__year=2026 |
month |
Match month | ?created_at__month=7 |
day |
Match day | ?created_at__day=14 |
hour |
Match hour | ?created_at__hour=10 |
minute |
Match minute | ?created_at__minute=30 |
second |
Match second | ?created_at__second=15 |
time |
Match the time part of a datetime | ?created_at__time=10:30:00 |
Note
icontainsis implemented internally using SQLAlchemy'silike()and performs a case-insensitive substring search (ILIKE '%value%').containsuses SQLAlchemy's nativecontains()operator, which works with strings as well as database types such as PostgreSQLARRAYandJSON.likesupports SQL wildcard characters such as%and_.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fastapi_filters_standard-0.7.1.tar.gz.
File metadata
- Download URL: fastapi_filters_standard-0.7.1.tar.gz
- Upload date:
- Size: 106.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98cf324f9e931463c692c8a950f61450a13297c90e6263abab159fdad4b5a997
|
|
| MD5 |
97b897401514ab45b4a85f5e710c0236
|
|
| BLAKE2b-256 |
076290761f3a145232edb6a2bcd5833c3b18b638cc8e403b84995a07c0056d88
|
File details
Details for the file fastapi_filters_standard-0.7.1-py3-none-any.whl.
File metadata
- Download URL: fastapi_filters_standard-0.7.1-py3-none-any.whl
- Upload date:
- Size: 29.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
758c6d917276705b6aad98663f003fc75bdeaaaabd1453f15ddda6523722aa51
|
|
| MD5 |
193aa53c8415ba06d9dda4ba9f89dd43
|
|
| BLAKE2b-256 |
6fae6b184bc60b693a37a18c85c5bbb4a6a44267217669672070a2a3965edba0
|