paginate-fastapi 0.1.0__tar.gz → 1.0.2__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -29,6 +29,21 @@ pip install paginate-fastapi
29
29
 
30
30
  ## Quick Start
31
31
 
32
+ ### Usage as decorator
33
+ ```python
34
+ from pagination.decorator import paginate
35
+
36
+ @router.get("/", response_model=PageResponse[YourModel])
37
+ @paginate(YourModel, lambda: get_db)
38
+ async def get_items(
39
+ db: AsyncSession = Depends(get_db),
40
+ pagination: PaginationParams = Depends()
41
+ ):
42
+ ...
43
+ return { 'extra_data': 'data' }
44
+ ```
45
+
46
+ ### Usage as middleware
32
47
  ```python
33
48
  from fastapi import FastAPI, Depends
34
49
  from sqlmodel import SQLModel, Field
@@ -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
@@ -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
  }
@@ -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,6 +1,6 @@
1
1
  [project]
2
2
  name = "paginate-fastapi"
3
- version = "0.1.0"
3
+ version = "1.0.2"
4
4
  description = "A simple and efficient pagination library for FastAPI applications"
5
5
  authors = [
6
6
  {name = "Ritvik Dayal", email = "ritvikr1605@gmail.com"}