### Install Basic Dependencies Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Install only the basic dependencies required for development using uv. ```shell uv sync --dev ``` -------------------------------- ### Install fastapi-pagination Source: https://github.com/uriyyo/fastapi-pagination/blob/main/README.md Install the library using pip. ```bash pip install fastapi-pagination ``` -------------------------------- ### Install Documentation Dependencies Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Install dependencies specifically for building and serving the documentation using uv. ```shell uv sync --group docs ``` -------------------------------- ### Install fastapi-pagination Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Install the library using pip. ORM-specific extras can be installed with additional packages. ```bash pip install fastapi-pagination ``` ```bash pip install fastapi-pagination[sqlalchemy] ``` ```bash pip install fastapi-pagination[sqlmodel] ``` ```bash pip install fastapi-pagination[tortoise] ``` ```bash pip install fastapi-pagination[beanie] ``` ```bash pip install fastapi-pagination[motor] ``` ```bash pip install fastapi-pagination[django] ``` -------------------------------- ### Install Development Dependencies Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Install all development dependencies, including extras, using uv. ```shell uv sync --dev --all-extras ``` -------------------------------- ### SQLAlchemy Pagination with unwrap_mode Examples Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/integrations/sqlalchemy/paginate.md Demonstrates various `unwrap_mode` settings for SQLAlchemy pagination. Each example shows how different modes affect the structure of the paginated results when selecting a single column. ```python from pydantic import BaseModel from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column from fastapi_pagination import set_params, set_page, Page, Params from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() class UserName(BaseModel): name: str with Session(engine) as session: Base.metadata.create_all(session.bind) session.add(User(name="John")) session.commit() set_params(Params(size=10)) print('unwrap_mode="auto"') set_page(Page[UserName]) page = paginate( session, select(User.name), unwrap_mode="auto", ) print(page.model_dump_json(indent=4)) print() print('unwrap_mode="legacy"') set_page(Page[str]) page = paginate( session, select(User.name), unwrap_mode="legacy", ) print(page.model_dump_json(indent=4)) print() print('unwrap_mode="unwrap"') set_page(Page[str]) page = paginate( session, select(User.name), unwrap_mode="unwrap", ) print(page.model_dump_json(indent=4)) print() print('unwrap_mode="no-unwrap"') set_page(Page[UserName]) page = paginate( session, select(User.name), unwrap_mode="no-unwrap", ) print(page.model_dump_json(indent=4)) print() ``` -------------------------------- ### Install Pre-commit Hooks Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Install pre-commit hooks to automatically run checks before each commit. ```shell uv run pre-commit install ``` -------------------------------- ### Page-Based Pagination Example Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/pagination/techniques.md Demonstrates setting page parameters and paginating a list using page-based strategy. Ensure Page and Params are imported and configured. ```python from fastapi_pagination import paginate, set_page, set_params from fastapi_pagination.default import Page, Params set_page(Page[int]) set_params(Params(page=2, size=10)) page = paginate([*range(100)]) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### Limit-Offset Pagination Example Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/pagination/techniques.md Shows how to configure and use limit-offset pagination for retrieving data. Imports LimitOffsetPage and LimitOffsetParams for this strategy. ```python from fastapi_pagination import paginate, set_page, set_params from fastapi_pagination.limit_offset import LimitOffsetParams, LimitOffsetPage set_page(LimitOffsetPage[int]) set_params(LimitOffsetParams(limit=10, offset=5)) page = paginate([*range(100)]) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### Paginate Data with FastAPI and Dependencies Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_user_guide/first_steps.md This example shows the equivalent functionality using vanilla FastAPI with dependencies. It utilizes `Params` and `Depends` for pagination parameters and `set_page` to configure the pagination response type. ```python from fastapi import FastAPI, Depends from fastapi_pagination import Page, Params, paginate, set_page app = FastAPI() # req: GET /users?page=2&size=10 @app.get("/users") async def get_users(params: Params = Depends()) -> Page[int]: set_page(Page[int]) return paginate([*range(100)], params) ``` -------------------------------- ### Clone the Repository Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Clone the fastapi-pagination repository to your local machine to start contributing. ```shell git clone https://github.com/uriyyo/fastapi-pagination ``` -------------------------------- ### Get and Set Item Transformers Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Demonstrates how to set a global item transformer and retrieve it using `resolve_items_transformer`. Also shows how to use a transformer within a context manager and pass a specific transformer instance. ```python from dataclasses import dataclass from fastapi_pagination.api import resolve_items_transformer, set_items_transformer @dataclass class Transformer: name: str multiplier: int def __call__(self, items: list[int]) -> list[int]: return [x * self.multiplier for x in items] set_items_transformer(Transformer("double", 2)) print(resolve_items_transformer()) with set_items_transformer(Transformer("triple", 3)): print(resolve_items_transformer()) print(resolve_items_transformer(Transformer("quadruple", 4))) ``` -------------------------------- ### SQLAlchemy Cursor Pagination Example Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/integrations/sqlalchemy/cursor_pagination.md Demonstrates setting up a SQLAlchemy model, creating an engine and session, populating data, and then paginating the data using cursor-based pagination. It shows how to set the page and params, paginate a query, and retrieve the next page cursor. ```python from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column from fastapi_pagination import set_params, set_page from fastapi_pagination.cursor import CursorPage, CursorParams from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() with Session(engine) as session: Base.metadata.create_all(session.bind) session.add_all([User(name=f"User-{i}") for i in range(1_000)]) session.commit() set_page(CursorPage[User]) cursor = None for i in range(1, 6): print(f"Page {i}") set_params(CursorParams(size=2, cursor=cursor)) page = paginate(session, select(User).order_by(User.id)) cursor = page.next_page print(page.model_dump_json(indent=4)) print() ``` -------------------------------- ### Implementing UseOptionalParams for Optional Parameters Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_optional_params.md This example demonstrates how to use the UseOptionalParams customizer to create a `CustomPage` type. This allows `paginate` to accept requests with missing parameters, defaulting to selecting all items. Ensure `add_pagination` is called on your FastAPI app. ```python from typing import TypeVar from fastapi import FastAPI from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import CustomizedPage, UseOptionalParams app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseOptionalParams() ] # req: GET /nums?size=5&page=1 # req: GET /nums @app.get("/nums") async def get_nums() -> CustomPage[int]: return paginate(range(100)) ``` -------------------------------- ### Paginate SQLAlchemy Relationships with Different Load Types Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/integrations/sqlalchemy/relationships.md This example shows how to paginate SQLAlchemy User objects and their associated Mark objects using joinedload, subqueryload, and selectinload. It sets up a database, populates it with data, and then demonstrates pagination with each loading strategy. ```python from __future__ import annotations from typing import Any from pydantic import BaseModel from sqlalchemy import ForeignKey, create_engine, select from sqlalchemy.orm import ( DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column, relationship, joinedload, selectinload, subqueryload, ) from fastapi_pagination import Page, set_params, set_page from fastapi_pagination.customization import CustomizedPage, UseIncludeTotal from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() age: Mapped[int] = mapped_column() marks: Mapped[list[Mark]] = relationship(back_populates="user", default_factory=list) class Mark(Base): __tablename__ = "marks" id: Mapped[int] = mapped_column(default=None, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey(User.id), default=None) mark: Mapped[int] = mapped_column() user: Mapped[User] = relationship(back_populates="marks", default=None) class MarkOut(BaseModel): id: int mark: int class UserOut(BaseModel): id: int name: str age: int marks: list[MarkOut] with Session(engine) as session: Base.metadata.create_all(session.bind) session.add_all( [ User( name="John", age=25, marks=[ Mark(mark=10), Mark(mark=20), ], ), User( name="Jane", age=30, marks=[ Mark(mark=30), Mark(mark=40), Mark(mark=50), ], ), User( name="Bob", age=20, marks=[ Mark(mark=60), ], ), ], ) session.commit() CustomPage = CustomizedPage[Page[UserOut], UseIncludeTotal(False)] set_page(CustomPage) set_params(CustomPage.__params_type__(size=1, page=2)) def run_pagination(type_: str, load: Any) -> None: print(f"relationship load type: {type_}") page = paginate(session, select(User).options(load(User.marks))) print(page.model_dump_json(indent=4)) print() run_pagination("joinedload", joinedload) run_pagination("subqueryload", subqueryload) run_pagination("selectinload", selectinload) ``` -------------------------------- ### Custom Params Model for Limit-Offset Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_user_guide/own_page_model.md Create a custom `Params` model by inheriting from `AbstractParams` and implementing `to_raw_params`. This example uses `RawParams` for limit-offset pagination and skips total calculation. ```python from typing import Annotated from fastapi import Query from fastapi_pagination.bases import AbstractParams, RawParams class MyParams(AbstractParams): pageNumber: Annotated[int, Query(..., ge=1)] pageSize: Annotated[int, Query(..., ge=1, le=100)] def to_raw_params(self) -> RawParams: return RawParams( limit=self.pageSize, offset=(self.pageNumber - 1) * self.pageSize, include_total=False, # skip total calculation ) ``` -------------------------------- ### Use Pydantic V1 Customizer for FastAPI Pagination Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_pydantic_v1.md This example demonstrates how to use the `UsePydanticV1` customizer to ensure Pydantic v1 compatibility. It defines a FastAPI application, adds pagination, and creates a custom page type that leverages `UsePydanticV1` for Pydantic v1 model conversion. The `get_items` endpoint returns paginated items using this custom page type. ```python from random import random from typing import TypeVar from fastapi import FastAPI from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import CustomizedPage, UsePydanticV1 from pydantic.v1 import BaseModel class Item(BaseModel): id: int score: float = 0.0 app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UsePydanticV1(), ] # req: GET /items?size=5&page=2 @app.get("/items") async def get_items() -> CustomPage[Item]: return paginate( [Item(id=id_, score=random()) for id_ in range(1_000)] ) ``` -------------------------------- ### Combine Customizers for CustomizedPage Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizer_combinations.md Use `CustomizedPage` to chain multiple customizers like `UseName`, `UseIncludeTotal`, `UseExcludedFields`, `UseParamsFields`, and `UseFieldsAliases` to create a highly specific pagination configuration. This example defines a `CustomPage` that excludes total and page count, renames parameters, and sets aliases for response fields. ```python from typing import TypeVar from fastapi import FastAPI, Query from fastapi_pagination import Page, paginate, add_pagination from fastapi_pagination.customization import ( CustomizedPage, UseParamsFields, UseFieldsAliases, UseExcludedFields, UseIncludeTotal, UseName, ) app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseName("CustomPage"), UseIncludeTotal(False), UseExcludedFields("total", "pages"), UseParamsFields( size=Query(10, ge=1, le=1000, alias="pageSize"), page=Query(1, ge=1, alias="pageNumber"), ), UseFieldsAliases( items="content", size="pageSize", page="pageNumber", ), ] # req: GET /nums @app.get("/nums") async def get_nums() -> CustomPage[int]: return paginate(range(1_000)) ``` -------------------------------- ### Run All Pre-commit Hooks Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Execute all pre-commit hooks to ensure code quality and style consistency before committing. ```shell uv run pre-commit run --all-files ``` -------------------------------- ### Run Full Test Suite Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Execute the entire test suite, including unit and integration tests. ```shell ./scripts/ci-test.sh ``` -------------------------------- ### Get Current Pagination Items Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Retrieves the current pagination items within a specific context. This is particularly useful when defining custom page response models that need access to the paginated items. ```python from __future__ import annotations from typing import TypeVar, Generic, Sequence, Optional, Any from fastapi import FastAPI from fastapi_pagination import Params, add_pagination, paginate from fastapi_pagination.bases import AbstractParams, AbstractPage from fastapi_pagination.api import pagination_items from pydantic import BaseModel, Field T = TypeVar("T") class InnerModel(BaseModel, Generic[T]): results: list[T] = Field(default_factory=pagination_items) class MyPageResponse(AbstractPage[T]): inner: InnerModel[T] __params_type__ = Params @classmethod def create( cls, items: Sequence[T], params: AbstractParams, *, total: Optional[int] = None, **kwargs: Any, ) -> MyPageResponse[T]: return cls(inner={}) app = FastAPI() add_pagination(app) # req: GET /nums?page=2&size=10 @app.get("/nums") async def route() -> MyPageResponse[int]: return paginate([*range(100)]) ``` -------------------------------- ### Run Integration Tests Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Execute integration tests, which require PostgreSQL, MongoDB, and Cassandra to be running. ```shell uv run pytest tests/ext ``` -------------------------------- ### Define Page-Based Params Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_user_guide/elements.md Define a custom Params class for page-based pagination using Pydantic and FastAPI's Query parameters. This example sets default values and validation constraints for page and size. ```python from pydantic import BaseModel from fastapi import Query class Params(BaseModel): page: int = Query(1, ge=1) size: int = Query(50, ge=1, le=100) ``` -------------------------------- ### Increase Default Page Size with CustomizedPage Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/general.md Use `CustomizedPage` to define a custom page structure that increases the default size parameter. This example sets the default size to 100 and enforces limits between 1 and 1000. ```python from typing import TypeVar from fastapi import FastAPI, Query from fastapi_pagination import Page, paginate, add_pagination from fastapi_pagination.customization import CustomizedPage, UseParamsFields app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseParamsFields( size=Query(100, ge=1, le=1000), ), ] # req: GET /nums @app.get("/nums") async def get_nums() -> CustomPage[int]: return paginate(range(1_000)) ``` -------------------------------- ### Use UseQuotedCursor to Disable Cursor Quoting Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_quoted_cursor.md This example demonstrates how to disable cursor quoting by setting `UseQuotedCursor(False)` when customizing the page type. This is useful when you need the cursor to be a raw value rather than a quoted string. ```python from typing import TypeVar from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column from fastapi_pagination import set_params, set_page from fastapi_pagination.customization import CustomizedPage, UseQuotedCursor from fastapi_pagination.cursor import CursorPage, CursorParams from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() with Session(engine) as session: Base.metadata.create_all(session.bind) session.add_all([User(name=f"User-{i}") for i in range(1_000)]) session.commit() T = TypeVar("T") CursorPageNotQuotedCursor = CustomizedPage[ CursorPage[T], UseQuotedCursor(False), ] set_page(CursorPageNotQuotedCursor[User]) cursor = None for i in range(1, 6): print(f"Page {i}") set_params(CursorParams(size=2, cursor=cursor)) page = paginate(session, select(User).order_by(User.id)) cursor = page.next_page print(page.model_dump_json(indent=4)) print() ``` -------------------------------- ### Run Tests with Coverage Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Run tests and generate a code coverage report. ```shell uv run pytest tests --cov=fastapi_pagination ``` -------------------------------- ### Define Custom Page with Field Aliases Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_fields_aliases.md Define a custom Page class that uses UseFieldAliases to rename serialization fields. This example renames 'items' to 'content', 'size' to 'pageSize', 'page' to 'pageNumber', 'pages' to 'totalPages', and 'total' to 'totalElements'. ```python from typing import TypeVar from fastapi import FastAPI from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import CustomizedPage, UseFieldsAliases app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseFieldsAliases( items="content", size="pageSize", page="pageNumber", pages="totalPages", total="totalElements", ), ] # req: GET /nums?size=5&page=2 @app.get("/nums") async def get_nums() -> CustomPage[int]: return paginate(range(1_000)) ``` -------------------------------- ### Define Custom Page with UseModule Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_module.md Use `UseModule` to specify the module for a custom page class. This is useful when `fastapi-pagination` has trouble resolving the module automatically. The example demonstrates how to set `my_module` as the module and prints the resolved module name. ```python from typing import TypeVar from fastapi_pagination import Page from fastapi_pagination.customization import CustomizedPage, UseModule T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseModule("my_module"), ] print(CustomPage.__module__) ``` -------------------------------- ### Use CustomizedPage with UseOptionalParams Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/v0_13_migration.md The `OptionalParams` and `OptionalLimitOffsetParams` classes have been removed. Use `CustomizedPage` with `UseOptionalParams` for optional parameters. ```python from typing import TypeVar from fastapi_pagination import Page from fastapi_pagination.customization import CustomizedPage, UseOptionalParams T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseOptionalParams(), ] ``` -------------------------------- ### CursorPage with SQLAlchemy Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Demonstrates cursor-based pagination using `CursorPage` and `CursorParams` with SQLAlchemy. This is efficient for large datasets and requires a stable sort order. ```APIDOC ## `CursorPage` — Cursor-based pagination Use `CursorPage` and `CursorParams` for efficient pagination over large datasets. The cursor is returned in `next_page` / `previous_page` fields of the response and passed back as the `cursor` query parameter for the next request. Requires a stable sort order (e.g., `order_by(User.id)`). ```python from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from fastapi_pagination import set_page, set_params from fastapi_pagination.cursor import CursorPage, CursorParams from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() with Session(engine) as session: Base.metadata.create_all(session.bind) session.add_all([User(name=f"User-{i}") for i in range(20)]) session.commit() set_page(CursorPage[User]) cursor = None for page_num in range(1, 4): set_params(CursorParams(size=5, cursor=cursor)) page = paginate(session, select(User).order_by(User.id)) cursor = page.next_page # pass to next request print(f"Page {page_num}: {[u.name for u in page.items]}") # Page 1: ['User-0', 'User-1', 'User-2', 'User-3', 'User-4'] # Page 2: ['User-5', 'User-6', 'User-7', 'User-8', 'User-9'] # Page 3: ['User-10', 'User-11', 'User-12', 'User-13', 'User-14'] ``` ``` -------------------------------- ### Run Unit Tests Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Execute only the unit tests. ```shell uv run pytest tests --unit-tests ``` -------------------------------- ### Customize Page Params Fields with UseParamsFields Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/customization/customizers/use_params_fields.md Use `UseParamsFields` to change the default names and values of page parameters like `size` and `page`. This example renames `size` to `pageSize` and `page` to `pageNumber`, and sets a default `pageSize` of 5 with constraints. ```python from typing import TypeVar from fastapi import FastAPI, Query from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import CustomizedPage, UseParamsFields app = FastAPI() add_pagination(app) T = TypeVar("T") CustomPage = CustomizedPage[ Page[T], UseParamsFields( size=Query(5, ge=1, le=100, alias="pageSize"), page=Query(1, ge=1, alias="pageNumber"), ), ] # req: GET /nums?pageSize=5&pageNumber=2 @app.get("/nums") async def get_nums() -> CustomPage[int]: return paginate(range(1_000)) ``` -------------------------------- ### Cursor-Based Pagination with SQLAlchemy Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/pagination/techniques.md Illustrates cursor-based pagination integrated with SQLAlchemy for efficient data retrieval. Requires Pydantic, SQLAlchemy, and fastapi-pagination extensions. ```python from pydantic import BaseModel from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from fastapi_pagination import set_params, set_page from fastapi_pagination.cursor import CursorPage, CursorParams from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() age: Mapped[int] = mapped_column() class UserOut(BaseModel): id: int name: str age: int with engine.begin() as conn: Base.metadata.drop_all(conn) Base.metadata.create_all(conn) with Session(engine) as session: session.add_all( [ User(name="John", age=25), User(name="Jane", age=30), User(name="Bob", age=20), ], ) session.commit() set_page(CursorPage[UserOut]) set_params(CursorParams(size=10)) page = paginate(session, select(User).order_by(User.id)) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### Rename async paginate to apaginate Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/v0_13_migration.md Starting from v0.13.0, the async `paginate` function is renamed to `apaginate`. Old code will still work, but updating is recommended for async operations. Support for `async` calls for `paginate` will be removed in the next major version. -------------------------------- ### Limit-offset pagination Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Switch to limit-offset pagination using `LimitOffsetPage` and `LimitOffsetParams`. Query parameters become `limit` and `offset`. ```python from fastapi import FastAPI from fastapi_pagination import add_pagination, paginate from fastapi_pagination.limit_offset import LimitOffsetPage, LimitOffsetParams from fastapi_pagination import set_page, set_params app = FastAPI() add_pagination(app) # GET /items?limit=10&offset=20 @app.get("/items") async def get_items() -> LimitOffsetPage[int]: return paginate(range(200)) # Standalone usage: set_page(LimitOffsetPage[int]) set_params(LimitOffsetParams(limit=10, offset=5)) page = paginate(list(range(100))) print(page.model_dump_json(indent=4)) # {"items": [5, 6, 7, ...14], "total": 100, "limit": 10, "offset": 5} ``` -------------------------------- ### FastAPI Pagination with Navigation Links Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Include navigation links (first, last, next, previous) in the response metadata by importing `Page` or `LimitOffsetPage` from `fastapi_pagination.links`. ```python from fastapi import FastAPI from fastapi_pagination import add_pagination, paginate from fastapi_pagination.links import Page, LimitOffsetPage app = FastAPI() add_pagination(app) # GET /nums?page=2&size=10 @app.get("/nums") async def get_nums() -> Page[int]: return paginate(range(200)) # Response includes: # "links": {"first": "...?page=1&size=10", "last": "...?page=20&size=10", # "next": "...?page=3&size=10", "prev": "...?page=1&size=10"} # GET /nums-lo?offset=10&limit=5 @app.get("/nums-lo") async def get_nums_lo() -> LimitOffsetPage[int]: return paginate(range(200)) ``` -------------------------------- ### Customizing Page Shape with CustomizedPage Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Use `CustomizedPage` to modify input parameter fields, output field names, aliases, excluded fields, and additional fields without subclassing. This example creates an API-style page with custom parameter names and aliased output fields. ```python from typing import TypeVar from fastapi import FastAPI, Query from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import ( CustomizedPage, UseParamsFields, UseFieldsAliases, UseExcludedFields, UseIncludeTotal, UseName, ) app = FastAPI() add_pagination(app) T = TypeVar("T") # API-style page: custom param names, aliased output fields, no total/pages CustomPage = CustomizedPage[ Page[T], UseName("ApiPage"), UseIncludeTotal(False), UseExcludedFields("total", "pages"), UseParamsFields( size=Query(10, ge=1, le=1000, alias="pageSize"), page=Query(1, ge=1, alias="pageNumber"), ), UseFieldsAliases( items="content", size="pageSize", page="pageNumber", ), ] # GET /data?pageSize=10&pageNumber=2 @app.get("/data") async def get_data() -> CustomPage[int]: return paginate(range(1_000)) ``` -------------------------------- ### Use create_paginate_query from sqlalchemy extension Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/v0_13_migration.md The `paginate_query` function has been removed from `fastapi_pagination.ext.sqlalchemy`. Use the `create_paginate_query` function instead. ```python from fastapi_pagination.ext.sqlalchemy import create_paginate_query ``` -------------------------------- ### Apply Async Item Transformer Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Demonstrates applying an asynchronous item transformer. This is useful for transformations that involve I/O operations or other asynchronous tasks. ```python from fastapi_pagination.api import ( set_items_transformer, apply_items_transformer, ) async def transformer_double(items: list[int]) -> list[int]: return [x * 2 for x in items] set_items_transformer(transformer_double) r1 = await apply_items_transformer([1, 2, 3], async_=True) print(f"{r1=}") async def transformer_triple(items: list[int]) -> list[int]: return [x * 3 for x in items] r2 = await apply_items_transformer( [1, 2, 3], transformer=transformer_triple, async_=True, ) print(f"{r2=}") ``` -------------------------------- ### Run Tests Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/contributing.md Execute the test suite using pytest. ```shell uv run pytest tests ``` -------------------------------- ### Standard Pagination with SQLAlchemy Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Integrates with SQLAlchemy sync and async sessions. Accepts a `Session` or `AsyncSession` and a `select()` statement, executing COUNT and data queries efficiently. Use `add_pagination` to integrate with FastAPI. ```python from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from fastapi import FastAPI, Depends from sqlalchemy.orm import Session as DBSession from fastapi_pagination import Page, add_pagination from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///./test.db") class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() age: Mapped[int] = mapped_column() Base.metadata.create_all(engine) app = FastAPI() add_pagination(app) def get_db(): with Session(engine) as session: yield session # GET /users?page=1&size=20 @app.get("/users") def list_users(db: DBSession = Depends(get_db)) -> Page[User]: return paginate(db, select(User).order_by(User.id)) ``` -------------------------------- ### Create Page Instance with Custom Data Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Generates a new page instance using the current page type or a default. Requires items, parameters, and optionally the total count. Useful for manual page creation. ```python from fastapi_pagination import Page, Params from fastapi_pagination.api import set_page, create_page set_page(Page[int]) page = create_page([1, 2, 3], params=Params(page=1, size=3), total=1_000_000) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### Page.create class method signature change Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/v0_13_migration.md The `Page.create` class method now accepts `total` only as a keyword argument. This change was made because `total` is no longer a required argument and can be omitted in some cases. ```python @classmethod def create( cls, items: Sequence[T], params: AbstractParams, **kwargs: Any, ) -> Self: pass ``` -------------------------------- ### Optional Parameters with UseOptionalParams Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Make all pagination parameters optional using `UseOptionalParams`. When no parameters are provided, all items are returned. This is useful for endpoints that can optionally be filtered or paginated. ```python from typing import TypeVar from fastapi import FastAPI from fastapi_pagination import Page, add_pagination, paginate from fastapi_pagination.customization import CustomizedPage, UseOptionalParams app = FastAPI() add_pagination(app) T = TypeVar("T") UnlimitedPage = CustomizedPage[Page[T], UseOptionalParams()] # GET /all → returns all items # GET /all?size=5 → returns first 5 items @app.get("/all") async def get_all() -> UnlimitedPage[int]: return paginate(range(100)) ``` -------------------------------- ### Import Pagination Params Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_user_guide/elements.md Import the necessary Params classes for different pagination strategies: page-based, cursor-based, and limit-offset. ```python from fastapi_pagination.default import Params from fastapi_pagination.cursor import CursorParams from fastapi_pagination.limit_offset import LimitOffsetParams ``` -------------------------------- ### SQLAlchemy Relationship Loading with FastAPI Pagination Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Demonstrates how to paginate SQLAlchemy queries with eager loading using `selectinload`. Set `unique=True` or `UseIncludeTotal(False)` to prevent double-counting rows when joins are involved. ```python from sqlalchemy import ForeignKey, create_engine, select from sqlalchemy.orm import ( DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column, relationship, joinedload, selectinload, ) from pydantic import BaseModel from fastapi_pagination import Page, set_page, set_params from fastapi_pagination.customization import CustomizedPage, UseIncludeTotal from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() posts: Mapped[list["Post"]] = relationship(back_populates="user", default_factory=list) class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(default=None, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey(User.id), default=None) title: Mapped[str] = mapped_column() user: Mapped[User] = relationship(back_populates="posts", default=None) class PostOut(BaseModel): id: int title: str class UserOut(BaseModel): id: int name: str posts: list[PostOut] with Session(engine) as session: Base.metadata.create_all(session.bind) session.add(User(name="Alice", posts=[Post(title="Hello"), Post(title="World")])) session.commit() CustomPage = CustomizedPage[Page[UserOut], UseIncludeTotal(False)] set_page(CustomPage) set_params(CustomPage.__params_type__(size=10, page=1)) with Session(engine) as session: page = paginate(session, select(User).options(selectinload(User.posts))) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### Basic Pagination with In-Memory Data Source: https://github.com/uriyyo/fastapi-pagination/blob/main/README.md Use `Page` as a return type and `paginate` function for in-memory data. Ensure `add_pagination` is called on your FastAPI app. ```python from fastapi import FastAPI from pydantic import BaseModel, Field from fastapi_pagination import Page, add_pagination, paginate app = FastAPI() add_pagination(app) class UserOut(BaseModel): name: str = Field(..., example="Steve") surname: str = Field(..., example="Rogers") users = [ UserOut(name="Steve", surname="Rogers"), # ... ] @app.get("/users") async def get_users() -> Page[UserOut]: return paginate(users) ``` -------------------------------- ### Cursor-Based Pagination with SQLAlchemy Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Use `CursorPage` and `CursorParams` for efficient pagination over large datasets. Requires a stable sort order. The cursor is returned in `next_page` / `previous_page` fields and passed back as the `cursor` query parameter. ```python from sqlalchemy import create_engine, select from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column from fastapi_pagination import set_page, set_params from fastapi_pagination.cursor import CursorPage, CursorParams from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column() with Session(engine) as session: Base.metadata.create_all(session.bind) session.add_all([User(name=f"User-{i}") for i in range(20)]) session.commit() set_page(CursorPage[User]) cursor = None for page_num in range(1, 4): set_params(CursorParams(size=5, cursor=cursor)) page = paginate(session, select(User).order_by(User.id)) cursor = page.next_page # pass to next request print(f"Page {page_num}: {[u.name for u in page.items]}") # Page 1: ['User-0', 'User-1', 'User-2', 'User-3', 'User-4'] # Page 2: ['User-5', 'User-6', 'User-7', 'User-8', 'User-9'] # Page 3: ['User-10', 'User-11', 'User-12', 'User-13', 'User-14'] ``` -------------------------------- ### Implement Custom Page Model Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_user_guide/own_page_model.md Inherit from `AbstractPage` and implement the `create` classmethod to define a custom page structure. Ensure `total` is provided when creating instances. ```python from __future__ import annotations from typing import TypeVar, Generic, Any, Sequence, Optional from fastapi_pagination import Params from fastapi_pagination.bases import AbstractPage, AbstractParams T = TypeVar("T") class MyPage(AbstractPage[T], Generic[T]): results: list[T] totalResults: int __params_type__ = Params @classmethod def create( cls, items: Sequence[T], params: AbstractParams, *, total: Optional[int] = None, **kwargs: Any ) -> MyPage[T]: assert total is not None, "total must be provided" return cls( results=items, totalResults=total, ) page = MyPage[int].create(range(10), Params(), total=100) print(page.model_dump_json(indent=4)) ``` -------------------------------- ### SQLAlchemy Relationship Loading Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Demonstrates how to paginate SQLAlchemy queries with eager loading of relationships using `joinedload` or `selectinload`. It highlights the importance of `unique=True` or `UseIncludeTotal(False)` to prevent duplicate row counts when joins are involved. ```APIDOC ## SQLAlchemy relationship loading (joinedload / selectinload) When paginating queries that eager-load relationships, set `unique=True` or use `UseIncludeTotal(False)` as needed to avoid double-counting rows from joins. ```python from sqlalchemy import ForeignKey, create_engine, select from sqlalchemy.orm import ( DeclarativeBase, MappedAsDataclass, Mapped, Session, mapped_column, relationship, joinedload, selectinload, ) from pydantic import BaseModel from fastapi_pagination import Page, set_page, set_params from fastapi_pagination.customization import CustomizedPage, UseIncludeTotal from fastapi_pagination.ext.sqlalchemy import paginate engine = create_engine("sqlite:///:memory:") class Base(MappedAsDataclass, DeclarativeBase, kw_only=True): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(default=None, primary_key=True) name: Mapped[str] = mapped_column() posts: Mapped[list["Post"]] = relationship(back_populates="user", default_factory=list) class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(default=None, primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey(User.id), default=None) title: Mapped[str] = mapped_column() user: Mapped[User] = relationship(back_populates="posts", default=None) class PostOut(BaseModel): id: int title: str class UserOut(BaseModel): id: int name: str posts: list[PostOut] with Session(engine) as session: Base.metadata.create_all(session.bind) session.add(User(name="Alice", posts=[Post(title="Hello"), Post(title="World")])) session.commit() CustomPage = CustomizedPage[Page[UserOut], UseIncludeTotal(False)] set_page(CustomPage) set_params(CustomPage.__params_type__(size=10, page=1)) with Session(engine) as session: page = paginate(session, select(User).options(selectinload(User.posts))) print(page.model_dump_json(indent=4)) ``` ``` -------------------------------- ### create_page Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Creates a new page instance using the current page type or a default `Page` class. ```APIDOC ## `create_page` `create_page` is a function that allows to create a new page instance. It will take current page type from `set_page` function or use `Page` class as a default page type. `create_page` function accepts 1 required argument `items`, 1 required keyword argument `params` and 1 optional keyword argument `total`. ```py from fastapi_pagination import Page, Params from fastapi_pagination.api import set_page, create_page set_page(Page[int]) page = create_page([1, 2, 3], params=Params(page=1, size=3), total=1_000_000) print(page.model_dump_json(indent=4)) ``` ``` -------------------------------- ### Page with Navigation Links Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Utilizes `fastapi_pagination.links.Page` or `LimitOffsetPage` to include navigation links (first, last, next, previous) in the response metadata. This is useful for building paginated interfaces where clients need to easily navigate between pages. ```APIDOC ## `Page` with navigation links — `fastapi_pagination.links` Import `Page` or `LimitOffsetPage` from `fastapi_pagination.links` to include `first`, `last`, `next`, and `previous` URL links in the response metadata. ```python from fastapi import FastAPI from fastapi_pagination import add_pagination, paginate from fastapi_pagination.links import Page, LimitOffsetPage app = FastAPI() add_pagination(app) # GET /nums?page=2&size=10 @app.get("/nums") async def get_nums() -> Page[int]: return paginate(range(200)) # Response includes: # "links": {"first": "...?page=1&size=10", "last": "...?page=20&size=10", # "next": "...?page=3&size=10", "prev": "...?page=1&size=10"} # GET /nums-lo?offset=10&limit=5 @app.get("/nums-lo") async def get_nums_lo() -> LimitOffsetPage[int]: return paginate(range(200)) ``` ``` -------------------------------- ### Standalone Pagination API Usage Source: https://context7.com/uriyyo/fastapi-pagination/llms.txt Utilize the low-level API for pagination in tests or scripts. `set_page` and `set_params` configure the pagination context, and `paginate` generates the page. ```python from fastapi import FastAPI, Depends from fastapi_pagination import Page, Params, paginate from fastapi_pagination.api import ( set_page, set_params, create_page, resolve_params, pagination_ctx, response, request, ) # Standalone usage (e.g., in tests or scripts): set_page(Page[int]) set_params(Params(page=2, size=5)) page = paginate(list(range(100))) print(page.model_dump_json(indent=4)) # {"items": [5, 6, 7, 8, 9], "total": 100, "page": 2, "size": 5, "pages": 20} ``` ```python # create_page: manually assemble a page from pre-fetched items manual_page = create_page([10, 11, 12], params=Params(page=3, size=3), total=1_000) print(manual_page.model_dump_json(indent=4)) ``` -------------------------------- ### Set Global and Contextual Pagination Params Source: https://github.com/uriyyo/fastapi-pagination/blob/main/docs/learn/tutorial_advanced/low_level_api.md Manages the pagination parameters (page number and size). Can be set globally or within a specific context using a context manager. This affects how `resolve_params` behaves. ```python from fastapi_pagination import Params from fastapi_pagination.api import set_params # set global params set_params(Params(page=2, size=10)) # set params for a specific context with set_params(Params(page=3, size=20)): pass ```