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.2.1.tar.gz (49.0 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.2.1-py3-none-any.whl (9.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fastapi_fsp-0.2.1.tar.gz
  • Upload date:
  • Size: 49.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_fsp-0.2.1.tar.gz
Algorithm Hash digest
SHA256 6dfdf5e9d8a2a8b0edd5a53c2081225ea937fbb8b2880e0ee74cd740acc2c51c
MD5 59b97559aa5c32631cb595095a996d06
BLAKE2b-256 4429984392da4c351f4612e7c172c55d830e52ef678f9a8f562c6f862dbecdfc

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fastapi_fsp-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 9.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.21 {"installer":{"name":"uv","version":"0.9.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for fastapi_fsp-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 622a1ac5c182390d78480252b690dfd30ec06ac480b3e8196e3259d4e172d39e
MD5 7b00bba77db7b0fe8bc7bbd1e7aa122f
BLAKE2b-256 70e5c95eeafbfbdfe5617e7585e5dd7173b8b6f7156e6b6e5bc034e22f1aa97e

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