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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: paginate-fastapi
3
- Version: 0.1.0
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
  ]
@@ -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,,