/ src / solace_agent_mesh / shared / api / pagination.py
pagination.py
  1  """
  2  Pagination utilities for API responses.
  3  
  4  Provides standard pagination patterns for consistent API responses across the application.
  5  
  6  Default pagination settings:
  7  - Page number: 1
  8  - Page size: 20
  9  - Max page size: 100
 10  """
 11  
 12  from pydantic import BaseModel, Field
 13  from typing import TypeVar, Generic
 14  
 15  T = TypeVar("T")
 16  
 17  DEFAULT_PAGE_NUMBER = 1
 18  DEFAULT_PAGE_SIZE = 20
 19  MAX_PAGE_SIZE = 100
 20  
 21  
 22  class PaginationParams(BaseModel):
 23      """
 24      Request parameters for pagination with sensible defaults.
 25  
 26      Defaults:
 27      - page_number: 1
 28      - page_size: 20
 29      """
 30      page_number: int = Field(default=DEFAULT_PAGE_NUMBER, ge=1, alias="pageNumber")
 31      page_size: int = Field(default=DEFAULT_PAGE_SIZE, ge=1, le=MAX_PAGE_SIZE, alias="pageSize")
 32  
 33      @property
 34      def offset(self) -> int:
 35          """Calculate the offset for database queries."""
 36          return (self.page_number - 1) * self.page_size
 37  
 38      model_config = {"populate_by_name": True}
 39  
 40  
 41  def get_pagination_or_default(pagination: PaginationParams | None = None) -> PaginationParams:
 42      """
 43      Get pagination parameters or return defaults if None.
 44  
 45      This helper ensures all paginated endpoints use the same defaults:
 46      - page_number: 1
 47      - page_size: 20
 48  
 49      Args:
 50          pagination: Optional pagination parameters
 51  
 52      Returns:
 53          PaginationParams with defaults if None provided
 54  
 55      Example:
 56          pagination = get_pagination_or_default(request_pagination)
 57          # Always returns valid PaginationParams with defaults if None
 58      """
 59      if pagination is None:
 60          return PaginationParams()
 61      return pagination
 62  
 63  
 64  class PaginationMeta(BaseModel):
 65      """Pagination metadata for API responses."""
 66      page_number: int = Field(..., alias="pageNumber")
 67      count: int
 68      page_size: int = Field(..., alias="pageSize")
 69      next_page: int | None = Field(..., alias="nextPage")
 70      total_pages: int = Field(..., alias="totalPages")
 71  
 72      model_config = {"populate_by_name": True}
 73  
 74  
 75  class Meta(BaseModel):
 76      """Metadata container for API responses."""
 77      pagination: PaginationMeta
 78  
 79  
 80  class PaginatedResponse(BaseModel, Generic[T]):
 81      """Generic paginated response with data and metadata."""
 82      data: list[T]
 83      meta: Meta
 84  
 85      @classmethod
 86      def create(
 87          cls, data: list[T], total_count: int, pagination: PaginationParams
 88      ) -> "PaginatedResponse[T]":
 89          """
 90          Create a paginated response from data and pagination parameters.
 91  
 92          Args:
 93              data: List of items for current page
 94              total_count: Total number of items across all pages
 95              pagination: Pagination parameters used for the request
 96  
 97          Returns:
 98              PaginatedResponse with data and calculated metadata
 99          """
100          total_pages = (total_count + pagination.page_size - 1) // pagination.page_size
101          next_page = pagination.page_number + 1 if pagination.page_number < total_pages else None
102  
103          pagination_meta = PaginationMeta(
104              page_number=pagination.page_number,
105              count=total_count,
106              page_size=pagination.page_size,
107              next_page=next_page,
108              total_pages=total_pages,
109          )
110  
111          return cls(
112              data=data,
113              meta=Meta(pagination=pagination_meta)
114          )
115  
116      model_config = {"populate_by_name": True}
117  
118  
119  class DataResponse(BaseModel, Generic[T]):
120      """Simple data response wrapper."""
121      data: T
122  
123      @classmethod
124      def create(cls, data: T) -> "DataResponse[T]":
125          """Create a data response from data."""
126          return cls(data=data)
127  
128  
129  __all__ = [
130      "PaginationParams",
131      "PaginationMeta",
132      "PaginatedResponse",
133      "DataResponse",
134      "get_pagination_or_default",
135      "DEFAULT_PAGE_NUMBER",
136      "DEFAULT_PAGE_SIZE",
137      "MAX_PAGE_SIZE",
138  ]