Example #1
0
    def paginate(
            self,
            query: Select,
            item_factory: Callable[[Row], ModelT],
            *,
            sort_model: Base = None,
            custom_sort: str = None,
    ) -> Page[ModelT]:
        total = Session.execute(
            select(func.count()).
            select_from(query.subquery())
        ).scalar_one()

        try:
            if sort_model:
                sort_col = getattr(sort_model, self.sort)
            elif custom_sort:
                sort_col = text(custom_sort)
            else:
                sort_col = self.sort

            limit = self.size or total

            items = [
                item_factory(row) for row in Session.execute(
                    query.
                    order_by(sort_col).
                    offset(limit * (self.page - 1)).
                    limit(limit)
                )
            ]
        except (AttributeError, CompileError):
            raise HTTPException(HTTP_422_UNPROCESSABLE_ENTITY, 'Invalid sort column')

        return Page(
            items=items,
            total=total,
            page=self.page,
            pages=ceil(total / limit) if limit else 0,
        )
Example #2
0
    async def _paginated_query(
        self,
        stmt: Select,
        cursor: Optional[HistoryCursor],
        limit: Optional[int],
    ) -> PaginatedHistory[TokenChangeHistoryEntry]:
        """Run a paginated query (one with a limit or a cursor)."""
        limited_stmt = stmt

        # Apply the cursor, if there is one.
        if cursor:
            limited_stmt = self._apply_cursor(limited_stmt, cursor)

        # When retrieving a previous set of results using a previous
        # cursor, we have to reverse the sort algorithm so that the cursor
        # boundary can be applied correctly.  We'll then later reverse the
        # result set to return it in proper forward-sorted order.
        if cursor and cursor.previous:
            limited_stmt = limited_stmt.order_by(
                TokenChangeHistory.event_time, TokenChangeHistory.id
            )
        else:
            limited_stmt = limited_stmt.order_by(
                TokenChangeHistory.event_time.desc(),
                TokenChangeHistory.id.desc(),
            )

        # Grab one more element than the query limit so that we know whether
        # to create a cursor (because there are more elements) and what the
        # cursor value should be (for forward cursors).
        if limit:
            limited_stmt = limited_stmt.limit(limit + 1)

        # Execute the query twice, once to get the next bach of results and
        # once to get the count of all entries without pagination.
        result = await self._session.scalars(limited_stmt)
        entries = result.all()
        count_stmt = select(func.count()).select_from(stmt.subquery())
        count = await self._session.scalar(count_stmt)

        # Calculate the cursors, remove the extra element we asked for, and
        # reverse the results again if we did a reverse sort because we were
        # using a previous cursor.
        prev_cursor = None
        next_cursor = None
        if cursor and cursor.previous:
            if limit:
                next_cursor = HistoryCursor.invert(cursor)
                if len(entries) > limit:
                    prev_cursor = self._build_prev_cursor(entries[limit - 1])
                    entries = entries[:limit]
            entries.reverse()
        elif limit:
            if cursor:
                prev_cursor = HistoryCursor.invert(cursor)
            if len(entries) > limit:
                next_cursor = self._build_next_cursor(entries[limit])
                entries = entries[:limit]

        # Return the results.
        return PaginatedHistory[TokenChangeHistoryEntry](
            entries=[TokenChangeHistoryEntry.from_orm(e) for e in entries],
            count=count,
            prev_cursor=prev_cursor,
            next_cursor=next_cursor,
        )