You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2020/08/25 11:50:04 UTC

[tinkerpop] 02/02: TINKERPOP-2395 Added deserialization support for dict as keys

This is an automated email from the ASF dual-hosted git repository.

spmallette pushed a commit to branch TINKERPOP-2407
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git

commit 9913ae68e72d7e47162d1513da7f9d8cda17aee7
Author: Stephen Mallette <sp...@genoprime.com>
AuthorDate: Tue Aug 25 07:26:32 2020 -0400

    TINKERPOP-2395 Added deserialization support for dict as keys
---
 CHANGELOG.asciidoc                                 |  1 +
 docs/src/upgrade/release-3.5.x.asciidoc            | 14 ++++++++
 .../gremlin_python/structure/io/graphbinaryV1.py   |  3 +-
 .../gremlin_python/structure/io/graphsonV3d0.py    | 25 ++-------------
 .../python/gremlin_python/structure/io/util.py     | 37 ++++++++++++++++++++++
 .../tests/driver/test_driver_remote_connection.py  | 15 +++++----
 6 files changed, 65 insertions(+), 30 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 059b507..2942e1c 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -30,6 +30,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 * Ensured better consistency of the use of `null` as arguments to mutation steps.
 * Allowed `property(T.label,Object)` to be used if no value was supplied to `addV(String)`.
 * Allowed additional arguments to `Client.submit()` in Javascript driver to enable setting of parameters like `scriptEvaluationTimeout`.
+* Supported deserialization of `dict` as a key in a `dict` for Python.
 * Added a `Graph.Feature` for `supportsNullPropertyValues`.
 * Modified `TokenTraversal` to support `Property` thus `by(key)` and `by(value)` can now apply to `Edge` and meta-properties.
 * Added `SeedStrategy` to allow deterministic behavior for `coin()`, `sample()` and `Order.shuffle`.
diff --git a/docs/src/upgrade/release-3.5.x.asciidoc b/docs/src/upgrade/release-3.5.x.asciidoc
index 635c2d4..2ff012f 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -292,6 +292,20 @@ appropriate token versions automatically.
 
 See: link:https://issues.apache.org/jira/browse/TINKERPOP-1682[TINKERPOP-1682]
 
+==== Complex dict Deserialization
+
+In Gremlin it is common to return a `dict` as a key value in another `dict`. The problem for Python is that a `dict`
+is not hashable and will result in an error. By introducing a `HashableDict` for those keys, it is now possible to
+return these types of results and not have to work around them:
+
+[source,text]
+----
+>>> g.V().has('person', 'name', 'marko').elementMap("name").groupCount().next()
+{{<T.id: 1>: 1, <T.label: 4>: 'person', 'name': 'marko'}: 1}
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2395[TINKERPOP-2395]
+
 ==== Python 2.x Support
 
 The gremlinpython module no longer supports Python 2.x. Users must use Python 3 going forward. For the most part, from
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
index c1fd32e..a5a258a 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py
@@ -38,6 +38,7 @@ from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardina
                                              TraversalStrategy, T
 from gremlin_python.process.graph_traversal import GraphTraversal
 from gremlin_python.structure.graph import Graph, Edge, Property, Vertex, VertexProperty, Path
+from gremlin_python.structure.io.util import HashableDict
 
 log = logging.getLogger(__name__)
 
@@ -485,7 +486,7 @@ class MapIO(_GraphBinaryTypeIO):
         size = cls.read_int(b)
         the_dict = {}
         while size > 0:
-            k = r.readObject(b)
+            k = HashableDict.of(r.readObject(b))
             v = r.readObject(b)
             the_dict[k] = v
             size = size - 1
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV3d0.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV3d0.py
index 4d5a288..9ba2127 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV3d0.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphsonV3d0.py
@@ -32,6 +32,7 @@ from gremlin_python import statics
 from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, TypeType, DictType, ListType, SetType, SingleByte, ByteBufferType, SingleChar
 from gremlin_python.process.traversal import Binding, Bytecode, Direction, P, TextP, Traversal, Traverser, TraversalStrategy, T
 from gremlin_python.structure.graph import Edge, Property, Vertex, VertexProperty, Path
+from gremlin_python.structure.io.util import HashableDict
 
 log = logging.getLogger(__name__)
 
@@ -41,28 +42,6 @@ log = logging.getLogger(__name__)
 _serializers = OrderedDict()
 _deserializers = {}
 
-
-class hashable_dict(dict):
-    def __hash__(self):
-        try:
-            return hash(tuple(sorted(self.items())))
-        except:
-            return hash(tuple(sorted(str(x) for x in self.items())))
-
-    @classmethod
-    def of(cls, o):
-        if isinstance(o, (tuple, set, list)):
-            return tuple([cls.of(e) for e in o])
-        elif not isinstance(o, (dict, hashable_dict)):
-            return o
-
-        new_o = hashable_dict()
-        for k, v in o.items():
-            new_o[k] = cls.of(v)
-        return new_o
-
-
-
 class GraphSONTypeType(type):
     def __new__(mcs, name, bases, dct):
         cls = super(GraphSONTypeType, mcs).__new__(mcs, name, bases, dct)
@@ -497,7 +476,7 @@ class MapType(_GraphSONTypeIO):
         if len(l) > 0:
             x = 0
             while x < len(l):
-                new_dict[hashable_dict.of(reader.toObject(l[x]))] = reader.toObject(l[x + 1])
+                new_dict[HashableDict.of(reader.toObject(l[x]))] = reader.toObject(l[x + 1])
                 x = x + 2
         return new_dict
 
diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/util.py b/gremlin-python/src/main/python/gremlin_python/structure/io/util.py
new file mode 100644
index 0000000..86fd72a
--- /dev/null
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/util.py
@@ -0,0 +1,37 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+
+class HashableDict(dict):
+    def __hash__(self):
+        try:
+            return hash(tuple(sorted(self.items())))
+        except:
+            return hash(tuple(sorted(str(x) for x in self.items())))
+
+    @classmethod
+    def of(cls, o):
+        if isinstance(o, (tuple, set, list)):
+            return tuple([cls.of(e) for e in o])
+        elif not isinstance(o, (dict, HashableDict)):
+            return o
+
+        new_o = HashableDict()
+        for k, v in o.items():
+            new_o[k] = cls.of(v)
+        return new_o
+
diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
index 663858e..24cdffb 100644
--- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
+++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py
@@ -18,21 +18,19 @@
 #
 import pytest
 
-from tornado import ioloop, gen
-
 from gremlin_python import statics
 from gremlin_python.driver.protocol import GremlinServerError
 from gremlin_python.statics import long
-from gremlin_python.driver.driver_remote_connection import (
-    DriverRemoteConnection)
 from gremlin_python.process.traversal import Traverser
 from gremlin_python.process.traversal import TraversalStrategy
 from gremlin_python.process.traversal import Bindings
-from gremlin_python.process.traversal import P, Order
+from gremlin_python.process.traversal import P, Order, T
 from gremlin_python.process.graph_traversal import __
 from gremlin_python.process.anonymous_traversal import traversal
 from gremlin_python.structure.graph import Vertex
 from gremlin_python.process.strategies import SubgraphStrategy, ReservedKeysVerificationStrategy, SeedStrategy
+from gremlin_python.structure.io.util import HashableDict
+from gremlin_python.driver.serializer import GraphSONSerializersV2d0
 
 __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)'
 
@@ -99,7 +97,12 @@ class TestDriverRemoteConnection(object):
         # test binding in P
         results = g.V().has('person', 'age', Bindings.of('x', lt(30))).count().next()
         assert 2 == results
-
+        # #
+        # test dict keys which can only work on GraphBinary and GraphSON3 which include specific serialization
+        # types for dict
+        if not isinstance(remote_connection._client._message_serializer, GraphSONSerializersV2d0):
+            results = g.V().has('person', 'name', 'marko').elementMap("name").groupCount().next()
+            assert {HashableDict.of({T.id: 1, T.label: 'person', 'name': 'marko'}): 1} == results
 
     def test_lambda_traversals(self, remote_connection):
         statics.load_statics(globals())