mirror of
https://github.com/graphql-python/graphene.git
synced 2024-12-01 22:14:02 +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