Repository: graphql-python/graphene
Pull request #787 (2018-07-01 20:12:56)

Crunch response data
Similar to https://github.com/graphql-python/graphene/pull/786 this PR implements graphql-crunch in python.

Unfortunately I'm not seeing any benefit when running it on our code base (it actually increases response size by a bit) but hopefully it's useful for someone else.


Patch:
diff --git a/docs/types/mutations.rst b/docs/types/mutations.rst
index 6949008..70c80d0 100644
--- a/docs/types/mutations.rst
+++ b/docs/types/mutations.rst
@@ -76,7 +76,7 @@ We should receive:
     {
         "createPerson": {
             "person" : {
-                "name": "Peter"
+                name: "Peter"
             },
             "ok": true
         }
@@ -116,7 +116,7 @@ Note that  **name** and **age** are part of **person_data** now
 
 Using the above mutation your new query would look like this:
 
-.. code::
+.. code:: json
 
     mutation myFirstMutation {
         createPerson(personData: {name:"Peter", age: 24}) {
@@ -143,41 +143,3 @@ as complex of input data as you need
         name = graphene.String()
         latlng = graphene.InputField(LatLngInput)
 
-Output type example
--------------------
-To return an existing ObjectType instead of a mutation-specific type, set the **Output** attribute to the desired ObjectType:
-
-.. code:: python
-
-    import graphene
-
-    class CreatePerson(graphene.Mutation):
-        class Arguments:
-            name = graphene.String()
-
-        Output = Person
-
-        def mutate(self, info, name):
-            return Person(name=name)
-
-Then, if we query (``schema.execute(query_str)``) the following:
-
-.. code::
-
-    mutation myFirstMutation {
-        createPerson(name:"Peter") {
-            name
-            __typename
-        }
-    }
-
-We should receive:
-
-.. code:: json
-
-    {
-        "createPerson": {
-            "name": "Peter",
-            "__typename": "Person"
-        }
-    }
diff --git a/graphene/utils/crunch.py b/graphene/utils/crunch.py
new file mode 100644
index 0000000..16902e2
--- /dev/null
+++ b/graphene/utils/crunch.py
@@ -0,0 +1,37 @@
+import json
+from collections import Mapping
+
+
+def to_key(value):
+    return json.dumps(value)
+
+
+def insert(value, index, values):
+    key = to_key(value)
+
+    if key not in index:
+        index[key] = len(values)
+        values.append(value)
+        return len(values) - 1
+
+    return index.get(key)
+
+
+def flatten(data, index, values):
+    if isinstance(data, (list, tuple)):
+        flattened = [flatten(child, index, values) for child in data]
+    elif isinstance(data, Mapping):
+        flattened = {
+            key: flatten(child, index, values) for key, child in data.items()
+        }
+    else:
+        flattened = data
+    return insert(flattened, index, values)
+
+
+def crunch(data):
+    index = {}
+    values = []
+
+    flatten(data, index, values)
+    return values


Test patch:
diff --git a/graphene/utils/tests/test_crunch.py b/graphene/utils/tests/test_crunch.py
new file mode 100644
index 0000000..6dd396c
--- /dev/null
+++ b/graphene/utils/tests/test_crunch.py
@@ -0,0 +1,48 @@
+import pytest
+from collections import OrderedDict
+
+from ..crunch import crunch
+
+
+@pytest.mark.parametrize("description,uncrunched,crunched", [
+    ['number primitive', 0, [0]],
+    ['boolean primitive', True, [True]],
+    ['string primitive', "string", ["string"]],
+    ['empty array', [], [[]]],
+    ['single-item array', [None], [None, [0]]],
+    ['multi-primitive all distinct array', [None, 0, True, "string"], [None, 0, True, "string", [0, 1, 2, 3]]],
+    ['multi-primitive repeated array', [True, True, True, True], [True, [0, 0, 0, 0]]],
+    ['one-level nested array', [[1, 2, 3]], [1, 2, 3, [0, 1, 2], [3]]],
+    ['two-level nested array', [[[1, 2, 3]]], [1, 2, 3, [0, 1, 2], [3], [4]]],
+    ['empty object', {}, [{}]],
+    ['single-item object', {'a': None}, [None, {'a': 0}]],
+    [
+        'multi-item all distinct object',
+        OrderedDict([('a', None), ('b', 0), ('c', True), ('d', 'string')]),
+        [None, 0, True, "string", {'a': 0, 'b': 1, 'c': 2, 'd': 3}]
+    ],
+    [
+        'multi-item repeated object',
+        OrderedDict([('a', True), ('b', True), ('c', True), ('d', True)]),
+        [True, {'a': 0, 'b': 0, 'c': 0, 'd': 0}]
+    ],
+    [
+        'complex array',
+        [
+            OrderedDict([('a', True), ('b', [1, 2, 3])]),
+            [1, 2, 3]
+        ],
+        [True, 1, 2, 3, [1, 2, 3], {'a': 0, 'b': 4}, [5, 4]]
+    ],
+    [
+        'complex object',
+        OrderedDict([
+            ('a', True),
+            ('b', [1, 2, 3]),
+            ('c', OrderedDict([('a', True), ('b', [1, 2, 3])]))
+        ]),
+        [True, 1, 2, 3, [1, 2, 3], {'a': 0, 'b': 4}, {'a': 0, 'b': 4, 'c': 5}]
+    ],
+])
+def test_crunch(description, uncrunched, crunched):
+    assert crunch(uncrunched) == crunched