mirror of
https://github.com/graphql-python/graphene.git
synced 2024-11-25 19:13:57 +03:00
commit
2e41db8d95
38
graphene/utils/deduplicator.py
Normal file
38
graphene/utils/deduplicator.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from collections import Mapping, OrderedDict
|
||||
|
||||
|
||||
def deflate(node, index=None, path=None):
|
||||
if index is None:
|
||||
index = {}
|
||||
if path is None:
|
||||
path = []
|
||||
|
||||
if node and 'id' in node and '__typename' in node:
|
||||
route = ','.join(path)
|
||||
cache_key = ':'.join([route, str(node['__typename']), str(node['id'])])
|
||||
|
||||
if index.get(cache_key) is True:
|
||||
return {
|
||||
'__typename': node['__typename'],
|
||||
'id': node['id'],
|
||||
}
|
||||
else:
|
||||
index[cache_key] = True
|
||||
|
||||
field_names = node.keys()
|
||||
result = OrderedDict()
|
||||
|
||||
for field_name in field_names:
|
||||
value = node[field_name]
|
||||
|
||||
new_path = path + [field_name]
|
||||
if isinstance(value, (list, tuple)):
|
||||
result[field_name] = [
|
||||
deflate(child, index, new_path) for child in value
|
||||
]
|
||||
elif isinstance(value, Mapping):
|
||||
result[field_name] = deflate(value, index, new_path)
|
||||
else:
|
||||
result[field_name] = value
|
||||
|
||||
return result
|
257
graphene/utils/tests/test_deduplicator.py
Normal file
257
graphene/utils/tests/test_deduplicator.py
Normal file
|
@ -0,0 +1,257 @@
|
|||
import datetime
|
||||
import graphene
|
||||
from graphene import relay
|
||||
from graphene.types.resolver import dict_resolver
|
||||
|
||||
from ..deduplicator import deflate
|
||||
|
||||
|
||||
def test_does_not_modify_object_without_typename_and_id():
|
||||
response = {
|
||||
'foo': 'bar',
|
||||
}
|
||||
|
||||
deflated_response = deflate(response)
|
||||
assert deflated_response == {
|
||||
'foo': 'bar',
|
||||
}
|
||||
|
||||
|
||||
def test_does_not_modify_first_instance_of_an_object():
|
||||
response = {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
deflated_response = deflate(response)
|
||||
|
||||
assert deflated_response == {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_does_not_modify_first_instance_of_an_object_nested():
|
||||
response = {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'bar1': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'bar2': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'id': 1
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'bar1': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'bar2': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'id': 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
deflated_response = deflate(response)
|
||||
|
||||
assert deflated_response == {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'bar1': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'bar2': {
|
||||
'__typename': 'bar',
|
||||
'id': 1,
|
||||
'name': 'bar'
|
||||
},
|
||||
'id': 1
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'bar1': {
|
||||
'__typename': 'bar',
|
||||
'id': 1
|
||||
},
|
||||
'bar2': {
|
||||
'__typename': 'bar',
|
||||
'id': 1
|
||||
},
|
||||
'id': 2
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_does_not_modify_input():
|
||||
response = {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
deflate(response)
|
||||
|
||||
assert response == {
|
||||
'data': [
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
},
|
||||
{
|
||||
'__typename': 'foo',
|
||||
'id': 1,
|
||||
'name': 'foo'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
TEST_DATA = {
|
||||
'events': [
|
||||
{
|
||||
'id': '568',
|
||||
'date': datetime.date(2017, 5, 19),
|
||||
'movie': '1198359',
|
||||
},
|
||||
{
|
||||
'id': '234',
|
||||
'date': datetime.date(2017, 5, 20),
|
||||
'movie': '1198359',
|
||||
},
|
||||
],
|
||||
'movies': {
|
||||
'1198359': {
|
||||
'name': 'King Arthur: Legend of the Sword',
|
||||
'synopsis': (
|
||||
"When the child Arthur's father is murdered, Vortigern, "
|
||||
"Arthur's uncle, seizes the crown. Robbed of his birthright and "
|
||||
"with no idea who he truly is..."
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_example_end_to_end():
|
||||
class Movie(graphene.ObjectType):
|
||||
class Meta:
|
||||
interfaces = (relay.Node,)
|
||||
default_resolver = dict_resolver
|
||||
|
||||
name = graphene.String(required=True)
|
||||
synopsis = graphene.String(required=True)
|
||||
|
||||
class Event(graphene.ObjectType):
|
||||
class Meta:
|
||||
interfaces = (relay.Node,)
|
||||
default_resolver = dict_resolver
|
||||
|
||||
movie = graphene.Field(Movie, required=True)
|
||||
date = graphene.types.datetime.Date(required=True)
|
||||
|
||||
def resolve_movie(event, info):
|
||||
return TEST_DATA['movies'][event['movie']]
|
||||
|
||||
class Query(graphene.ObjectType):
|
||||
events = graphene.List(
|
||||
graphene.NonNull(Event),
|
||||
required=True
|
||||
)
|
||||
|
||||
def resolve_events(_, info):
|
||||
return TEST_DATA['events']
|
||||
|
||||
schema = graphene.Schema(query=Query)
|
||||
query = """\
|
||||
{
|
||||
events {
|
||||
__typename
|
||||
id
|
||||
date
|
||||
movie {
|
||||
__typename
|
||||
id
|
||||
name
|
||||
synopsis
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
result = schema.execute(query)
|
||||
assert not result.errors
|
||||
|
||||
result.data = deflate(result.data)
|
||||
assert result.data == {
|
||||
'events': [
|
||||
{
|
||||
'__typename': 'Event',
|
||||
'id': 'RXZlbnQ6NTY4',
|
||||
'date': '2017-05-19',
|
||||
'movie': {
|
||||
'__typename': 'Movie',
|
||||
'id': 'TW92aWU6Tm9uZQ==',
|
||||
'name': 'King Arthur: Legend of the Sword',
|
||||
'synopsis': (
|
||||
"When the child Arthur's father is murdered, Vortigern, "
|
||||
"Arthur's uncle, seizes the crown. Robbed of his birthright and "
|
||||
"with no idea who he truly is..."
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
'__typename': 'Event',
|
||||
'id': 'RXZlbnQ6MjM0',
|
||||
'date': '2017-05-20',
|
||||
'movie': {
|
||||
'__typename': 'Movie',
|
||||
'id': 'TW92aWU6Tm9uZQ==',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
Loading…
Reference in New Issue
Block a user