Graphene: relay.Connection class cannot declare my own Paginator

Created on 19 Oct 2017  路  4Comments  路  Source: graphql-python/graphene

Im working with a connection Class of this shape:

class WishConnection(relay.Connection):
    class Meta:
        node = ProductType

    class Edge:
        through = graphene.Field(WishReasonType, description='Connection reason')

        def resolve_through(self, info, **args):
            return self.node.preason[0]

The problem is that I want to use django own queryset paginator to resolve the list that gets passed by the resolve for where the connection is used.

products = relay.ConnectionField(WishConnection)
def resolve_products(self, info, **args):
    qs = Reason.objects.filter(wish=self).only('reason', 'qty')
    len = args.get
    result = Paginator(self.products.prefetch_related(
        Prefetch('reason_set', queryset=qs, to_attr='preason')).all(),
        args.get('first', 20))
    return result 

my query looks like

{
  wish(pk: "2") {
    products(first: 1, after: "YXJyYXljb25uZWN0aW9uOjA=", ) {
      pageInfo {
        startCursor
        endCursor
        hasNextPage
        hasPreviousPage
      }
      edges {
        cursor
        through {
          reason
          qty
        }
        node {
          title
          description
          active
          disabled
        }
      }
    }
  }
}

I would Like to mak my own PageInfo class that has the attributes that can be returned by djangos Paginator class like

Paginator.count
Paginator.num_pages
Paginator.page_range

and also the methods for each pages most notably has_next has_previous
https://docs.djangoproject.com/en/1.11/topics/pagination/#page-objects

In order to do that I need to be able to control how def connection_from_list_slice(...) resolves for PageInfo and for that matter the nodes, which means that I will need to change this line to call my own method
https://github.com/graphql-python/graphene/blob/c38ffa5ffdf726e1f84b908a7c1e3be25b88f1ed/graphene/relay/connection.py#L122

Simplest way to do that is to create a subclass of the ConnectionField and override the def resolve_connection method.

well looks like I just found a path to a solution to my own problem. Going to post to see if anyone has anything to ask or add, also might comeback to this issue if I find any more problems or difficult behavior I don't know how to implement while trying to work this out.

Most helpful comment

shure, I have a few extra stuff related to my implementation, here is the meat and potatoes of it tho simplified to work with your example here is a quick example.
Not aware of a simpler way, maybe to just use a resolver for page info, you could maybe pass the info you need into the resolver some other way.

 33 class MyConnectionField(relay.ConnectionField):                                                                                                                                                                                                                                                                                                                                                 | |
 32                                                                                                                                                                                                                                                                                                                                                                                        | |
 31     """PQCField Is a custom ConnectionField that works with a django Paginator """                                                                                                                                                                                                                                                                                                     | |
 30     def __init__(self, *args, **kwargs):                                                                                                                                                                                                                                                                                                                                               | |
 29         kwargs.setdefault('page', Int(description='Page to retrieve'))                                                                                                                                                                                                                                                                                                                 | |
 28         kwargs.setdefault('perpage', Int(description='Items per page'))                                                                                                                                                                                                                                                                                                                | |
 27         super().__init__(*args, **kwargs)                                                                                                                                                                                                                                                                                                                                              | |
 26         remove = ['before', 'after', 'first', 'last']                                                                                                                                                                                                                                                                                                                                  | |
 25         for x in remove:                                                                                                                                                                                                                                                                                                                                                               | |
 24             self.args.pop(x, None)                                                                                                                                                                                                                                                                                                                                                     | |
 23                                                                                                                                                                                                                                                                                                                                                                                        | |
 22     @classmethod                                                                                                                                                                                                                                                                                                                                                                       | |
 21     def resolve_connection(cls, connection_type, args, resolved):                                                                                                                                                                                                                                                                                                                      | |
 20         if isinstance(resolved, connection_type):                                                                                                                                                                                                                                                                                                                                      |4|
 19             return resolved                                                                                                                                                                                                                                                                                                                                                            | |
 18                                                                                                                                                                                                                                                                                                                                                                                        | |
 17         assert isinstance(resolved, Iterable) or isinstance(resolved, Paginator), (                                                                                                                                                                                                                                                                                                    | |
 16             'Resolved value from the connection field have to be iterable or instance of {}. '                                                                                                                                                                                                                                                                                         | |
 15             'or an instance of Paginator'                                                                                                                                                                                                                                                                                                                                              | |
 14             'Received "{}"'                                                                                                                                                                                                                                                                                                                                                            | |
 13         ).format(connection_type, resolved)                                                                                                                                                                                                                                                                                                                                            | |
 12                                                                                                                                                                                                                                                                                                                                                                                        | |
 11         if isinstance(resolved, Iterable):                                                                                                                                                                                                                                                                                                                                             | |
 10             resolved = Paginator(resolved, 20)                                                                                                                                                                                                                                                                                                                                         | |
  9                                                                                                                                                                                                                                                                                                                                                                                        | |
  8         connection = connection_from_paginator(                                                                                                                                                                                                                                                                                                                                        | |
  7             resolved,                                                                                                                                                                                                                                                                                                                                                                  | |
  6             args,                                                                                                                                                                                                                                                                                                                                                                      | |
  5             connection_type=connection_type,                                                                                                                                                                                                                                                                                                                                           | |
  4             edge_type=connection_type.Edge,                                                                                                                                                                                                                                                                                                                                            | |
  3             pageinfo_type=PaginatorInfo                                                                                                                                                                                                                                                                                                                                                | |
  2         )                                                                                                                                                                                                                                                                                                                                                                              | |
  1         connection.iterable = resolved                                                                                                                                                                                                                                                                                                                                                 | |
128         return connection

 | 36 def connection_from_paginator(list_slice, args=None, connection_type=None,                                                                                                                                                                                                                                                                                                             |
 | 35                               edge_type=None, pageinfo_type=None, **kwargs):                                                                                                                                                                                                                                                                                                           |
 | 34     '''                                                                                                                                                                                                                                                                                                                                                                                |
 | 33     Given a slice (subset)from a Paginator, returns a connection object for use in                                                                                                                                                                                                                                                                                                     |
 | 32     GraphQL.                                                                                                                                                                                                                                                                                                                                                                           |
 | 31     This function is similar to `connectionFromArray`, but is intended for use                                                                                                                                                                                                                                                                                                         |
 | 30     cases where you know the cardinality of the connection, consider it too large                                                                                                                                                                                                                                                                                                      |
 | 29     to materialize the entire array, and instead wish pass in a slice of the                                                                                                                                                                                                                                                                                                           |
 | 28     total result large enough to cover the range specified in `args`.                                                                                                                                                                                                                                                                                                                  |
 | 27     '''                                                                                                                                                                                                                                                                                                                                                                                |
 | 26     connection_type = connection_type                                                                                                                                                                                                                                                                                                                                                  |
 | 25     edge_type = edge_type or Edge                                                                                                                                                                                                                                                                                                                                                      |
 | 24     pageinfo_type = pageinfo_type                                                                                                                                                                                                                                                                                                                                                      |
 | 23                                                                                                                                                                                                                                                                                                                                                                                        |
 | 22     args = args or {}                                                                                                                                                                                                                                                                                                                                                                  |
 | 21     page = args.get('page', 1)                                                                                                                                                                                                                                                                                                                                                         |
 | 20                                                                                                                                                                                                                                                                                                                                                                                        |
 | 19     try:                                                                                                                                                                                                                                                                                                                                                                               |
 | 18         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 | 17     except PageNotAnInteger:                                                                                                                                                                                                                                                                                                                                                           |
 | 16         # If page is not an integer, deliver first page.                                                                                                                                                                                                                                                                                                                               |
 | 15         page = 1                                                                                                                                                                                                                                                                                                                                                                       |
 | 14         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 | 13     except InvalidPage:                                                                                                                                                                                                                                                                                                                                                                |
 | 12         # If page is out of range (e.g. 9999), deliver last page of results.                                                                                                                                                                                                                                                                                                           |
 | 11         page = list_slice.num_pages                                                                                                                                                                                                                                                                                                                                                    |
 | 10         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 |  9                                                                                                                                                                                                                                                                                                                                                                                        |
 |  8     edges = [                                                                                                                                                                                                                                                                                                                                                                          |
 |  7         edge_type(                                                                                                                                                                                                                                                                                                                                                                     |
 |  6             node=node,                                                                                                                                                                                                                                                                                                                                                                 |
 |  5             cursor=offset_to_cursor(page, page + i)                                                                                                                                                                                                                                                                                                                                    |
 |  4         )                                                                                                                                                                                                                                                                                                                                                                              |
 |  3         for i, node in enumerate(_slice)                                                                                                                                                                                                                                                                                                                                               |
 |  2     ]                                                                                                                                                                                                                                                                                                                                                                                  |
 |  1     page = pageinfo_type()                                                                                                                                                                                                                                                                                                                                                             |
 |44      setattr(page, 'pcursor', list_slice)                                                                                                                                                                                                                                                                                                                                               |
 |  1     setattr(page, 'cpage', _slice)                                                                                                                                                                                                                                                                                                                                                     |
 |  2     return connection_type(                                                                                                                                                                                                                                                                                                                                                            |
 |  3             edges=edges,                                                                                                                                                                                                                                                                                                                                                               |
 |  4             page_info=page                                                                                                                                                                                                                                                                                                                                                             |
 |  5         )

class PaginatorInfo(graphene.ObjectType):
      pcursor = None
      cpage = None
      count = graphene.Int(name='Count', description='Total count')
      def resolve_count(self, info, **args):
          return self.pcursor.count

class MyConnection(relay.Connection):
     page_info = graphene.Field(PaginatorInfo, required=True)
     class Meta:
          node = SomeType

class MyType(DjangoObjectType):
       somemodel = MyConnectionField(MyConnection)
       def resolve_somemodel(self, info, **args):
            return Paginator(somemodel.objects.all(), 30)

All 4 comments

The reason I need to use the django Paginator with the connection is because if i slice the queryset in the resolver for that ConnectionField than the connection field will not know that it has already been sliced, the list, and it will try to slice the list again in the slice method, and the pageinfo in the connection will be wrong. Also PageInfo isn't really a page info more like a slice info.

https://github.com/graphql-python/graphql-relay-py/blob/17ce2efa3c396df42791ae00667120b5fae64610/graphql_relay/connection/arrayconnection.py#L32

*Well I did it 馃槃 *
This is how my queries look now

{
  wish(pk: "2") {
    title
    products (page: 1) {
      pageInfo {
        Count
        NumPages
        hasNext
        hasPrevious
        hasOtherPages
        nextPageNumber
        previousPageNumber
        startIndex
        endIndex
      }
      edges {
        through{
          reason
          qty
        }
        node {
          active
          title
        }
      }
    }
  }
}

and this is what I get back

{
  "data": {
    "wish": {
      "title": "testme",
      "products": {
        "pageInfo": {
          "Count": 2,
          "NumPages": 1,
          "hasNext": false,
          "hasPrevious": false,
          "hasOtherPages": false,
          "nextPageNumber": null,
          "previousPageNumber": null,
          "startIndex": 1,
          "endIndex": 2
        },
        "edges": [
          {
            "through": {
              "reason": "Added this Car to test",
              "qty": 1
            },
            "node": {
              "active": false,
              "title": "This is Item is a Car"
            }
          },
          {
            "through": {
              "reason": "Needs more cowbell",
              "qty": 7
            },
            "node": {
              "active": true,
              "title": "CowBell"
            }
          }
        ]
      }
    }
  }
}

@japrogramer
Mind sharing how you added custom fields to pageInfo please?
I reached a point where I use a custom connection (class MyConnection(relay.Connection):) and my version of PageInfo, however I believe the questions is - how to pass data from a resolver to PageInfo? i.e. total counts, etc.
Thank you!

shure, I have a few extra stuff related to my implementation, here is the meat and potatoes of it tho simplified to work with your example here is a quick example.
Not aware of a simpler way, maybe to just use a resolver for page info, you could maybe pass the info you need into the resolver some other way.

 33 class MyConnectionField(relay.ConnectionField):                                                                                                                                                                                                                                                                                                                                                 | |
 32                                                                                                                                                                                                                                                                                                                                                                                        | |
 31     """PQCField Is a custom ConnectionField that works with a django Paginator """                                                                                                                                                                                                                                                                                                     | |
 30     def __init__(self, *args, **kwargs):                                                                                                                                                                                                                                                                                                                                               | |
 29         kwargs.setdefault('page', Int(description='Page to retrieve'))                                                                                                                                                                                                                                                                                                                 | |
 28         kwargs.setdefault('perpage', Int(description='Items per page'))                                                                                                                                                                                                                                                                                                                | |
 27         super().__init__(*args, **kwargs)                                                                                                                                                                                                                                                                                                                                              | |
 26         remove = ['before', 'after', 'first', 'last']                                                                                                                                                                                                                                                                                                                                  | |
 25         for x in remove:                                                                                                                                                                                                                                                                                                                                                               | |
 24             self.args.pop(x, None)                                                                                                                                                                                                                                                                                                                                                     | |
 23                                                                                                                                                                                                                                                                                                                                                                                        | |
 22     @classmethod                                                                                                                                                                                                                                                                                                                                                                       | |
 21     def resolve_connection(cls, connection_type, args, resolved):                                                                                                                                                                                                                                                                                                                      | |
 20         if isinstance(resolved, connection_type):                                                                                                                                                                                                                                                                                                                                      |4|
 19             return resolved                                                                                                                                                                                                                                                                                                                                                            | |
 18                                                                                                                                                                                                                                                                                                                                                                                        | |
 17         assert isinstance(resolved, Iterable) or isinstance(resolved, Paginator), (                                                                                                                                                                                                                                                                                                    | |
 16             'Resolved value from the connection field have to be iterable or instance of {}. '                                                                                                                                                                                                                                                                                         | |
 15             'or an instance of Paginator'                                                                                                                                                                                                                                                                                                                                              | |
 14             'Received "{}"'                                                                                                                                                                                                                                                                                                                                                            | |
 13         ).format(connection_type, resolved)                                                                                                                                                                                                                                                                                                                                            | |
 12                                                                                                                                                                                                                                                                                                                                                                                        | |
 11         if isinstance(resolved, Iterable):                                                                                                                                                                                                                                                                                                                                             | |
 10             resolved = Paginator(resolved, 20)                                                                                                                                                                                                                                                                                                                                         | |
  9                                                                                                                                                                                                                                                                                                                                                                                        | |
  8         connection = connection_from_paginator(                                                                                                                                                                                                                                                                                                                                        | |
  7             resolved,                                                                                                                                                                                                                                                                                                                                                                  | |
  6             args,                                                                                                                                                                                                                                                                                                                                                                      | |
  5             connection_type=connection_type,                                                                                                                                                                                                                                                                                                                                           | |
  4             edge_type=connection_type.Edge,                                                                                                                                                                                                                                                                                                                                            | |
  3             pageinfo_type=PaginatorInfo                                                                                                                                                                                                                                                                                                                                                | |
  2         )                                                                                                                                                                                                                                                                                                                                                                              | |
  1         connection.iterable = resolved                                                                                                                                                                                                                                                                                                                                                 | |
128         return connection

 | 36 def connection_from_paginator(list_slice, args=None, connection_type=None,                                                                                                                                                                                                                                                                                                             |
 | 35                               edge_type=None, pageinfo_type=None, **kwargs):                                                                                                                                                                                                                                                                                                           |
 | 34     '''                                                                                                                                                                                                                                                                                                                                                                                |
 | 33     Given a slice (subset)from a Paginator, returns a connection object for use in                                                                                                                                                                                                                                                                                                     |
 | 32     GraphQL.                                                                                                                                                                                                                                                                                                                                                                           |
 | 31     This function is similar to `connectionFromArray`, but is intended for use                                                                                                                                                                                                                                                                                                         |
 | 30     cases where you know the cardinality of the connection, consider it too large                                                                                                                                                                                                                                                                                                      |
 | 29     to materialize the entire array, and instead wish pass in a slice of the                                                                                                                                                                                                                                                                                                           |
 | 28     total result large enough to cover the range specified in `args`.                                                                                                                                                                                                                                                                                                                  |
 | 27     '''                                                                                                                                                                                                                                                                                                                                                                                |
 | 26     connection_type = connection_type                                                                                                                                                                                                                                                                                                                                                  |
 | 25     edge_type = edge_type or Edge                                                                                                                                                                                                                                                                                                                                                      |
 | 24     pageinfo_type = pageinfo_type                                                                                                                                                                                                                                                                                                                                                      |
 | 23                                                                                                                                                                                                                                                                                                                                                                                        |
 | 22     args = args or {}                                                                                                                                                                                                                                                                                                                                                                  |
 | 21     page = args.get('page', 1)                                                                                                                                                                                                                                                                                                                                                         |
 | 20                                                                                                                                                                                                                                                                                                                                                                                        |
 | 19     try:                                                                                                                                                                                                                                                                                                                                                                               |
 | 18         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 | 17     except PageNotAnInteger:                                                                                                                                                                                                                                                                                                                                                           |
 | 16         # If page is not an integer, deliver first page.                                                                                                                                                                                                                                                                                                                               |
 | 15         page = 1                                                                                                                                                                                                                                                                                                                                                                       |
 | 14         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 | 13     except InvalidPage:                                                                                                                                                                                                                                                                                                                                                                |
 | 12         # If page is out of range (e.g. 9999), deliver last page of results.                                                                                                                                                                                                                                                                                                           |
 | 11         page = list_slice.num_pages                                                                                                                                                                                                                                                                                                                                                    |
 | 10         _slice = list_slice.page(page)                                                                                                                                                                                                                                                                                                                                                 |
 |  9                                                                                                                                                                                                                                                                                                                                                                                        |
 |  8     edges = [                                                                                                                                                                                                                                                                                                                                                                          |
 |  7         edge_type(                                                                                                                                                                                                                                                                                                                                                                     |
 |  6             node=node,                                                                                                                                                                                                                                                                                                                                                                 |
 |  5             cursor=offset_to_cursor(page, page + i)                                                                                                                                                                                                                                                                                                                                    |
 |  4         )                                                                                                                                                                                                                                                                                                                                                                              |
 |  3         for i, node in enumerate(_slice)                                                                                                                                                                                                                                                                                                                                               |
 |  2     ]                                                                                                                                                                                                                                                                                                                                                                                  |
 |  1     page = pageinfo_type()                                                                                                                                                                                                                                                                                                                                                             |
 |44      setattr(page, 'pcursor', list_slice)                                                                                                                                                                                                                                                                                                                                               |
 |  1     setattr(page, 'cpage', _slice)                                                                                                                                                                                                                                                                                                                                                     |
 |  2     return connection_type(                                                                                                                                                                                                                                                                                                                                                            |
 |  3             edges=edges,                                                                                                                                                                                                                                                                                                                                                               |
 |  4             page_info=page                                                                                                                                                                                                                                                                                                                                                             |
 |  5         )

class PaginatorInfo(graphene.ObjectType):
      pcursor = None
      cpage = None
      count = graphene.Int(name='Count', description='Total count')
      def resolve_count(self, info, **args):
          return self.pcursor.count

class MyConnection(relay.Connection):
     page_info = graphene.Field(PaginatorInfo, required=True)
     class Meta:
          node = SomeType

class MyType(DjangoObjectType):
       somemodel = MyConnectionField(MyConnection)
       def resolve_somemodel(self, info, **args):
            return Paginator(somemodel.objects.all(), 30)

Was this page helpful?
0 / 5 - 0 ratings