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())