| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- """Utilities for client-side handling of "relay-style" GraphQL pagination.
- For formal specs and definitions, see https://relay.dev/graphql/connections.htm.
- """
- from __future__ import annotations
- from collections.abc import Iterator
- from typing import Generic, Literal, Optional, TypeVar
- from pydantic import NonNegativeInt
- from .base import GQLResult
- NodeT = TypeVar("NodeT")
- """A generic type variable for a GraphQL relay node."""
- class PageInfo(GQLResult):
- """Pagination metadata returned by the server for a single page of results."""
- typename__: Literal["PageInfo"] = "PageInfo"
- end_cursor: Optional[str]
- """Opaque token marking the end of this page and the start of the next page."""
- has_next_page: bool
- """True if more results exist beyond this page."""
- class Edge(GQLResult, Generic[NodeT]):
- """A wrapper around a single result item in a paginated response.
- In relay-style pagination, individual items are wrapped in "edges" which can
- carry additional metadata, e.g., per-item cursors. This base implementation
- only exposes the `node` (the actual result item, like a GraphQL `Run` or `Project`).
- """
- node: NodeT
- """The actual result item."""
- class Connection(GQLResult, Generic[NodeT]):
- """A page of results from the response of a paginated GraphQL query.
- This follows the "Relay Connection" specification, which is a standard
- way to paginate large result sets in GraphQL. Instead of returning all
- results at once, the server returns one page at a time. Each "page" is
- represented by a `Connection` object that includes:
- - A list of `edges`, each wrapping a single result item (`node`).
- - A `page_info` object with metadata for fetching subsequent pages.
- - Optionally, a `total_count` of all results (not just this page).
- """
- edges: list[Edge[NodeT]]
- """The items in this page, each wrapped in an `Edge`."""
- page_info: PageInfo
- """Pagination metadata, e.g. `end_cursor`, `has_next_page`."""
- total_count: Optional[NonNegativeInt] = None
- """Total number of results across all pages, if available."""
- def nodes(self) -> Iterator[NodeT]:
- """Returns an iterator over the nodes in the connection."""
- return (node for edge in self.edges if (node := edge.node))
- @property
- def has_next(self) -> bool:
- """Returns True if there are more pages to fetch."""
- return self.page_info.has_next_page
- @property
- def next_cursor(self) -> Optional[str]:
- """The cursor value to pass as the `after` arg in the next page request."""
- return self.page_info.end_cursor
- class ConnectionWithTotal(Connection[NodeT], Generic[NodeT]):
- """A `Connection` where the `totalCount` field must be present.
- Use this INSTEAD of `Connection` when the paginated query is expected
- to return a finite `totalCount` field, i.e. when `totalCount` is:
- - explicitly requested in the GraphQL query
- - non-nullable in the GraphQL schema
- """
- total_count: NonNegativeInt
- """Total number of results across all pages (required, not optional)."""
|