Source code for flask_more_smorest.crud.pagination

from functools import wraps
from typing import Any

from flask_smorest.pagination import PaginationParameters
from werkzeug.exceptions import BadRequest


[docs] class CRUDPaginationMixin: """Mixin class to add custom pagination support to CRUDBlueprint."""
[docs] def paginate( self, pager: Any = None, *, page: int | None = None, page_size: int | None = None, max_page_size: int | None = None, ) -> Any: """Decorator adding pagination to the endpoint. Overrides flask-smorest's paginate to allow compatibility with argument schemas that already include pagination parameters. Allows defining multiple @bp.arguments decorators without conflict. If pager is None (default), we assume manual handling compatible with filter schemas. """ # If a pager class/instance is provided, use standard behavior if pager is not None: return super().paginate(pager, page=page, page_size=page_size, max_page_size=max_page_size) # type: ignore # Custom behavior for pager=None (manual pagination handling) def decorator(func: Any) -> Any: @wraps(func) def wrapper(*args: Any, **kwargs: Any) -> Any: # Try to get params from validated 'filters' dict filters = kwargs.get("filters") # If not in kwargs, check args (MethodView: args[0]=self, args[1]=filters) if filters is None and len(args) > 1: filters = args[1] # Fallback to empty dict if not found if filters is None: filters = {} def _coerce_int(name: str, value: Any, default: int, *, allow_zero: bool = False) -> int: candidate = value if value is not None else default if candidate is None: raise BadRequest(f"Missing pagination parameter: {name}") try: int_value = int(candidate) except (TypeError, ValueError) as exc: raise BadRequest( f"{name} must be a non-negative integer" if allow_zero else f"{name} must be a positive integer" ) from exc min_val = 0 if allow_zero else 1 if int_value < min_val: raise BadRequest( f"{name} must be a non-negative integer" if allow_zero else f"{name} must be a positive integer" ) return int_value # Extract values with fallbacks and validation raw_page = filters.get("page") if raw_page is None: raw_page = page p_val = _coerce_int("page", raw_page, default=1) raw_page_size = filters.get("page_size") if raw_page_size is None: raw_page_size = page_size p_size_default = page_size if page_size is not None else 20 p_size_val = _coerce_int("page_size", raw_page_size, default=p_size_default, allow_zero=True) # Create parameters object pagination_parameters = PaginationParameters(page=p_val, page_size=p_size_val) # Inject into kwargs kwargs["pagination_parameters"] = pagination_parameters # Remove from filters so application logic doesn't see them as filters if "page" in filters: del filters["page"] if "page_size" in filters: del filters["page_size"] # Execute decorated function result = func(*args, **kwargs) # Handle response (could be value or tuple) status = 200 headers = {} if isinstance(result, tuple): n = len(result) if n == 3: result, status, headers = result elif n == 2: result, status = result # Set pagination metadata if getattr(self, "PAGINATION_HEADER_NAME", None) is not None: metadata_parameters = pagination_parameters if pagination_parameters.page_size == 0: item_count = pagination_parameters.item_count or 0 safe_page_size = max(item_count, 1) metadata_parameters = PaginationParameters( page=1, page_size=safe_page_size, ) metadata_parameters.item_count = item_count result, headers = self._set_pagination_metadata( # type: ignore metadata_parameters, result, headers ) return result, status, headers return wrapper return decorator