paginate-fastapi 0.1.0__py3-none-any.whl → 1.0.2__py3-none-any.whl
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.
- {paginate_fastapi-0.1.0.dist-info → paginate_fastapi-1.0.2.dist-info}/METADATA +16 -1
- paginate_fastapi-1.0.2.dist-info/RECORD +8 -0
- pagination/__init__.py +11 -1
- pagination/decorator.py +117 -0
- pagination/middleware.py +0 -2
- pagination/models.py +7 -11
- paginate_fastapi-0.1.0.dist-info/RECORD +0 -7
- {paginate_fastapi-0.1.0.dist-info → paginate_fastapi-1.0.2.dist-info}/LICENSE +0 -0
- {paginate_fastapi-0.1.0.dist-info → paginate_fastapi-1.0.2.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: paginate-fastapi
|
3
|
-
Version:
|
3
|
+
Version: 1.0.2
|
4
4
|
Summary: A simple and efficient pagination library for FastAPI applications
|
5
5
|
License: MIT
|
6
6
|
Keywords: fastapi,sqlmodel,pagination,async,filtering,sorting
|
@@ -67,6 +67,21 @@ pip install paginate-fastapi
|
|
67
67
|
|
68
68
|
## Quick Start
|
69
69
|
|
70
|
+
### Usage as decorator
|
71
|
+
```python
|
72
|
+
from pagination.decorator import paginate
|
73
|
+
|
74
|
+
@router.get("/", response_model=PageResponse[YourModel])
|
75
|
+
@paginate(YourModel, lambda: get_db)
|
76
|
+
async def get_items(
|
77
|
+
db: AsyncSession = Depends(get_db),
|
78
|
+
pagination: PaginationParams = Depends()
|
79
|
+
):
|
80
|
+
...
|
81
|
+
return { 'extra_data': 'data' }
|
82
|
+
```
|
83
|
+
|
84
|
+
### Usage as middleware
|
70
85
|
```python
|
71
86
|
from fastapi import FastAPI, Depends
|
72
87
|
from sqlmodel import SQLModel, Field
|
@@ -0,0 +1,8 @@
|
|
1
|
+
pagination/__init__.py,sha256=YzRh-vflsV-bTqz2Kh-uNYQEkZYeSYoX3k9znzdscas,1076
|
2
|
+
pagination/decorator.py,sha256=t0fjp-P6cJV05fevxmoceOyZLanR0R2mPKtblVFMHnk,4070
|
3
|
+
pagination/middleware.py,sha256=Y2AuLvmrG6nuj_BtwmuzDQdyQphEDy9gaag8oGsgVv0,6131
|
4
|
+
pagination/models.py,sha256=p01gxJlj-yDeYbLCe4Zm5M-WXh2sYJdI7XYlBfWcp6k,3387
|
5
|
+
paginate_fastapi-1.0.2.dist-info/LICENSE,sha256=04BsGfNMsEqA705-GhDsFirDIQyWUiCWJFx0h2ss6yE,1079
|
6
|
+
paginate_fastapi-1.0.2.dist-info/METADATA,sha256=7OKL7meGnK05T7CujCiBksQpveuGOhyGykLXX5HL4Bc,5739
|
7
|
+
paginate_fastapi-1.0.2.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
8
|
+
paginate_fastapi-1.0.2.dist-info/RECORD,,
|
pagination/__init__.py
CHANGED
@@ -6,9 +6,11 @@ to FastAPI applications using SQLModel.
|
|
6
6
|
|
7
7
|
Example:
|
8
8
|
from fastapi import FastAPI, Depends
|
9
|
-
from pagination import PaginationMiddleware, PaginationParams
|
9
|
+
from pagination import PaginationMiddleware, PaginationParams, paginate
|
10
10
|
|
11
11
|
app = FastAPI()
|
12
|
+
|
13
|
+
# Using middleware
|
12
14
|
paginator = PaginationMiddleware(get_session)
|
13
15
|
|
14
16
|
@app.get("/items/")
|
@@ -17,8 +19,15 @@ Example:
|
|
17
19
|
paginator: PaginationMiddleware = Depends(lambda: paginator),
|
18
20
|
):
|
19
21
|
return await paginator.paginate(Item, pagination)
|
22
|
+
|
23
|
+
# Using decorator
|
24
|
+
@app.get("/users/")
|
25
|
+
@paginate(User, get_session)
|
26
|
+
async def get_users():
|
27
|
+
return {"extra": "Additional data can be included"}
|
20
28
|
"""
|
21
29
|
|
30
|
+
from .decorator import paginate
|
22
31
|
from .middleware import PaginationMiddleware
|
23
32
|
from .models import FilterOperator, PageResponse, PaginationParams, SortOrder
|
24
33
|
|
@@ -28,4 +37,5 @@ __all__ = [
|
|
28
37
|
"PaginationMiddleware",
|
29
38
|
"PaginationParams",
|
30
39
|
"SortOrder",
|
40
|
+
"paginate",
|
31
41
|
]
|
pagination/decorator.py
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
"""
|
2
|
+
Pagination utilities.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from collections.abc import Callable
|
6
|
+
|
7
|
+
from fastapi import Depends
|
8
|
+
from sqlalchemy import func
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
10
|
+
from sqlmodel import SQLModel, select
|
11
|
+
|
12
|
+
from pagination.models import PageResponse, PaginationParams
|
13
|
+
|
14
|
+
|
15
|
+
def paginate(model: type[SQLModel], db_dependency: Callable):
|
16
|
+
"""
|
17
|
+
Decorator for FastAPI endpoints to add pagination support.
|
18
|
+
|
19
|
+
Usage:
|
20
|
+
@router.get("/", response_model=PageResponse[YourModel])
|
21
|
+
@paginate(YourModel, lambda: get_db)
|
22
|
+
async def get_items(
|
23
|
+
db: AsyncSession = Depends(get_db),
|
24
|
+
pagination: PaginationParams = Depends()
|
25
|
+
):
|
26
|
+
pass # The decorator handles everything
|
27
|
+
"""
|
28
|
+
|
29
|
+
def decorator(function: Callable) -> Callable:
|
30
|
+
def _apply_filter(query, field, operator: str, value):
|
31
|
+
"""Apply filter operation to query."""
|
32
|
+
filter_ops = {
|
33
|
+
"eq": lambda: field == value,
|
34
|
+
"neq": lambda: field != value,
|
35
|
+
"gt": lambda: field > value,
|
36
|
+
"lt": lambda: field < value,
|
37
|
+
"gte": lambda: field >= value,
|
38
|
+
"lte": lambda: field <= value,
|
39
|
+
"in": lambda: field.in_(value),
|
40
|
+
"not_in": lambda: field.not_in(value),
|
41
|
+
"contains": lambda: field.contains(value),
|
42
|
+
}
|
43
|
+
if op_func := filter_ops.get(operator):
|
44
|
+
return query.where(op_func())
|
45
|
+
return query
|
46
|
+
|
47
|
+
async def wrapper(
|
48
|
+
db: AsyncSession = Depends(db_dependency), # noqa: B008
|
49
|
+
pagination: PaginationParams = Depends(), # noqa: B008
|
50
|
+
) -> PageResponse:
|
51
|
+
# Build the base query
|
52
|
+
query = select(model)
|
53
|
+
|
54
|
+
# Apply filtering if specified
|
55
|
+
if all(
|
56
|
+
[
|
57
|
+
pagination.filter_field,
|
58
|
+
pagination.filter_operator,
|
59
|
+
pagination.filter_value,
|
60
|
+
]
|
61
|
+
):
|
62
|
+
if field := getattr(model, pagination.filter_field, None):
|
63
|
+
query = _apply_filter(
|
64
|
+
query, field, pagination.filter_operator, pagination.filter_value
|
65
|
+
)
|
66
|
+
|
67
|
+
# Apply sorting if specified
|
68
|
+
if pagination.sort_by:
|
69
|
+
field = getattr(model, pagination.sort_by, None)
|
70
|
+
if field is not None:
|
71
|
+
query = query.order_by(
|
72
|
+
field.desc() if pagination.sort_order == "desc" else field
|
73
|
+
)
|
74
|
+
|
75
|
+
# Execute count query
|
76
|
+
count_query = select(func.count()).select_from(query.subquery())
|
77
|
+
total = await db.scalar(count_query) or 0
|
78
|
+
|
79
|
+
# Calculate pages
|
80
|
+
pages = (total + pagination.page_size - 1) // pagination.page_size
|
81
|
+
|
82
|
+
# Apply pagination
|
83
|
+
offset = (pagination.page - 1) * pagination.page_size
|
84
|
+
query = query.offset(offset).limit(pagination.page_size)
|
85
|
+
|
86
|
+
# Execute main query
|
87
|
+
result = await db.execute(query)
|
88
|
+
items = result.scalars().all()
|
89
|
+
|
90
|
+
# Calculate pagination flags
|
91
|
+
has_next = pagination.page < pages
|
92
|
+
has_previous = pagination.page > 1
|
93
|
+
|
94
|
+
# Create base pagination response
|
95
|
+
pagination_response = {
|
96
|
+
"items": items,
|
97
|
+
"total": total,
|
98
|
+
"page": pagination.page,
|
99
|
+
"pages": pages,
|
100
|
+
"page_size": pagination.page_size,
|
101
|
+
"has_next": has_next,
|
102
|
+
"has_previous": has_previous,
|
103
|
+
}
|
104
|
+
|
105
|
+
# Execute the original function to get any custom data
|
106
|
+
custom_data = await function(db=db, pagination=pagination)
|
107
|
+
print(custom_data)
|
108
|
+
if isinstance(custom_data, dict):
|
109
|
+
# Merge custom data with pagination data
|
110
|
+
pagination_response.update(custom_data)
|
111
|
+
print(pagination_response)
|
112
|
+
|
113
|
+
return pagination_response
|
114
|
+
|
115
|
+
return wrapper
|
116
|
+
|
117
|
+
return decorator
|
pagination/middleware.py
CHANGED
@@ -102,8 +102,6 @@ class PaginationMiddleware:
|
|
102
102
|
FilterOperator.LT: lambda f, v: f < v,
|
103
103
|
FilterOperator.GTE: lambda f, v: f >= v,
|
104
104
|
FilterOperator.LTE: lambda f, v: f <= v,
|
105
|
-
FilterOperator.LIKE: lambda f, v: f.like(f"%{v}%"),
|
106
|
-
FilterOperator.ILIKE: lambda f, v: f.ilike(f"%{v}%"),
|
107
105
|
FilterOperator.IN: lambda f, v: f.in_(v),
|
108
106
|
FilterOperator.NOT_IN: lambda f, v: ~f.in_(v),
|
109
107
|
}
|
pagination/models.py
CHANGED
@@ -39,8 +39,6 @@ class FilterOperator(str, Enum):
|
|
39
39
|
LT: Less than (<)
|
40
40
|
GTE: Greater than or equal (>=)
|
41
41
|
LTE: Less than or equal (<=)
|
42
|
-
LIKE: SQL LIKE pattern matching
|
43
|
-
ILIKE: Case-insensitive LIKE pattern matching
|
44
42
|
IN: Value in list
|
45
43
|
NOT_IN: Value not in list
|
46
44
|
"""
|
@@ -51,8 +49,6 @@ class FilterOperator(str, Enum):
|
|
51
49
|
LT = "lt" # less than
|
52
50
|
GTE = "gte" # greater than or equal
|
53
51
|
LTE = "lte" # less than or equal
|
54
|
-
LIKE = "like" # LIKE operator
|
55
|
-
ILIKE = "ilike" # ILIKE operator
|
56
52
|
IN = "in" # IN operator
|
57
53
|
NOT_IN = "not_in" # NOT IN operator
|
58
54
|
|
@@ -74,13 +70,13 @@ class PaginationParams(BaseModel):
|
|
74
70
|
filter_value: Value to filter by
|
75
71
|
"""
|
76
72
|
|
77
|
-
page: int = Field(default=1, gt=0)
|
78
|
-
page_size: int = Field(default=10, gt=0)
|
79
|
-
sort_by: str | None = None
|
80
|
-
sort_order: SortOrder = SortOrder.ASC
|
81
|
-
filter_field: str | None = None
|
82
|
-
filter_operator: FilterOperator | None = None
|
83
|
-
filter_value: Any | None = None
|
73
|
+
page: int = Field(default=1, gt=0, description="Page number")
|
74
|
+
page_size: int = Field(default=10, gt=0, le=100, description="Items per page")
|
75
|
+
sort_by: str | None = Field(default=None, description="Field to sort by")
|
76
|
+
sort_order: SortOrder = Field(default=SortOrder.ASC, description="Sort direction (asc/desc)")
|
77
|
+
filter_field: str | None = Field(default=None, description="Field to filter on")
|
78
|
+
filter_operator: FilterOperator | None = Field(default=None, description="Filter operator")
|
79
|
+
filter_value: Any | None = Field(default=None, description="Filter value")
|
84
80
|
|
85
81
|
model_config = ConfigDict(from_attributes=True)
|
86
82
|
|
@@ -1,7 +0,0 @@
|
|
1
|
-
pagination/__init__.py,sha256=Lr6iRikRx03cnFaV6n5e9s1yDAEOracpxbx6ImOA-v4,827
|
2
|
-
pagination/middleware.py,sha256=7PAzExzv55gBrZQORuJJORd1VTas8b-Oed-jm3eLquA,6261
|
3
|
-
pagination/models.py,sha256=nSASwuG-rlrti2RWI4Oj6epDiYGV0qCQmcijqGfMRbU,3248
|
4
|
-
paginate_fastapi-0.1.0.dist-info/LICENSE,sha256=04BsGfNMsEqA705-GhDsFirDIQyWUiCWJFx0h2ss6yE,1079
|
5
|
-
paginate_fastapi-0.1.0.dist-info/METADATA,sha256=TMdNZICsznUU2JXVHSeOeYNg_KTLAqP0TFXnVRR3QeU,5387
|
6
|
-
paginate_fastapi-0.1.0.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
|
7
|
-
paginate_fastapi-0.1.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|