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