Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

Paginate without requiring length

オープン
#16 コメント 9 件 リアクション 3 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
5/5
見積もり時間
1週間以上
初心者へのやさしさ
35/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
停滞
技術スタック
graphql, python

調査の方向性

まず既存の connection pagination の実装を見つけ、first、last、before、after、total counts の扱いを、提案されている connection_from_list_slice 関数と比較します。count-free pagination をデフォルトにするべきか、オプションの戦略にするべきかを判断し、その後、次/前のページ情報と total_count の動作が引き続き正しいことを確認します。

索引モデルが issue の本文から書いたものです。

説明

The current implementation requires you to pass the total length of the iterable, and this usually means making an extra query to find out the count. There's no need for this, since we can actually accomplish the same results by trying to fetch n+1 results, which will mean that there are more to be consumed, without the need for the real total count.

This is the custom implementation we have applied to spare us the count query, which can be costly and adds an unnecessary overhead to every query. We only need the real total count, when that value is requested (using a custom CountableConnection)

class CountableConnection(graphene.relay.Connection):
    class Meta:
        abstract = True

    total_count = graphene.Int()

    @staticmethod
    def resolve_total_count(root, info, *args, **kwargs):
        try:
            t_count = root.iterable.count()
        except:
            t_count = len(root.iterable)
        
        return t_count

def connection_from_list_slice(list_slice, args=None, connection_type=None,
                               edge_type=None, pageinfo_type=None):
    '''
    Given an iterator it consumes the needed amount based on the pagination
    params, and also tries to figure out if there are more results to be
    consumed. We do so by trying to fetch one more element than the specified
    amount, if we were able to fetch n+1 it means there are more to be consumed.

    This spares the caller passing the total count of results, which
    usually means making an extra query just to find out that number.
    '''
    from graphql_relay.utils import base64, unbase64, is_str
    from graphql_relay.connection.connectiontypes import Connection, PageInfo, Edge

    connection_type = connection_type or Connection
    edge_type = edge_type or Edge
    pageinfo_type = pageinfo_type or PageInfo

    args = args or {}

    before = args.get('before')
    after = args.get('after')
    first = args.get('first')
    last = args.get('last')

    if first:
        after = get_offset_with_default(after, -1) + 1
        _slice = list_slice[after:  max(after, 0) + first + 1]  # fetch n+1

        items = _slice[:-1]
        if len(items) < first:
            items = _slice[:]  # If there are not enough, get them all

        edges = [
            edge_type(
                node=node,
                cursor=offset_to_cursor(after + i)
            )
            for i, node in enumerate(items)
        ]

    elif last:
        if before:
            before = get_offset_with_default(before)
            _slice = list_slice[max(before-last-1, 0):before]  # fetch n+1

        else:
            # beware that not specifying before results in the need
            # to calculate the total amount
            _slice = list_slice[(last*-1)-1:]

            try:  
                before = list_slice.count()
            except:
                before = len(list_slice)

        items = _slice[1:]
        if len(items) < last:
            items = _slice[:]  # If there are not enough, get them all

        edges = [
            edge_type(
                node=node,
                cursor=offset_to_cursor(before - last -1 + i)
            )
            for i, node in enumerate(items)
        ]

    else:  # we are not allowing to pass after/before without first/last
        items = list_slice[:]
        edges = [
            edge_type(
                node=node,
                cursor=offset_to_cursor(i)
            )
            for i, node in enumerate(items)
        ]

    first_edge_cursor = edges[0].cursor if edges else None
    last_edge_cursor = edges[-1].cursor if edges else None
    
    has_previous_page = False
    if (isinstance(last, int) and len(_slice) > last) or after > 0:
        has_previous_page = True

    return connection_type(
        edges=edges,
        page_info=pageinfo_type(
            start_cursor=first_edge_cursor,
            end_cursor=last_edge_cursor,
            has_previous_page=has_previous_page,
            has_next_page=len(_slice) > first if isinstance(first, int) else False
        )
    )

@syrusakbary Not sure if we want this as the default or at least have this as an optional stragey.
If this sounds reasonable I can make a PR for this.

主要言語
Python
スター
143
フォーク
42
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

graphql-python/graphql-relay-py のほかの issue

graphql-python/graphql-relay-py の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。