fastapi-pagination

repository·main·Indexed 23 days ago

https://github.com/uriyyo/fastapi-pagination

A Python library for adding page-based and cursor-based pagination to FastAPI applications. It provides utilities for in-memory data and specific extensions for database frameworks like SQLAlchemy to avoid loading all data into memory. The library includes a flexible customization system via CustomizedPage, allowing developers to rename fields, exclude metadata, flatten responses, and add additional fields to the pagination schema.

Tokens
33.6K
Snippets
82
Records
156
Agent score
81%

What's inside fastapi-pagination

  1. Choose a pagination technique

    main

    The fastapi-pagination library supports multiple pagination strategies. By default, it uses page-based pagination, but you can switch to other techniques by importing from the specific module corresponding to your needs:

    • Page-based pagination: Use fastapi_pagination.default (the default behavior).
    • Limit-offset pagination: Use fastapi_pagination.limit_offset.
    • Cursor-based pagination: Use fastapi_pagination.cursor.
  2. How Page[T] works in fastapi-pagination

    main
    The Page[T] type is a generic wrapper used in FastAPI response models to define a paginated response structure. When used as a return type annotation, fastapi-pagination automatically handles the extraction of page and size query parameters from the request and formats the JSON response to include metadata like total, page, and size alongside the list of items of type T.
  3. Quickstart: Basic pagination with in-memory data

    main

    To implement basic pagination for in-memory data (like lists), follow these steps:

    1. Import Page, add_pagination, and paginate from fastapi_pagination.
    2. Call add_pagination(app) on your FastAPI application instance to enable pagination support.
    3. Use Page[YourModel] as the return type annotation for your endpoint.
    4. Call paginate(your_data_list) within the endpoint to return the paginated response.

    Warning: Using the default paginate function on database queries will load all data into memory. For database integrations, use the specific extension modules provided by the library.

    from fastapi import FastAPI
    from pydantic import BaseModel, Field
    
    # import all you need from fastapi-pagination
    from fastapi_pagination import Page, add_pagination, paginate
    
    app = FastAPI()  # create FastAPI app
    add_pagination(app)  # important! add pagination to your app
    
    
    class UserOut(BaseModel):  # define your model
        name: str = Field(..., example="Steve")
        surname: str = Field(..., example="Rogers")
    
    
    users = [  # create some data
        UserOut(name="Steve", surname="Rogers"),
        # ...
    ]
    
    
    # req: GET /users
    @app.get("/users")
    async def get_users() -> Page[UserOut]:
        # use Page[UserOut] as return type annotation
        return paginate(users)  # use paginate function to paginate your data
  4. Quickstart: Implement pagination in FastAPI

    main

    To implement pagination in a FastAPI application, follow these steps:

    1. Initialize the plugin: Call add_pagination(app) on your FastAPI application instance. This sets up the necessary dependency injection for pagination parameters.
    2. Define your response model: Use the Page[T] generic type from fastapi_pagination as the return type annotation for your endpoint, where T is your schema (e.g., UserOut).
    3. Return paginated data: Use the paginate() function to wrap your data source (list, query, etc.).

    Example implementation:

    from fastapi import FastAPI
    from fastapi_pagination import Page, add_pagination, paginate
    from pydantic import BaseModel
    
    app = FastAPI()
    
    class UserOut(BaseModel):
        name: str
        email: str
    
    users = [
        UserOut(name="Alice", email="alice@example.com"),
        UserOut(name="Bob", email="bob@example.com"),
    ]
    
    @app.get("/users", response_model=Page[UserOut])
    async def get_users():
        return paginate(users)
    
    add_pagination(app)
  5. Quickstart: Add pagination to a FastAPI application

    main

    To enable pagination in a FastAPI application, you need to perform three main steps:

    1. Initialize pagination: Call add_pagination(app) on your FastAPI instance. This automatically handles the dependency injection for pagination parameters (like page and size) for all routes.
    2. Annotate return types: Use Page[T] (where T is your data type) as the return type annotation for your route handlers.
    3. Paginate data: Use the paginate() function inside your route handler, passing in the collection you want to paginate.

    Warning: The paginate() function is designed for data that is already loaded into memory. If you are using a database or an ORM, use the specific pagination methods provided by that database/ORM instead to avoid loading the entire dataset into memory.

    from fastapi import FastAPI
    from fastapi_pagination import Page, add_pagination, paginate
    
    app = FastAPI()
    add_pagination(app)
    
    # req: GET /users?page=2&size=10
    @app.get("/users")
    async def get_users() -> Page[int]:
        return paginate([*range(100)])
  6. Rename Page fields using UseFieldAliases

    main

    You can change the default field names of a Page object during serialization using the UseFieldAliases customizer. This is useful when your API needs to follow a specific naming convention (e.g., changing items to content or size to pageSize).

    To implement this, wrap your Page[T] type in a CustomizedPage and pass an instance of UseFieldAliases to it, specifying the mapping for the fields you wish to rename.

    Supported fields for aliasing:

    • items
    • size
    • page
    • pages
    • total
    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")
    
    # Define a custom Page type with aliased field names
    CustomPage = CustomizedPage[
        Page[T],
        UseFieldsAliases(
            items="content",
            size="pageSize",
            page="pageNumber",
            pages="totalPages",
            total="totalElements",
        ),
    ]
    
    @app.get("/nums")
    async def get_nums() -> CustomPage[int]:
        return paginate(range(1_000))
  7. Make pagination parameters optional with UseOptionalParams

    main

    By default, pagination parameters like size and page might be required depending on your Params class definition. The UseOptionalParams customizer allows you to make these fields optional. This enables users to omit certain parameters (e.g., requesting GET /nums instead of GET /nums?size=5&page=1) to select all available items or use default behaviors when parameters are missing.

    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")
    
    # Define a CustomPage type using the UseOptionalParams customizer
    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))
  8. Return a non-Page response model using set_page

    main

    If you need to return a response model that is NOT a Page object (for example, returning a raw list[int]), you must:

    1. Use set_page(Page[T]) inside the route handler to tell the library which page type to use for parameter parsing.
    2. Explicitly include params: Annotated[Params, Depends()] in your route function signature.
    3. Pass the params object to the paginate() function.
    4. Return the .items attribute from the result of paginate().
    from typing import Annotated
    
    from fastapi import FastAPI, Depends
    from fastapi_pagination import Params, Page, paginate, set_page
    
    app = FastAPI()
    
    # req: GET /non-page?page=2&size=5
    @app.get("/non-page")
    async def route(params: Annotated[Params, Depends()]) -> list[int]:
        set_page(Page[int])
        page = paginate(range(100), params=params)
        return page.items
  9. Add pagination to a FastAPI route using default behavior

    main

    When you have called add_pagination(app), you can enable pagination for a route by doing one of two things:

    1. Use a return type annotation that is a subclass of AbstractPage (e.g., Page[T]).
    2. Set the response_model parameter in the route decorator to a subclass of AbstractPage.

    This allows the paginate() function to automatically wrap your data into a paginated response structure.

    from fastapi import FastAPI
    from fastapi_pagination import Page, add_pagination, paginate
    
    app = FastAPI()
    add_pagination(app)
    
    # Option 1: Using return type annotation
    @app.get("/return-type-ann")
    async def route() -> Page[int]:
        return paginate(range(100))
    
    # Option 2: Using response_model
    @app.get("/return-model", response_model=Page[int])
    async def route():
        return paginate(range(100))
  10. Use LimitOffsetPage links for limit-offset pagination

    main

    To include metadata for limit-offset pagination, use the LimitOffsetPage type from the fastapi_pagination.links module. This is used when the client specifies an offset and a limit via query parameters.

    Example request: GET /nums?offset=10&limit=5

    from fastapi import FastAPI
    from fastapi_pagination import add_pagination, paginate
    from fastapi_pagination.links import LimitOffsetPage
    
    app = FastAPI()
    add_pagination(app)
    
    # req: GET /nums?offset=10&limit=5
    @app.get("/nums")
    async def get_users() -> LimitOffsetPage[int]:
        return paginate(range(200))
  11. Implement Cursor-Based Pagination

    main

    Cursor-Based pagination uses a unique identifier (a cursor) to track the current position in the dataset. This is the most efficient method for large datasets and provides consistent results even if the underlying data changes. However, it is more complex to implement and requires a unique, sequential field to serve as the cursor.

    To use it, configure the page type using set_page(CursorPage[T]) and the parameters using set_params(CursorParams(size=N)). When using SQLAlchemy, use the paginate function from fastapi_pagination.ext.sqlalchemy.

    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))