1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
|
import re
from collections import OrderedDict
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
from functools import partial
from graphql_relay import connection_from_list
from ..types import Boolean, Enum, Int, Interface, List, NonNull, Scalar, String, Union
from ..types.field import Field
from ..types.objecttype import ObjectType, ObjectTypeOptions
from ..utils.thenables import maybe_thenable
from .node import is_node
class PageInfo(ObjectType):
class Meta:
description = (
"The Relay compliant `PageInfo` type, containing data necessary to"
" paginate this connection."
)
has_next_page = Boolean(
required=True,
name="hasNextPage",
description="When paginating forwards, are there more items?",
)
has_previous_page = Boolean(
required=True,
name="hasPreviousPage",
description="When paginating backwards, are there more items?",
)
start_cursor = String(
name="startCursor",
description="When paginating backwards, the cursor to continue.",
)
end_cursor = String(
name="endCursor",
description="When paginating forwards, the cursor to continue.",
)
class ConnectionOptions(ObjectTypeOptions):
node = None
class Connection(ObjectType):
class Meta:
abstract = True
@classmethod
def __init_subclass_with_meta__(cls, node=None, name=None, **options):
_meta = ConnectionOptions(cls)
assert node, "You have to provide a node in {}.Meta".format(cls.__name__)
assert isinstance(node, NonNull) or issubclass(
node, (Scalar, Enum, ObjectType, Interface, Union, NonNull)
), ('Received incompatible node "{}" for Connection {}.').format(
node, cls.__name__
)
base_name = re.sub("Connection$", "", name or cls.__name__) or node._meta.name
if not name:
name = "{}Connection".format(base_name)
edge_class = getattr(cls, "Edge", None)
_node = node
class EdgeBase(object):
node = Field(_node, description="The item at the end of the edge")
cursor = String(required=True, description="A cursor for use in pagination")
class EdgeMeta:
description = "A Relay edge containing a `{}` and its cursor.".format(
base_name
)
edge_name = "{}Edge".format(base_name)
if edge_class:
edge_bases = (edge_class, EdgeBase, ObjectType)
else:
edge_bases = (EdgeBase, ObjectType)
edge = type(edge_name, edge_bases, {"Meta": EdgeMeta})
cls.Edge = edge
options["name"] = name
_meta.node = node
_meta.fields = OrderedDict(
[
(
"page_info",
Field(
PageInfo,
name="pageInfo",
required=True,
description="Pagination data for this connection.",
),
),
(
"edges",
Field(
NonNull(List(edge)),
description="Contains the nodes in this connection.",
),
),
]
)
return super(Connection, cls).__init_subclass_with_meta__(
_meta=_meta, **options
)
class IterableConnectionField(Field):
def __init__(self, type, *args, **kwargs):
kwargs.setdefault("before", String())
kwargs.setdefault("after", String())
kwargs.setdefault("first", Int())
kwargs.setdefault("last", Int())
super(IterableConnectionField, self).__init__(type, *args, **kwargs)
@property
def type(self):
type = super(IterableConnectionField, self).type
connection_type = type
if isinstance(type, NonNull):
connection_type = type.of_type
if is_node(connection_type):
raise Exception(
"ConnectionFields now need a explicit ConnectionType for Nodes.\n"
"Read more: https://github.com/graphql-python/graphene/blob/v2.0.0/UPGRADE-v2.0.md#node-connections"
)
assert issubclass(connection_type, Connection), (
'{} type have to be a subclass of Connection. Received "{}".'
).format(self.__class__.__name__, connection_type)
return type
@classmethod
def resolve_connection(cls, connection_type, args, resolved):
if isinstance(resolved, connection_type):
return resolved
assert isinstance(resolved, Iterable), (
"Resolved value from the connection field have to be iterable or instance of {}. "
'Received "{}"'
).format(connection_type, resolved)
connection = connection_from_list(
resolved,
args,
connection_type=connection_type,
edge_type=connection_type.Edge,
pageinfo_type=PageInfo,
)
connection.iterable = resolved
return connection
@classmethod
def connection_resolver(cls, resolver, connection_type, root, info, **args):
resolved = resolver(root, info, **args)
if isinstance(connection_type, NonNull):
connection_type = connection_type.of_type
on_resolve = partial(cls.resolve_connection, connection_type, args)
return maybe_thenable(resolved, on_resolve)
def get_resolver(self, parent_resolver):
resolver = super(IterableConnectionField, self).get_resolver(parent_resolver)
return partial(self.connection_resolver, resolver, self.type)
ConnectionField = IterableConnectionField
|