Skip to main content

Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel

Project description

fastapi-fsp

Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel.

fastapi-fsp helps you build standardized list endpoints that support:

  • Filtering on arbitrary fields with rich operators (eq, ne, lt, lte, gt, gte, in, between, like/ilike, null checks, contains/starts_with/ends_with)
  • Sorting by field (asc/desc)
  • Pagination with page/per_page and convenient HATEOAS links

It is framework-friendly: you declare it as a FastAPI dependency and feed it a SQLModel/SQLAlchemy Select query and a Session.

Installation

Using uv (recommended):

# create & activate virtual env with uv
uv venv
. .venv/bin/activate

# add runtime dependency
uv add fastapi-fsp

Using pip:

pip install fastapi-fsp

Quick start

Below is a minimal example using FastAPI and SQLModel.

from typing import Optional
from fastapi import Depends, FastAPI
from sqlmodel import Field, SQLModel, Session, create_engine, select

from fastapi_fsp.fsp import FSPManager
from fastapi_fsp.models import PaginatedResponse

class HeroBase(SQLModel):
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)

class Hero(HeroBase, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)

class HeroPublic(HeroBase):
    id: int

engine = create_engine("sqlite:///database.db", connect_args={"check_same_thread": False})
SQLModel.metadata.create_all(engine)

app = FastAPI()

def get_session():
    with Session(engine) as session:
        yield session

@app.get("/heroes/", response_model=PaginatedResponse[HeroPublic])
def read_heroes(*, session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
    query = select(Hero)
    return fsp.generate_response(query, session)

Run the app and query:

  • Pagination: GET /heroes/?page=1&per_page=10
  • Sorting: GET /heroes/?sort_by=name&order=asc
  • Filtering: GET /heroes/?field=age&operator=gte&value=21

The response includes data, meta (pagination, filters, sorting), and links (self, first, next, prev, last).

Query parameters

Pagination:

  • page: integer (>=1), default 1
  • per_page: integer (1..100), default 10

Sorting:

  • sort_by: the field name, e.g., name
  • order: asc or desc

Filtering (two supported formats):

  1. Simple (triplets repeated in the query string):
  • field: the field/column name, e.g., name
  • operator: one of
    • eq, ne
    • lt, lte, gt, gte
    • in, not_in (comma-separated values)
    • between (two comma-separated values)
    • like, not_like
    • ilike, not_ilike (if backend supports ILIKE)
    • is_null, is_not_null
    • contains, starts_with, ends_with (translated to LIKE patterns)
  • value: raw string value (or list-like comma-separated depending on operator)

Examples (simple format):

  • ?field=name&operator=eq&value=Deadpond
  • ?field=age&operator=between&value=18,30
  • ?field=name&operator=in&value=Deadpond,Rusty-Man
  • ?field=name&operator=contains&value=man
  • Chain multiple filters by repeating the triplet: ?field=age&operator=gte&value=18&field=name&operator=ilike&value=rust
  1. Indexed format (useful for clients that handle arrays of objects):
  • Use keys like filters[0][field], filters[0][operator], filters[0][value], then increment the index for additional filters (filters[1][...], etc.).

Example (indexed format):

?filters[0][field]=age&filters[0][operator]=gte&filters[0][value]=18&filters[1][field]=name&filters[1][operator]=ilike&filters[1][value]=joy

Notes:

  • Both formats are equivalent; the indexed format takes precedence if present.
  • If any filter is incomplete (missing operator or value in the indexed form, or mismatched counts of simple triplets), the API responds with HTTP 400.

Response model

{
  "data": [ ... ],
  "meta": {
    "pagination": {
      "total_items": 42,
      "per_page": 10,
      "current_page": 1,
      "total_pages": 5
    },
    "filters": [
      {"field": "name", "operator": "eq", "value": "Deadpond"}
    ],
    "sort": {"sort_by": "name", "order": "asc"}
  },
  "links": {
    "self": "/heroes/?page=1&per_page=10",
    "first": "/heroes/?page=1&per_page=10",
    "next": "/heroes/?page=2&per_page=10",
    "prev": null,
    "last": "/heroes/?page=5&per_page=10"
  }
}

Development

This project uses uv as the package manager.

  • Create env and sync deps:
uv venv
. .venv/bin/activate
uv sync --dev
  • Run lint and format checks:
uv run ruff check .
uv run ruff format --check .
  • Run tests:
uv run pytest -q
  • Build the package:
uv build

CI/CD and Releases

GitHub Actions workflows are included:

  • CI (lint + tests) runs on pushes and PRs.
  • Release: pushing a tag matching v*.*.* runs tests, builds, and publishes to PyPI using PYPI_API_TOKEN secret.

To release:

  1. Update the version in pyproject.toml.
  2. Push a tag, e.g. git tag v0.1.1 && git push origin v0.1.1.
  3. Ensure the repository has PYPI_API_TOKEN secret set (an API token from PyPI).

License

MIT License. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastapi_fsp-0.1.2.tar.gz (40.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastapi_fsp-0.1.2-py3-none-any.whl (8.7 kB view details)

Uploaded Python 3

File details

Details for the file fastapi_fsp-0.1.2.tar.gz.

File metadata

  • Download URL: fastapi_fsp-0.1.2.tar.gz
  • Upload date:
  • Size: 40.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.8

File hashes

Hashes for fastapi_fsp-0.1.2.tar.gz
Algorithm Hash digest
SHA256 2e4de46b804e76ba161bc84d683b3a50d3f13ef3225cb37c73df54263df17a9f
MD5 ef59cc9b26dad776959c3fc3bfba4ebd
BLAKE2b-256 0c1ed2dd2b65b486247fc853f8eb720d764f8b6e05a3ff21bbe67d0a463f17eb

See more details on using hashes here.

File details

Details for the file fastapi_fsp-0.1.2-py3-none-any.whl.

File metadata

File hashes

Hashes for fastapi_fsp-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 d72ce4e2bdd8a6432e607cf4a9522ecd1ae39c264c497a8ea9800e1ba043bc17
MD5 2d65c92705a7555d8f4850c43954051f
BLAKE2b-256 4c411012cc0506fcf45f4d33362255819f562a29d51a1cee6fbb8839aa1510d0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page