mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-23 18:13:54 +03:00
Co-authored-by: Thomas Leonard <thomas@loftorbital.com>
This commit is contained in:
parent
b20bbdcdf7
commit
ee1ff975d7
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
||||||
if: ${{ matrix.python == '3.10' }}
|
if: ${{ matrix.python == '3.10' }}
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: graphene-sqlalchemy-coverage
|
name: graphene-coverage
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
- name: Upload coverage.xml to codecov
|
- name: Upload coverage.xml to codecov
|
||||||
|
|
1
Makefile
1
Makefile
|
@ -7,6 +7,7 @@ help:
|
||||||
install-dev:
|
install-dev:
|
||||||
pip install -e ".[dev]"
|
pip install -e ".[dev]"
|
||||||
|
|
||||||
|
.PHONY: test ## Run tests
|
||||||
test:
|
test:
|
||||||
py.test graphene examples
|
py.test graphene examples
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
from .pyutils.version import get_version
|
from .pyutils.version import get_version
|
||||||
from .relay import (
|
from .relay import (
|
||||||
|
BaseGlobalIDType,
|
||||||
ClientIDMutation,
|
ClientIDMutation,
|
||||||
Connection,
|
Connection,
|
||||||
ConnectionField,
|
ConnectionField,
|
||||||
|
DefaultGlobalIDType,
|
||||||
GlobalID,
|
GlobalID,
|
||||||
Node,
|
Node,
|
||||||
PageInfo,
|
PageInfo,
|
||||||
|
SimpleGlobalIDType,
|
||||||
|
UUIDGlobalIDType,
|
||||||
is_node,
|
is_node,
|
||||||
)
|
)
|
||||||
from .types import (
|
from .types import (
|
||||||
|
@ -52,6 +56,7 @@ __all__ = [
|
||||||
"Argument",
|
"Argument",
|
||||||
"Base64",
|
"Base64",
|
||||||
"BigInt",
|
"BigInt",
|
||||||
|
"BaseGlobalIDType",
|
||||||
"Boolean",
|
"Boolean",
|
||||||
"ClientIDMutation",
|
"ClientIDMutation",
|
||||||
"Connection",
|
"Connection",
|
||||||
|
@ -60,6 +65,7 @@ __all__ = [
|
||||||
"Date",
|
"Date",
|
||||||
"DateTime",
|
"DateTime",
|
||||||
"Decimal",
|
"Decimal",
|
||||||
|
"DefaultGlobalIDType",
|
||||||
"Dynamic",
|
"Dynamic",
|
||||||
"Enum",
|
"Enum",
|
||||||
"Field",
|
"Field",
|
||||||
|
@ -80,10 +86,12 @@ __all__ = [
|
||||||
"ResolveInfo",
|
"ResolveInfo",
|
||||||
"Scalar",
|
"Scalar",
|
||||||
"Schema",
|
"Schema",
|
||||||
|
"SimpleGlobalIDType",
|
||||||
"String",
|
"String",
|
||||||
"Time",
|
"Time",
|
||||||
"UUID",
|
|
||||||
"Union",
|
"Union",
|
||||||
|
"UUID",
|
||||||
|
"UUIDGlobalIDType",
|
||||||
"is_node",
|
"is_node",
|
||||||
"lazy_import",
|
"lazy_import",
|
||||||
"resolve_only_args",
|
"resolve_only_args",
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
from .node import Node, is_node, GlobalID
|
from .node import Node, is_node, GlobalID
|
||||||
from .mutation import ClientIDMutation
|
from .mutation import ClientIDMutation
|
||||||
from .connection import Connection, ConnectionField, PageInfo
|
from .connection import Connection, ConnectionField, PageInfo
|
||||||
|
from .id_type import (
|
||||||
|
BaseGlobalIDType,
|
||||||
|
DefaultGlobalIDType,
|
||||||
|
SimpleGlobalIDType,
|
||||||
|
UUIDGlobalIDType,
|
||||||
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Node",
|
"BaseGlobalIDType",
|
||||||
"is_node",
|
|
||||||
"GlobalID",
|
|
||||||
"ClientIDMutation",
|
"ClientIDMutation",
|
||||||
"Connection",
|
"Connection",
|
||||||
"ConnectionField",
|
"ConnectionField",
|
||||||
|
"DefaultGlobalIDType",
|
||||||
|
"GlobalID",
|
||||||
|
"Node",
|
||||||
"PageInfo",
|
"PageInfo",
|
||||||
|
"SimpleGlobalIDType",
|
||||||
|
"UUIDGlobalIDType",
|
||||||
|
"is_node",
|
||||||
]
|
]
|
||||||
|
|
87
graphene/relay/id_type.py
Normal file
87
graphene/relay/id_type.py
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
from graphql_relay import from_global_id, to_global_id
|
||||||
|
|
||||||
|
from ..types import ID, UUID
|
||||||
|
from ..types.base import BaseType
|
||||||
|
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
class BaseGlobalIDType:
|
||||||
|
"""
|
||||||
|
Base class that define the required attributes/method for a type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
graphene_type = ID # type: Type[BaseType]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
# return _type, _id
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
# return _id
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultGlobalIDType(BaseGlobalIDType):
|
||||||
|
"""
|
||||||
|
Default global ID type: base64 encoded version of "<node type name>: <node id>".
|
||||||
|
"""
|
||||||
|
|
||||||
|
graphene_type = ID
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
try:
|
||||||
|
_type, _id = from_global_id(global_id)
|
||||||
|
if not _type:
|
||||||
|
raise ValueError("Invalid Global ID")
|
||||||
|
return _type, _id
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(
|
||||||
|
f'Unable to parse global ID "{global_id}". '
|
||||||
|
'Make sure it is a base64 encoded string in the format: "TypeName:id". '
|
||||||
|
f"Exception message: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
return to_global_id(_type, _id)
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleGlobalIDType(BaseGlobalIDType):
|
||||||
|
"""
|
||||||
|
Simple global ID type: simply the id of the object.
|
||||||
|
To be used carefully as the user is responsible for ensuring that the IDs are indeed global
|
||||||
|
(otherwise it could cause request caching issues).
|
||||||
|
"""
|
||||||
|
|
||||||
|
graphene_type = ID
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
_type = info.return_type.graphene_type._meta.name
|
||||||
|
return _type, global_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
return _id
|
||||||
|
|
||||||
|
|
||||||
|
class UUIDGlobalIDType(BaseGlobalIDType):
|
||||||
|
"""
|
||||||
|
UUID global ID type.
|
||||||
|
By definition UUID are global so they are used as they are.
|
||||||
|
"""
|
||||||
|
|
||||||
|
graphene_type = UUID
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
_type = info.return_type.graphene_type._meta.name
|
||||||
|
return _type, global_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
return _id
|
|
@ -1,11 +1,10 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from inspect import isclass
|
from inspect import isclass
|
||||||
|
|
||||||
from graphql_relay import from_global_id, to_global_id
|
from ..types import Field, Interface, ObjectType
|
||||||
|
|
||||||
from ..types import ID, Field, Interface, ObjectType
|
|
||||||
from ..types.interface import InterfaceOptions
|
from ..types.interface import InterfaceOptions
|
||||||
from ..types.utils import get_type
|
from ..types.utils import get_type
|
||||||
|
from .id_type import BaseGlobalIDType, DefaultGlobalIDType
|
||||||
|
|
||||||
|
|
||||||
def is_node(objecttype):
|
def is_node(objecttype):
|
||||||
|
@ -22,8 +21,18 @@ def is_node(objecttype):
|
||||||
|
|
||||||
|
|
||||||
class GlobalID(Field):
|
class GlobalID(Field):
|
||||||
def __init__(self, node=None, parent_type=None, required=True, *args, **kwargs):
|
def __init__(
|
||||||
super(GlobalID, self).__init__(ID, required=required, *args, **kwargs)
|
self,
|
||||||
|
node=None,
|
||||||
|
parent_type=None,
|
||||||
|
required=True,
|
||||||
|
global_id_type=DefaultGlobalIDType,
|
||||||
|
*args,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super(GlobalID, self).__init__(
|
||||||
|
global_id_type.graphene_type, required=required, *args, **kwargs
|
||||||
|
)
|
||||||
self.node = node or Node
|
self.node = node or Node
|
||||||
self.parent_type_name = parent_type._meta.name if parent_type else None
|
self.parent_type_name = parent_type._meta.name if parent_type else None
|
||||||
|
|
||||||
|
@ -47,12 +56,14 @@ class NodeField(Field):
|
||||||
assert issubclass(node, Node), "NodeField can only operate in Nodes"
|
assert issubclass(node, Node), "NodeField can only operate in Nodes"
|
||||||
self.node_type = node
|
self.node_type = node
|
||||||
self.field_type = type_
|
self.field_type = type_
|
||||||
|
global_id_type = node._meta.global_id_type
|
||||||
|
|
||||||
super(NodeField, self).__init__(
|
super(NodeField, self).__init__(
|
||||||
# If we don's specify a type, the field type will be the node
|
# If we don't specify a type, the field type will be the node interface
|
||||||
# interface
|
|
||||||
type_ or node,
|
type_ or node,
|
||||||
id=ID(required=True, description="The ID of the object"),
|
id=global_id_type.graphene_type(
|
||||||
|
required=True, description="The ID of the object"
|
||||||
|
),
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -65,11 +76,23 @@ class AbstractNode(Interface):
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __init_subclass_with_meta__(cls, **options):
|
def __init_subclass_with_meta__(cls, global_id_type=DefaultGlobalIDType, **options):
|
||||||
|
assert issubclass(
|
||||||
|
global_id_type, BaseGlobalIDType
|
||||||
|
), "Custom ID type need to be implemented as a subclass of BaseGlobalIDType."
|
||||||
_meta = InterfaceOptions(cls)
|
_meta = InterfaceOptions(cls)
|
||||||
_meta.fields = {"id": GlobalID(cls, description="The ID of the object")}
|
_meta.global_id_type = global_id_type
|
||||||
|
_meta.fields = {
|
||||||
|
"id": GlobalID(
|
||||||
|
cls, global_id_type=global_id_type, description="The ID of the object"
|
||||||
|
)
|
||||||
|
}
|
||||||
super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
super(AbstractNode, cls).__init_subclass_with_meta__(_meta=_meta, **options)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
return cls._meta.global_id_type.resolve_global_id(info, global_id)
|
||||||
|
|
||||||
|
|
||||||
class Node(AbstractNode):
|
class Node(AbstractNode):
|
||||||
"""An object with an ID"""
|
"""An object with an ID"""
|
||||||
|
@ -84,16 +107,7 @@ class Node(AbstractNode):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_node_from_global_id(cls, info, global_id, only_type=None):
|
def get_node_from_global_id(cls, info, global_id, only_type=None):
|
||||||
try:
|
_type, _id = cls.resolve_global_id(info, global_id)
|
||||||
_type, _id = cls.from_global_id(global_id)
|
|
||||||
if not _type:
|
|
||||||
raise ValueError("Invalid Global ID")
|
|
||||||
except Exception as e:
|
|
||||||
raise Exception(
|
|
||||||
f'Unable to parse global ID "{global_id}". '
|
|
||||||
'Make sure it is a base64 encoded string in the format: "TypeName:id". '
|
|
||||||
f"Exception message: {e}"
|
|
||||||
)
|
|
||||||
|
|
||||||
graphene_type = info.schema.get_type(_type)
|
graphene_type = info.schema.get_type(_type)
|
||||||
if graphene_type is None:
|
if graphene_type is None:
|
||||||
|
@ -116,10 +130,6 @@ class Node(AbstractNode):
|
||||||
if get_node:
|
if get_node:
|
||||||
return get_node(info, _id)
|
return get_node(info, _id)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_global_id(cls, global_id):
|
|
||||||
return from_global_id(global_id)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def to_global_id(cls, type_, id):
|
def to_global_id(cls, type_, id):
|
||||||
return to_global_id(type_, id)
|
return cls._meta.global_id_type.to_global_id(type_, id)
|
||||||
|
|
325
graphene/relay/tests/test_custom_global_id.py
Normal file
325
graphene/relay/tests/test_custom_global_id.py
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
import re
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from graphql import graphql_sync
|
||||||
|
|
||||||
|
from ..id_type import BaseGlobalIDType, SimpleGlobalIDType, UUIDGlobalIDType
|
||||||
|
from ..node import Node
|
||||||
|
from ...types import Int, ObjectType, Schema, String
|
||||||
|
|
||||||
|
|
||||||
|
class TestUUIDGlobalID:
|
||||||
|
def setup(self):
|
||||||
|
self.user_list = [
|
||||||
|
{"id": uuid4(), "name": "First"},
|
||||||
|
{"id": uuid4(), "name": "Second"},
|
||||||
|
{"id": uuid4(), "name": "Third"},
|
||||||
|
{"id": uuid4(), "name": "Fourth"},
|
||||||
|
]
|
||||||
|
self.users = {user["id"]: user for user in self.user_list}
|
||||||
|
|
||||||
|
class CustomNode(Node):
|
||||||
|
class Meta:
|
||||||
|
global_id_type = UUIDGlobalIDType
|
||||||
|
|
||||||
|
class User(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = [CustomNode]
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, _type, _id):
|
||||||
|
return self.users[_id]
|
||||||
|
|
||||||
|
class RootQuery(ObjectType):
|
||||||
|
user = CustomNode.Field(User)
|
||||||
|
|
||||||
|
self.schema = Schema(query=RootQuery, types=[User])
|
||||||
|
self.graphql_schema = self.schema.graphql_schema
|
||||||
|
|
||||||
|
def test_str_schema_correct(self):
|
||||||
|
"""
|
||||||
|
Check that the schema has the expected and custom node interface and user type and that they both use UUIDs
|
||||||
|
"""
|
||||||
|
parsed = re.findall(r"(.+) \{\n\s*([\w\W]*?)\n\}", str(self.schema))
|
||||||
|
types = [t for t, f in parsed]
|
||||||
|
fields = [f for t, f in parsed]
|
||||||
|
custom_node_interface = "interface CustomNode"
|
||||||
|
assert custom_node_interface in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: UUID!'
|
||||||
|
== fields[types.index(custom_node_interface)]
|
||||||
|
)
|
||||||
|
user_type = "type User implements CustomNode"
|
||||||
|
assert user_type in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: UUID!\n name: String'
|
||||||
|
== fields[types.index(user_type)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_by_id(self):
|
||||||
|
query = """query userById($id: UUID!) {
|
||||||
|
user(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
# UUID need to be converted to string for serialization
|
||||||
|
result = graphql_sync(
|
||||||
|
self.graphql_schema,
|
||||||
|
query,
|
||||||
|
variable_values={"id": str(self.user_list[0]["id"])},
|
||||||
|
)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data["user"]["id"] == str(self.user_list[0]["id"])
|
||||||
|
assert result.data["user"]["name"] == self.user_list[0]["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestSimpleGlobalID:
|
||||||
|
def setup(self):
|
||||||
|
self.user_list = [
|
||||||
|
{"id": "my global primary key in clear 1", "name": "First"},
|
||||||
|
{"id": "my global primary key in clear 2", "name": "Second"},
|
||||||
|
{"id": "my global primary key in clear 3", "name": "Third"},
|
||||||
|
{"id": "my global primary key in clear 4", "name": "Fourth"},
|
||||||
|
]
|
||||||
|
self.users = {user["id"]: user for user in self.user_list}
|
||||||
|
|
||||||
|
class CustomNode(Node):
|
||||||
|
class Meta:
|
||||||
|
global_id_type = SimpleGlobalIDType
|
||||||
|
|
||||||
|
class User(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = [CustomNode]
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, _type, _id):
|
||||||
|
return self.users[_id]
|
||||||
|
|
||||||
|
class RootQuery(ObjectType):
|
||||||
|
user = CustomNode.Field(User)
|
||||||
|
|
||||||
|
self.schema = Schema(query=RootQuery, types=[User])
|
||||||
|
self.graphql_schema = self.schema.graphql_schema
|
||||||
|
|
||||||
|
def test_str_schema_correct(self):
|
||||||
|
"""
|
||||||
|
Check that the schema has the expected and custom node interface and user type and that they both use UUIDs
|
||||||
|
"""
|
||||||
|
parsed = re.findall(r"(.+) \{\n\s*([\w\W]*?)\n\}", str(self.schema))
|
||||||
|
types = [t for t, f in parsed]
|
||||||
|
fields = [f for t, f in parsed]
|
||||||
|
custom_node_interface = "interface CustomNode"
|
||||||
|
assert custom_node_interface in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: ID!'
|
||||||
|
== fields[types.index(custom_node_interface)]
|
||||||
|
)
|
||||||
|
user_type = "type User implements CustomNode"
|
||||||
|
assert user_type in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: ID!\n name: String'
|
||||||
|
== fields[types.index(user_type)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_by_id(self):
|
||||||
|
query = """query {
|
||||||
|
user(id: "my global primary key in clear 3") {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = graphql_sync(self.graphql_schema, query)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data["user"]["id"] == self.user_list[2]["id"]
|
||||||
|
assert result.data["user"]["name"] == self.user_list[2]["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestCustomGlobalID:
|
||||||
|
def setup(self):
|
||||||
|
self.user_list = [
|
||||||
|
{"id": 1, "name": "First"},
|
||||||
|
{"id": 2, "name": "Second"},
|
||||||
|
{"id": 3, "name": "Third"},
|
||||||
|
{"id": 4, "name": "Fourth"},
|
||||||
|
]
|
||||||
|
self.users = {user["id"]: user for user in self.user_list}
|
||||||
|
|
||||||
|
class CustomGlobalIDType(BaseGlobalIDType):
|
||||||
|
"""
|
||||||
|
Global id that is simply and integer in clear.
|
||||||
|
"""
|
||||||
|
|
||||||
|
graphene_type = Int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
_type = info.return_type.graphene_type._meta.name
|
||||||
|
return _type, global_id
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
return _id
|
||||||
|
|
||||||
|
class CustomNode(Node):
|
||||||
|
class Meta:
|
||||||
|
global_id_type = CustomGlobalIDType
|
||||||
|
|
||||||
|
class User(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = [CustomNode]
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, _type, _id):
|
||||||
|
return self.users[_id]
|
||||||
|
|
||||||
|
class RootQuery(ObjectType):
|
||||||
|
user = CustomNode.Field(User)
|
||||||
|
|
||||||
|
self.schema = Schema(query=RootQuery, types=[User])
|
||||||
|
self.graphql_schema = self.schema.graphql_schema
|
||||||
|
|
||||||
|
def test_str_schema_correct(self):
|
||||||
|
"""
|
||||||
|
Check that the schema has the expected and custom node interface and user type and that they both use UUIDs
|
||||||
|
"""
|
||||||
|
parsed = re.findall(r"(.+) \{\n\s*([\w\W]*?)\n\}", str(self.schema))
|
||||||
|
types = [t for t, f in parsed]
|
||||||
|
fields = [f for t, f in parsed]
|
||||||
|
custom_node_interface = "interface CustomNode"
|
||||||
|
assert custom_node_interface in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: Int!'
|
||||||
|
== fields[types.index(custom_node_interface)]
|
||||||
|
)
|
||||||
|
user_type = "type User implements CustomNode"
|
||||||
|
assert user_type in types
|
||||||
|
assert (
|
||||||
|
'"""The ID of the object"""\n id: Int!\n name: String'
|
||||||
|
== fields[types.index(user_type)]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_by_id(self):
|
||||||
|
query = """query {
|
||||||
|
user(id: 2) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = graphql_sync(self.graphql_schema, query)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data["user"]["id"] == self.user_list[1]["id"]
|
||||||
|
assert result.data["user"]["name"] == self.user_list[1]["name"]
|
||||||
|
|
||||||
|
|
||||||
|
class TestIncompleteCustomGlobalID:
|
||||||
|
def setup(self):
|
||||||
|
self.user_list = [
|
||||||
|
{"id": 1, "name": "First"},
|
||||||
|
{"id": 2, "name": "Second"},
|
||||||
|
{"id": 3, "name": "Third"},
|
||||||
|
{"id": 4, "name": "Fourth"},
|
||||||
|
]
|
||||||
|
self.users = {user["id"]: user for user in self.user_list}
|
||||||
|
|
||||||
|
def test_must_define_to_global_id(self):
|
||||||
|
"""
|
||||||
|
Test that if the `to_global_id` method is not defined, we can query the object, but we can't request its ID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CustomGlobalIDType(BaseGlobalIDType):
|
||||||
|
graphene_type = Int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def resolve_global_id(cls, info, global_id):
|
||||||
|
_type = info.return_type.graphene_type._meta.name
|
||||||
|
return _type, global_id
|
||||||
|
|
||||||
|
class CustomNode(Node):
|
||||||
|
class Meta:
|
||||||
|
global_id_type = CustomGlobalIDType
|
||||||
|
|
||||||
|
class User(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = [CustomNode]
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, _type, _id):
|
||||||
|
return self.users[_id]
|
||||||
|
|
||||||
|
class RootQuery(ObjectType):
|
||||||
|
user = CustomNode.Field(User)
|
||||||
|
|
||||||
|
self.schema = Schema(query=RootQuery, types=[User])
|
||||||
|
self.graphql_schema = self.schema.graphql_schema
|
||||||
|
|
||||||
|
query = """query {
|
||||||
|
user(id: 2) {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = graphql_sync(self.graphql_schema, query)
|
||||||
|
assert not result.errors
|
||||||
|
assert result.data["user"]["name"] == self.user_list[1]["name"]
|
||||||
|
|
||||||
|
query = """query {
|
||||||
|
user(id: 2) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = graphql_sync(self.graphql_schema, query)
|
||||||
|
assert result.errors is not None
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert result.errors[0].path == ["user", "id"]
|
||||||
|
|
||||||
|
def test_must_define_resolve_global_id(self):
|
||||||
|
"""
|
||||||
|
Test that if the `resolve_global_id` method is not defined, we can't query the object by ID.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CustomGlobalIDType(BaseGlobalIDType):
|
||||||
|
graphene_type = Int
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def to_global_id(cls, _type, _id):
|
||||||
|
return _id
|
||||||
|
|
||||||
|
class CustomNode(Node):
|
||||||
|
class Meta:
|
||||||
|
global_id_type = CustomGlobalIDType
|
||||||
|
|
||||||
|
class User(ObjectType):
|
||||||
|
class Meta:
|
||||||
|
interfaces = [CustomNode]
|
||||||
|
|
||||||
|
name = String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_node(cls, _type, _id):
|
||||||
|
return self.users[_id]
|
||||||
|
|
||||||
|
class RootQuery(ObjectType):
|
||||||
|
user = CustomNode.Field(User)
|
||||||
|
|
||||||
|
self.schema = Schema(query=RootQuery, types=[User])
|
||||||
|
self.graphql_schema = self.schema.graphql_schema
|
||||||
|
|
||||||
|
query = """query {
|
||||||
|
user(id: 2) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
result = graphql_sync(self.graphql_schema, query)
|
||||||
|
assert result.errors is not None
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert result.errors[0].path == ["user"]
|
|
@ -55,6 +55,7 @@ def test_node_good():
|
||||||
assert "id" in MyNode._meta.fields
|
assert "id" in MyNode._meta.fields
|
||||||
assert is_node(MyNode)
|
assert is_node(MyNode)
|
||||||
assert not is_node(object)
|
assert not is_node(object)
|
||||||
|
assert not is_node("node")
|
||||||
|
|
||||||
|
|
||||||
def test_node_query():
|
def test_node_query():
|
||||||
|
|
Loading…
Reference in New Issue
Block a user