fastapi-fsp


Namefastapi-fsp JSON
Version 0.1.1 PyPI version JSON
download
home_pageNone
SummaryFilter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel
upload_time2025-08-20 13:31:06
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseMIT
keywords api fastapi filtering pagination sorting sqlmodel
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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.

```python
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 (repeatable sets; arrays are supported by sending multiple parameters):
- 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:
- `?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`

You can chain multiple filters by repeating the triplet:
```
?field=age&operator=gte&value=18&field=name&operator=ilike&value=rust
```

## 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.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fastapi-fsp",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.12",
    "maintainer_email": null,
    "keywords": "api, fastapi, filtering, pagination, sorting, sqlmodel",
    "author": null,
    "author_email": "Evert Jan Stamhuis <ej@fromejdevelopment.nl>",
    "download_url": "https://files.pythonhosted.org/packages/8a/60/bdb75c0007716da0005b4bd6cd55df7c05c64467e55f82bf29e6a859d1ac/fastapi_fsp-0.1.1.tar.gz",
    "platform": null,
    "description": "# fastapi-fsp\n\nFilter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel.\n\nfastapi-fsp helps you build standardized list endpoints that support:\n- Filtering on arbitrary fields with rich operators (eq, ne, lt, lte, gt, gte, in, between, like/ilike, null checks, contains/starts_with/ends_with)\n- Sorting by field (asc/desc)\n- Pagination with page/per_page and convenient HATEOAS links\n\nIt is framework-friendly: you declare it as a FastAPI dependency and feed it a SQLModel/SQLAlchemy Select query and a Session.\n\n## Installation\n\nUsing uv (recommended):\n\n```\n# create & activate virtual env with uv\nuv venv\n. .venv/bin/activate\n\n# add runtime dependency\nuv add fastapi-fsp\n```\n\nUsing pip:\n\n```\npip install fastapi-fsp\n```\n\n## Quick start\n\nBelow is a minimal example using FastAPI and SQLModel.\n\n```python\nfrom typing import Optional\nfrom fastapi import Depends, FastAPI\nfrom sqlmodel import Field, SQLModel, Session, create_engine, select\n\nfrom fastapi_fsp.fsp import FSPManager\nfrom fastapi_fsp.models import PaginatedResponse\n\nclass HeroBase(SQLModel):\n    name: str = Field(index=True)\n    secret_name: str\n    age: Optional[int] = Field(default=None, index=True)\n\nclass Hero(HeroBase, table=True):\n    id: Optional[int] = Field(default=None, primary_key=True)\n\nclass HeroPublic(HeroBase):\n    id: int\n\nengine = create_engine(\"sqlite:///database.db\", connect_args={\"check_same_thread\": False})\nSQLModel.metadata.create_all(engine)\n\napp = FastAPI()\n\ndef get_session():\n    with Session(engine) as session:\n        yield session\n\n@app.get(\"/heroes/\", response_model=PaginatedResponse[HeroPublic])\ndef read_heroes(*, session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):\n    query = select(Hero)\n    return fsp.generate_response(query, session)\n```\n\nRun the app and query:\n\n- Pagination: `GET /heroes/?page=1&per_page=10`\n- Sorting: `GET /heroes/?sort_by=name&order=asc`\n- Filtering: `GET /heroes/?field=age&operator=gte&value=21`\n\nThe response includes data, meta (pagination, filters, sorting), and links (self, first, next, prev, last).\n\n## Query parameters\n\nPagination:\n- page: integer (>=1), default 1\n- per_page: integer (1..100), default 10\n\nSorting:\n- sort_by: the field name, e.g., `name`\n- order: `asc` or `desc`\n\nFiltering (repeatable sets; arrays are supported by sending multiple parameters):\n- field: the field/column name, e.g., `name`\n- operator: one of\n  - eq, ne\n  - lt, lte, gt, gte\n  - in, not_in (comma-separated values)\n  - between (two comma-separated values)\n  - like, not_like\n  - ilike, not_ilike (if backend supports ILIKE)\n  - is_null, is_not_null\n  - contains, starts_with, ends_with (translated to LIKE patterns)\n- value: raw string value (or list-like comma-separated depending on operator)\n\nExamples:\n- `?field=name&operator=eq&value=Deadpond`\n- `?field=age&operator=between&value=18,30`\n- `?field=name&operator=in&value=Deadpond,Rusty-Man`\n- `?field=name&operator=contains&value=man`\n\nYou can chain multiple filters by repeating the triplet:\n```\n?field=age&operator=gte&value=18&field=name&operator=ilike&value=rust\n```\n\n## Response model\n\n```\n{\n  \"data\": [ ... ],\n  \"meta\": {\n    \"pagination\": {\n      \"total_items\": 42,\n      \"per_page\": 10,\n      \"current_page\": 1,\n      \"total_pages\": 5\n    },\n    \"filters\": [\n      {\"field\": \"name\", \"operator\": \"eq\", \"value\": \"Deadpond\"}\n    ],\n    \"sort\": {\"sort_by\": \"name\", \"order\": \"asc\"}\n  },\n  \"links\": {\n    \"self\": \"/heroes/?page=1&per_page=10\",\n    \"first\": \"/heroes/?page=1&per_page=10\",\n    \"next\": \"/heroes/?page=2&per_page=10\",\n    \"prev\": null,\n    \"last\": \"/heroes/?page=5&per_page=10\"\n  }\n}\n```\n\n## Development\n\nThis project uses uv as the package manager.\n\n- Create env and sync deps:\n```\nuv venv\n. .venv/bin/activate\nuv sync --dev\n```\n\n- Run lint and format checks:\n```\nuv run ruff check .\nuv run ruff format --check .\n```\n\n- Run tests:\n```\nuv run pytest -q\n```\n\n- Build the package:\n```\nuv build\n```\n\n## CI/CD and Releases\n\nGitHub Actions workflows are included:\n- CI (lint + tests) runs on pushes and PRs.\n- Release: pushing a tag matching `v*.*.*` runs tests, builds, and publishes to PyPI using `PYPI_API_TOKEN` secret.\n\nTo release:\n1. Update the version in `pyproject.toml`.\n2. Push a tag, e.g. `git tag v0.1.1 && git push origin v0.1.1`.\n3. Ensure the repository has `PYPI_API_TOKEN` secret set (an API token from PyPI).\n\n## License\n\nMIT License. See LICENSE.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel",
    "version": "0.1.1",
    "project_urls": {
        "Homepage": "https://github.com/fromej-dev/fastapi-fsp",
        "Issues": "https://github.com/fromej-dev/fastapi-fsp/issues",
        "Repository": "https://github.com/fromej-dev/fastapi-fsp"
    },
    "split_keywords": [
        "api",
        " fastapi",
        " filtering",
        " pagination",
        " sorting",
        " sqlmodel"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b43f5981f1ac27f2bd23f792edcf4bddbe5f36c9c98c7fa84765292ee626c76a",
                "md5": "676c73bbd6687acf44ae83cadb8c316b",
                "sha256": "0c673977433af3054fea1838735843eeedf00fe96c456fb0c5da48baa917b1c7"
            },
            "downloads": -1,
            "filename": "fastapi_fsp-0.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "676c73bbd6687acf44ae83cadb8c316b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.12",
            "size": 7842,
            "upload_time": "2025-08-20T13:31:05",
            "upload_time_iso_8601": "2025-08-20T13:31:05.728275Z",
            "url": "https://files.pythonhosted.org/packages/b4/3f/5981f1ac27f2bd23f792edcf4bddbe5f36c9c98c7fa84765292ee626c76a/fastapi_fsp-0.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "8a60bdb75c0007716da0005b4bd6cd55df7c05c64467e55f82bf29e6a859d1ac",
                "md5": "22086d790fc9f5517d5b947f824b1d7f",
                "sha256": "f216b5d65c14a8d16014b6dddedfd14998cdb557d3aacdf9964ae4ba0c73f76c"
            },
            "downloads": -1,
            "filename": "fastapi_fsp-0.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "22086d790fc9f5517d5b947f824b1d7f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.12",
            "size": 39139,
            "upload_time": "2025-08-20T13:31:06",
            "upload_time_iso_8601": "2025-08-20T13:31:06.495466Z",
            "url": "https://files.pythonhosted.org/packages/8a/60/bdb75c0007716da0005b4bd6cd55df7c05c64467e55f82bf29e6a859d1ac/fastapi_fsp-0.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-20 13:31:06",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "fromej-dev",
    "github_project": "fastapi-fsp",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "fastapi-fsp"
}
        
Elapsed time: 0.94707s