fastapi-fsp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, master ]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build-test-lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.12'
19
+
20
+ - name: Set up uv
21
+ uses: astral-sh/setup-uv@v4
22
+
23
+ - name: Sync dependencies (with dev)
24
+ run: uv sync --dev
25
+
26
+ - name: Lint (ruff)
27
+ run: uv run ruff check .
28
+
29
+ - name: Format check (ruff)
30
+ run: uv run ruff format --check .
31
+
32
+ - name: Run tests
33
+ env:
34
+ PYTHONWARNINGS: default
35
+ run: uv run pytest -q
36
+
37
+ - name: Upload coverage artifacts
38
+ if: always()
39
+ uses: actions/upload-artifact@v4
40
+ with:
41
+ name: coverage-reports
42
+ path: |
43
+ coverage.xml
44
+ htmlcov
45
+
46
+ - name: Upload coverage to Codecov
47
+ if: always()
48
+ uses: codecov/codecov-action@v4
49
+ with:
50
+ files: ./coverage.xml
51
+ fail_ci_if_error: false
52
+ env:
53
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,42 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*.*.*'
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ contents: read
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Set up Python
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: '3.12'
22
+
23
+ - name: Set up uv
24
+ uses: astral-sh/setup-uv@v4
25
+
26
+ - name: Sync (no dev)
27
+ run: uv sync
28
+
29
+ - name: Run tests
30
+ run: uv run pytest -q --maxfail=1
31
+
32
+ - name: Build package
33
+ run: uv build
34
+
35
+ - name: Publish to PyPI
36
+ env:
37
+ UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
38
+ run: |
39
+ if [ -z "$UV_PUBLISH_TOKEN" ]; then
40
+ echo "PYPI_API_TOKEN secret is not set." && exit 1
41
+ fi
42
+ uv publish --token "$UV_PUBLISH_TOKEN"
@@ -0,0 +1,56 @@
1
+ # -------- Python --------
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+ ENV/
13
+ .env/
14
+ .python-version
15
+
16
+ # Packaging / build
17
+ build/
18
+ dist/
19
+ *.egg-info/
20
+ .eggs/
21
+ *.egg
22
+
23
+ # Unit test / coverage reports
24
+ .coverage
25
+ .coverage.*
26
+ .cache/
27
+ .pytest_cache/
28
+ htmlcov/
29
+ coverage.xml
30
+
31
+ # Logs
32
+ *.log
33
+
34
+ # IDEs and editors
35
+ .vscode/
36
+ .idea/
37
+ *.iml
38
+
39
+ # OS-specific
40
+ .DS_Store
41
+ Thumbs.db
42
+
43
+ # Databases (local dev/testing)
44
+ *.db
45
+ *.sqlite3
46
+
47
+ # FastAPI/UVicorn
48
+ debug.log
49
+
50
+ # uv
51
+ # Keep uv.lock tracked (do not ignore)
52
+ # uv creates virtual envs typically under .venv which is already ignored
53
+
54
+ # Misc
55
+ .swp
56
+ .swo
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-fsp
3
+ Version: 0.1.0
4
+ Summary: Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel
5
+ Project-URL: Homepage, https://github.com/your-org/fastapi-fsp
6
+ Project-URL: Repository, https://github.com/your-org/fastapi-fsp
7
+ Project-URL: Issues, https://github.com/your-org/fastapi-fsp/issues
8
+ Author-email: Evert Jan Stamhuis <ej@fromejdevelopment.nl>
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: api,fastapi,filtering,pagination,sorting,sqlmodel
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Internet :: WWW/HTTP
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: fastapi>=0.111
23
+ Requires-Dist: sqlmodel>=0.0.24
24
+ Description-Content-Type: text/markdown
25
+
26
+ # fastapi-fsp
27
+
28
+ Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel.
29
+
30
+ fastapi-fsp helps you build standardized list endpoints that support:
31
+ - Filtering on arbitrary fields with rich operators (eq, ne, lt, lte, gt, gte, in, between, like/ilike, null checks, contains/starts_with/ends_with)
32
+ - Sorting by field (asc/desc)
33
+ - Pagination with page/per_page and convenient HATEOAS links
34
+
35
+ It is framework-friendly: you declare it as a FastAPI dependency and feed it a SQLModel/SQLAlchemy Select query and a Session.
36
+
37
+ ## Installation
38
+
39
+ Using uv (recommended):
40
+
41
+ ```
42
+ # create & activate virtual env with uv
43
+ uv venv
44
+ . .venv/bin/activate
45
+
46
+ # add runtime dependency
47
+ uv add fastapi-fsp
48
+ ```
49
+
50
+ Using pip:
51
+
52
+ ```
53
+ pip install fastapi-fsp
54
+ ```
55
+
56
+ ## Quick start
57
+
58
+ Below is a minimal example using FastAPI and SQLModel.
59
+
60
+ ```python
61
+ from typing import Optional
62
+ from fastapi import Depends, FastAPI
63
+ from sqlmodel import Field, SQLModel, Session, create_engine, select
64
+
65
+ from fastapi_fsp.fsp import FSPManager
66
+ from fastapi_fsp.models import PaginatedResponse
67
+
68
+ class HeroBase(SQLModel):
69
+ name: str = Field(index=True)
70
+ secret_name: str
71
+ age: Optional[int] = Field(default=None, index=True)
72
+
73
+ class Hero(HeroBase, table=True):
74
+ id: Optional[int] = Field(default=None, primary_key=True)
75
+
76
+ class HeroPublic(HeroBase):
77
+ id: int
78
+
79
+ engine = create_engine("sqlite:///database.db", connect_args={"check_same_thread": False})
80
+ SQLModel.metadata.create_all(engine)
81
+
82
+ app = FastAPI()
83
+
84
+ def get_session():
85
+ with Session(engine) as session:
86
+ yield session
87
+
88
+ @app.get("/heroes/", response_model=PaginatedResponse[HeroPublic])
89
+ def read_heroes(*, session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
90
+ query = select(Hero)
91
+ return fsp.generate_response(query, session)
92
+ ```
93
+
94
+ Run the app and query:
95
+
96
+ - Pagination: `GET /heroes/?page=1&per_page=10`
97
+ - Sorting: `GET /heroes/?sort_by=name&order=asc`
98
+ - Filtering: `GET /heroes/?field=age&operator=gte&value=21`
99
+
100
+ The response includes data, meta (pagination, filters, sorting), and links (self, first, next, prev, last).
101
+
102
+ ## Query parameters
103
+
104
+ Pagination:
105
+ - page: integer (>=1), default 1
106
+ - per_page: integer (1..100), default 10
107
+
108
+ Sorting:
109
+ - sort_by: the field name, e.g., `name`
110
+ - order: `asc` or `desc`
111
+
112
+ Filtering (repeatable sets; arrays are supported by sending multiple parameters):
113
+ - field: the field/column name, e.g., `name`
114
+ - operator: one of
115
+ - eq, ne
116
+ - lt, lte, gt, gte
117
+ - in, not_in (comma-separated values)
118
+ - between (two comma-separated values)
119
+ - like, not_like
120
+ - ilike, not_ilike (if backend supports ILIKE)
121
+ - is_null, is_not_null
122
+ - contains, starts_with, ends_with (translated to LIKE patterns)
123
+ - value: raw string value (or list-like comma-separated depending on operator)
124
+
125
+ Examples:
126
+ - `?field=name&operator=eq&value=Deadpond`
127
+ - `?field=age&operator=between&value=18,30`
128
+ - `?field=name&operator=in&value=Deadpond,Rusty-Man`
129
+ - `?field=name&operator=contains&value=man`
130
+
131
+ You can chain multiple filters by repeating the triplet:
132
+ ```
133
+ ?field=age&operator=gte&value=18&field=name&operator=ilike&value=rust
134
+ ```
135
+
136
+ ## Response model
137
+
138
+ ```
139
+ {
140
+ "data": [ ... ],
141
+ "meta": {
142
+ "pagination": {
143
+ "total_items": 42,
144
+ "per_page": 10,
145
+ "current_page": 1,
146
+ "total_pages": 5
147
+ },
148
+ "filters": [
149
+ {"field": "name", "operator": "eq", "value": "Deadpond"}
150
+ ],
151
+ "sort": {"sort_by": "name", "order": "asc"}
152
+ },
153
+ "links": {
154
+ "self": "/heroes/?page=1&per_page=10",
155
+ "first": "/heroes/?page=1&per_page=10",
156
+ "next": "/heroes/?page=2&per_page=10",
157
+ "prev": null,
158
+ "last": "/heroes/?page=5&per_page=10"
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## Development
164
+
165
+ This project uses uv as the package manager.
166
+
167
+ - Create env and sync deps:
168
+ ```
169
+ uv venv
170
+ . .venv/bin/activate
171
+ uv sync --dev
172
+ ```
173
+
174
+ - Run lint and format checks:
175
+ ```
176
+ uv run ruff check .
177
+ uv run ruff format --check .
178
+ ```
179
+
180
+ - Run tests:
181
+ ```
182
+ uv run pytest -q
183
+ ```
184
+
185
+ - Build the package:
186
+ ```
187
+ uv build
188
+ ```
189
+
190
+ ## CI/CD and Releases
191
+
192
+ GitHub Actions workflows are included:
193
+ - CI (lint + tests) runs on pushes and PRs.
194
+ - Release: pushing a tag matching `v*.*.*` runs tests, builds, and publishes to PyPI using `PYPI_API_TOKEN` secret.
195
+
196
+ To release:
197
+ 1. Update the version in `pyproject.toml`.
198
+ 2. Push a tag, e.g. `git tag v0.1.1 && git push origin v0.1.1`.
199
+ 3. Ensure the repository has `PYPI_API_TOKEN` secret set (an API token from PyPI).
200
+
201
+ ## License
202
+
203
+ MIT License. See LICENSE.
@@ -0,0 +1,124 @@
1
+ # FastAPI Filtering, Sorting and Pagination
2
+
3
+ ## pypi package name
4
+ fastapi-fsp
5
+
6
+ ## pypi package version
7
+ 0.1.0
8
+
9
+ ## pypi package description
10
+ Package to implement filtering, sorting and pagination in FastAPI endpoints
11
+ using SQLModel
12
+
13
+ Endpoint Depends class that parses query parameters for filtering,
14
+ sorting and pagination. It performs all necessary checks.
15
+ Filtering is with query params: field, operator, value (support multiple)
16
+ Sorting is with query params: sort, order.
17
+ Pagination is with query params: page, per_page.
18
+
19
+ a request with query params:
20
+ http://localhost:8000/items?field=name&operator=eq&value=Deadpond&sort=name&order=asc&page=1&per_page=10
21
+
22
+ A response with data:
23
+
24
+ data: list of objects
25
+ meta: pagination info, filters, sort, etc.
26
+ links: pagination links
27
+
28
+ Example response:
29
+ ```
30
+ {
31
+ "data": [
32
+ {
33
+ "id": 1,
34
+ "name": "Deadpond",
35
+ "secret_name": "Dive Wilson",
36
+ "age": 28
37
+ },
38
+ ],
39
+ "meta": {
40
+ "pagination": {
41
+ "total_items": 1,
42
+ "per_page": 10,
43
+ "current_page": 1,
44
+ "total_pages": 1
45
+ },
46
+ "filters": [
47
+ {
48
+ "field": "name",
49
+ "operator": "eq",
50
+ "value": "Deadpond"
51
+ }
52
+ ],
53
+ "sort": {
54
+ "sort": "name",
55
+ "order": "asc"
56
+ }
57
+ },
58
+ "links": {
59
+ "self": "http://127.0.0.1:8000/heroes/?field=age&operator=gt&value=20&page=1&limit=2",
60
+ "first": "http://127.0.0.1:8000/heroes/?field=age&operator=gt&value=20&page=1&limit=2",
61
+ "next": "http://127.0.0.1:8000/heroes/?field=age&operator=gt&value=20&page=2&limit=2",
62
+ "prev": null
63
+ }
64
+ }
65
+ ```
66
+
67
+
68
+ Example usage in endpoint:
69
+
70
+ class Item(SQLModel, table=True):
71
+ id: int = Field(default=None, primary_key=True)
72
+ name: str
73
+ secret_name: str | None = None
74
+ age: int | None = None
75
+
76
+ @app.get("/items/", response_model=FSPResponse[Item])
77
+ def read_items(
78
+ fsp: Depends(FSPManager(Item)),
79
+ ):
80
+ query = Item.select()
81
+ return fsp.make_response(query)
82
+
83
+ also support async endpoints:
84
+
85
+ @app.get("/items/", response_model=FSPResponse[Item])
86
+ async def read_items(
87
+ fsp: Depends(FSPManager(Item)),
88
+ ):
89
+ query = Item.select()
90
+ return await fsp.make_response(query)
91
+
92
+ ## pypi package dependencies
93
+ use the following dependencies:
94
+ - uv as package manager
95
+
96
+
97
+ - black as code formatter
98
+ - isort as import formatter
99
+ - flake8 as linter
100
+ - pytest as test runner
101
+ - pytest-cov as test coverage runner
102
+ - pytest-asyncio as async test runner
103
+ - pytest-mock as mock runner
104
+ - FastAPI
105
+ - SQLModel
106
+ - pydantic
107
+
108
+
109
+ ## pypi package keywords
110
+ fastapi, SQLModel, orm, filtering, sorting, pagination
111
+
112
+ ## open source on github
113
+ Add a license file
114
+
115
+ ## Documentations
116
+ Add a README.md file with documentation
117
+ Add mkdocs documentation with mkdocs material theme
118
+ Add an implementation example
119
+
120
+ ## full test coverage
121
+ Implement unit tests using pytest
122
+
123
+ ## workflow to deploy to run all tests and package and publish to pypi
124
+ Github Actions workflow file to deploy to run all tests and package and publish to pypi
@@ -0,0 +1,178 @@
1
+ # fastapi-fsp
2
+
3
+ Filter, Sort, and Paginate (FSP) utilities for FastAPI + SQLModel.
4
+
5
+ fastapi-fsp helps you build standardized list endpoints that support:
6
+ - Filtering on arbitrary fields with rich operators (eq, ne, lt, lte, gt, gte, in, between, like/ilike, null checks, contains/starts_with/ends_with)
7
+ - Sorting by field (asc/desc)
8
+ - Pagination with page/per_page and convenient HATEOAS links
9
+
10
+ It is framework-friendly: you declare it as a FastAPI dependency and feed it a SQLModel/SQLAlchemy Select query and a Session.
11
+
12
+ ## Installation
13
+
14
+ Using uv (recommended):
15
+
16
+ ```
17
+ # create & activate virtual env with uv
18
+ uv venv
19
+ . .venv/bin/activate
20
+
21
+ # add runtime dependency
22
+ uv add fastapi-fsp
23
+ ```
24
+
25
+ Using pip:
26
+
27
+ ```
28
+ pip install fastapi-fsp
29
+ ```
30
+
31
+ ## Quick start
32
+
33
+ Below is a minimal example using FastAPI and SQLModel.
34
+
35
+ ```python
36
+ from typing import Optional
37
+ from fastapi import Depends, FastAPI
38
+ from sqlmodel import Field, SQLModel, Session, create_engine, select
39
+
40
+ from fastapi_fsp.fsp import FSPManager
41
+ from fastapi_fsp.models import PaginatedResponse
42
+
43
+ class HeroBase(SQLModel):
44
+ name: str = Field(index=True)
45
+ secret_name: str
46
+ age: Optional[int] = Field(default=None, index=True)
47
+
48
+ class Hero(HeroBase, table=True):
49
+ id: Optional[int] = Field(default=None, primary_key=True)
50
+
51
+ class HeroPublic(HeroBase):
52
+ id: int
53
+
54
+ engine = create_engine("sqlite:///database.db", connect_args={"check_same_thread": False})
55
+ SQLModel.metadata.create_all(engine)
56
+
57
+ app = FastAPI()
58
+
59
+ def get_session():
60
+ with Session(engine) as session:
61
+ yield session
62
+
63
+ @app.get("/heroes/", response_model=PaginatedResponse[HeroPublic])
64
+ def read_heroes(*, session: Session = Depends(get_session), fsp: FSPManager = Depends(FSPManager)):
65
+ query = select(Hero)
66
+ return fsp.generate_response(query, session)
67
+ ```
68
+
69
+ Run the app and query:
70
+
71
+ - Pagination: `GET /heroes/?page=1&per_page=10`
72
+ - Sorting: `GET /heroes/?sort_by=name&order=asc`
73
+ - Filtering: `GET /heroes/?field=age&operator=gte&value=21`
74
+
75
+ The response includes data, meta (pagination, filters, sorting), and links (self, first, next, prev, last).
76
+
77
+ ## Query parameters
78
+
79
+ Pagination:
80
+ - page: integer (>=1), default 1
81
+ - per_page: integer (1..100), default 10
82
+
83
+ Sorting:
84
+ - sort_by: the field name, e.g., `name`
85
+ - order: `asc` or `desc`
86
+
87
+ Filtering (repeatable sets; arrays are supported by sending multiple parameters):
88
+ - field: the field/column name, e.g., `name`
89
+ - operator: one of
90
+ - eq, ne
91
+ - lt, lte, gt, gte
92
+ - in, not_in (comma-separated values)
93
+ - between (two comma-separated values)
94
+ - like, not_like
95
+ - ilike, not_ilike (if backend supports ILIKE)
96
+ - is_null, is_not_null
97
+ - contains, starts_with, ends_with (translated to LIKE patterns)
98
+ - value: raw string value (or list-like comma-separated depending on operator)
99
+
100
+ Examples:
101
+ - `?field=name&operator=eq&value=Deadpond`
102
+ - `?field=age&operator=between&value=18,30`
103
+ - `?field=name&operator=in&value=Deadpond,Rusty-Man`
104
+ - `?field=name&operator=contains&value=man`
105
+
106
+ You can chain multiple filters by repeating the triplet:
107
+ ```
108
+ ?field=age&operator=gte&value=18&field=name&operator=ilike&value=rust
109
+ ```
110
+
111
+ ## Response model
112
+
113
+ ```
114
+ {
115
+ "data": [ ... ],
116
+ "meta": {
117
+ "pagination": {
118
+ "total_items": 42,
119
+ "per_page": 10,
120
+ "current_page": 1,
121
+ "total_pages": 5
122
+ },
123
+ "filters": [
124
+ {"field": "name", "operator": "eq", "value": "Deadpond"}
125
+ ],
126
+ "sort": {"sort_by": "name", "order": "asc"}
127
+ },
128
+ "links": {
129
+ "self": "/heroes/?page=1&per_page=10",
130
+ "first": "/heroes/?page=1&per_page=10",
131
+ "next": "/heroes/?page=2&per_page=10",
132
+ "prev": null,
133
+ "last": "/heroes/?page=5&per_page=10"
134
+ }
135
+ }
136
+ ```
137
+
138
+ ## Development
139
+
140
+ This project uses uv as the package manager.
141
+
142
+ - Create env and sync deps:
143
+ ```
144
+ uv venv
145
+ . .venv/bin/activate
146
+ uv sync --dev
147
+ ```
148
+
149
+ - Run lint and format checks:
150
+ ```
151
+ uv run ruff check .
152
+ uv run ruff format --check .
153
+ ```
154
+
155
+ - Run tests:
156
+ ```
157
+ uv run pytest -q
158
+ ```
159
+
160
+ - Build the package:
161
+ ```
162
+ uv build
163
+ ```
164
+
165
+ ## CI/CD and Releases
166
+
167
+ GitHub Actions workflows are included:
168
+ - CI (lint + tests) runs on pushes and PRs.
169
+ - Release: pushing a tag matching `v*.*.*` runs tests, builds, and publishes to PyPI using `PYPI_API_TOKEN` secret.
170
+
171
+ To release:
172
+ 1. Update the version in `pyproject.toml`.
173
+ 2. Push a tag, e.g. `git tag v0.1.1 && git push origin v0.1.1`.
174
+ 3. Ensure the repository has `PYPI_API_TOKEN` secret set (an API token from PyPI).
175
+
176
+ ## License
177
+
178
+ MIT License. See LICENSE.
@@ -0,0 +1,9 @@
1
+ """fastapi-fsp: Filter, Sort, and Paginate utilities for FastAPI + SQLModel."""
2
+
3
+ from . import models as models # noqa: F401
4
+ from .fsp import FSPManager # noqa: F401
5
+
6
+ __all__ = [
7
+ "FSPManager",
8
+ "models",
9
+ ]