Files
dify/api/libs/pagination.py
Checo 2e1ab194b7 refactor: replace db.paginate with plain SQLAlchemy pagination (#38280)
Co-authored-by: Asuka Minato <i@asukaminato.eu.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-04 04:50:01 +00:00

87 lines
2.2 KiB
Python

from __future__ import annotations
import math
from dataclasses import dataclass
from sqlalchemy import Select, func, select
from sqlalchemy.orm import Session, scoped_session
@dataclass
class PaginatedResult[T]:
"""Minimal pagination container backed by plain SQLAlchemy queries.
Drop-in replacement for Flask-SQLAlchemy's ``db.paginate`` return value.
Only the attributes actually consumed across the codebase are exposed:
``items``, ``total``, ``page``, ``per_page``, ``pages``, ``has_next``.
"""
items: list[T]
total: int
page: int
per_page: int
@property
def pages(self) -> int:
if self.per_page == 0:
return 0
return max(1, math.ceil(self.total / self.per_page))
@property
def has_next(self) -> bool:
return self.page < self.pages
def __iter__(self):
return iter(self.items)
def paginate_query(
stmt: Select,
*,
page: int = 1,
per_page: int = 20,
max_per_page: int | None = None,
session: Session | scoped_session | None = None,
) -> PaginatedResult:
"""Execute *stmt* as a paginated query using plain SQLAlchemy.
Parameters
----------
stmt:
A SQLAlchemy ``select()`` statement.
page:
1-based page number.
per_page:
Number of items per page.
max_per_page:
Hard ceiling for *per_page*; ``None`` means no cap.
session:
The session to use. Falls back to ``db.session`` when omitted.
"""
if session is None:
from extensions.ext_database import db
session = db.session
if max_per_page is not None:
per_page = min(per_page, max_per_page)
page = max(1, page)
per_page = max(1, per_page)
# total count — wrap in a scalar subquery so arbitrary selects work
count_stmt = select(func.count()).select_from(stmt.subquery())
total: int = session.scalar(count_stmt) or 0 # type: ignore[assignment]
# fetch the page
offset = (page - 1) * per_page
page_stmt = stmt.limit(per_page).offset(offset)
items = list(session.scalars(page_stmt).all())
return PaginatedResult(
items=items,
total=total,
page=page,
per_page=per_page,
)