Skip to content

Commit 2e41db8

Browse files
authored
Merge pull request #786 from jkimbo/deduplicator
Deduplicator
2 parents fa5f5b0 + 9ce78e3 commit 2e41db8

File tree

2 files changed

+295
-0
lines changed

2 files changed

+295
-0
lines changed

graphene/utils/deduplicator.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
from collections import Mapping, OrderedDict
2+
3+
4+
def deflate(node, index=None, path=None):
5+
if index is None:
6+
index = {}
7+
if path is None:
8+
path = []
9+
10+
if node and 'id' in node and '__typename' in node:
11+
route = ','.join(path)
12+
cache_key = ':'.join([route, str(node['__typename']), str(node['id'])])
13+
14+
if index.get(cache_key) is True:
15+
return {
16+
'__typename': node['__typename'],
17+
'id': node['id'],
18+
}
19+
else:
20+
index[cache_key] = True
21+
22+
field_names = node.keys()
23+
result = OrderedDict()
24+
25+
for field_name in field_names:
26+
value = node[field_name]
27+
28+
new_path = path + [field_name]
29+
if isinstance(value, (list, tuple)):
30+
result[field_name] = [
31+
deflate(child, index, new_path) for child in value
32+
]
33+
elif isinstance(value, Mapping):
34+
result[field_name] = deflate(value, index, new_path)
35+
else:
36+
result[field_name] = value
37+
38+
return result
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import datetime
2+
import graphene
3+
from graphene import relay
4+
from graphene.types.resolver import dict_resolver
5+
6+
from ..deduplicator import deflate
7+
8+
9+
def test_does_not_modify_object_without_typename_and_id():
10+
response = {
11+
'foo': 'bar',
12+
}
13+
14+
deflated_response = deflate(response)
15+
assert deflated_response == {
16+
'foo': 'bar',
17+
}
18+
19+
20+
def test_does_not_modify_first_instance_of_an_object():
21+
response = {
22+
'data': [
23+
{
24+
'__typename': 'foo',
25+
'id': 1,
26+
'name': 'foo'
27+
},
28+
{
29+
'__typename': 'foo',
30+
'id': 1,
31+
'name': 'foo'
32+
}
33+
]
34+
}
35+
36+
deflated_response = deflate(response)
37+
38+
assert deflated_response == {
39+
'data': [
40+
{
41+
'__typename': 'foo',
42+
'id': 1,
43+
'name': 'foo'
44+
},
45+
{
46+
'__typename': 'foo',
47+
'id': 1
48+
}
49+
]
50+
}
51+
52+
53+
def test_does_not_modify_first_instance_of_an_object_nested():
54+
response = {
55+
'data': [
56+
{
57+
'__typename': 'foo',
58+
'bar1': {
59+
'__typename': 'bar',
60+
'id': 1,
61+
'name': 'bar'
62+
},
63+
'bar2': {
64+
'__typename': 'bar',
65+
'id': 1,
66+
'name': 'bar'
67+
},
68+
'id': 1
69+
},
70+
{
71+
'__typename': 'foo',
72+
'bar1': {
73+
'__typename': 'bar',
74+
'id': 1,
75+
'name': 'bar'
76+
},
77+
'bar2': {
78+
'__typename': 'bar',
79+
'id': 1,
80+
'name': 'bar'
81+
},
82+
'id': 2
83+
}
84+
]
85+
}
86+
87+
deflated_response = deflate(response)
88+
89+
assert deflated_response == {
90+
'data': [
91+
{
92+
'__typename': 'foo',
93+
'bar1': {
94+
'__typename': 'bar',
95+
'id': 1,
96+
'name': 'bar'
97+
},
98+
'bar2': {
99+
'__typename': 'bar',
100+
'id': 1,
101+
'name': 'bar'
102+
},
103+
'id': 1
104+
},
105+
{
106+
'__typename': 'foo',
107+
'bar1': {
108+
'__typename': 'bar',
109+
'id': 1
110+
},
111+
'bar2': {
112+
'__typename': 'bar',
113+
'id': 1
114+
},
115+
'id': 2
116+
}
117+
]
118+
}
119+
120+
121+
def test_does_not_modify_input():
122+
response = {
123+
'data': [
124+
{
125+
'__typename': 'foo',
126+
'id': 1,
127+
'name': 'foo'
128+
},
129+
{
130+
'__typename': 'foo',
131+
'id': 1,
132+
'name': 'foo'
133+
}
134+
]
135+
}
136+
137+
deflate(response)
138+
139+
assert response == {
140+
'data': [
141+
{
142+
'__typename': 'foo',
143+
'id': 1,
144+
'name': 'foo'
145+
},
146+
{
147+
'__typename': 'foo',
148+
'id': 1,
149+
'name': 'foo'
150+
}
151+
]
152+
}
153+
154+
155+
TEST_DATA = {
156+
'events': [
157+
{
158+
'id': '568',
159+
'date': datetime.date(2017, 5, 19),
160+
'movie': '1198359',
161+
},
162+
{
163+
'id': '234',
164+
'date': datetime.date(2017, 5, 20),
165+
'movie': '1198359',
166+
},
167+
],
168+
'movies': {
169+
'1198359': {
170+
'name': 'King Arthur: Legend of the Sword',
171+
'synopsis': (
172+
"When the child Arthur's father is murdered, Vortigern, "
173+
"Arthur's uncle, seizes the crown. Robbed of his birthright and "
174+
"with no idea who he truly is..."
175+
),
176+
},
177+
},
178+
}
179+
180+
181+
def test_example_end_to_end():
182+
class Movie(graphene.ObjectType):
183+
class Meta:
184+
interfaces = (relay.Node,)
185+
default_resolver = dict_resolver
186+
187+
name = graphene.String(required=True)
188+
synopsis = graphene.String(required=True)
189+
190+
class Event(graphene.ObjectType):
191+
class Meta:
192+
interfaces = (relay.Node,)
193+
default_resolver = dict_resolver
194+
195+
movie = graphene.Field(Movie, required=True)
196+
date = graphene.types.datetime.Date(required=True)
197+
198+
def resolve_movie(event, info):
199+
return TEST_DATA['movies'][event['movie']]
200+
201+
class Query(graphene.ObjectType):
202+
events = graphene.List(
203+
graphene.NonNull(Event),
204+
required=True
205+
)
206+
207+
def resolve_events(_, info):
208+
return TEST_DATA['events']
209+
210+
schema = graphene.Schema(query=Query)
211+
query = """\
212+
{
213+
events {
214+
__typename
215+
id
216+
date
217+
movie {
218+
__typename
219+
id
220+
name
221+
synopsis
222+
}
223+
}
224+
}
225+
"""
226+
result = schema.execute(query)
227+
assert not result.errors
228+
229+
result.data = deflate(result.data)
230+
assert result.data == {
231+
'events': [
232+
{
233+
'__typename': 'Event',
234+
'id': 'RXZlbnQ6NTY4',
235+
'date': '2017-05-19',
236+
'movie': {
237+
'__typename': 'Movie',
238+
'id': 'TW92aWU6Tm9uZQ==',
239+
'name': 'King Arthur: Legend of the Sword',
240+
'synopsis': (
241+
"When the child Arthur's father is murdered, Vortigern, "
242+
"Arthur's uncle, seizes the crown. Robbed of his birthright and "
243+
"with no idea who he truly is..."
244+
),
245+
},
246+
},
247+
{
248+
'__typename': 'Event',
249+
'id': 'RXZlbnQ6MjM0',
250+
'date': '2017-05-20',
251+
'movie': {
252+
'__typename': 'Movie',
253+
'id': 'TW92aWU6Tm9uZQ==',
254+
},
255+
},
256+
],
257+
}

0 commit comments

Comments
 (0)