Skip to content

Commit

Permalink
Merge pull request #786 from jkimbo/deduplicator
Browse files Browse the repository at this point in the history
Deduplicator
  • Loading branch information
syrusakbary committed Jul 2, 2018
2 parents fa5f5b0 + 9ce78e3 commit 2e41db8
Show file tree
Hide file tree
Showing 2 changed files with 295 additions and 0 deletions.
38 changes: 38 additions & 0 deletions graphene/utils/deduplicator.py
@@ -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 changes: 257 additions & 0 deletions graphene/utils/tests/test_deduplicator.py
@@ -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==',
},
},
],
}

0 comments on commit 2e41db8

Please sign in to comment.