This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch TINKERPOP-3232 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 163e1d03bbb49565f2082a572fddd0d3b89e50d8 Author: Yang Xia <[email protected]> AuthorDate: Wed Mar 25 16:07:44 2026 -0700 coerce set into list when it contains non-hashable elements --- .../gremlin_python/structure/io/graphbinaryV1.py | 4 +- .../gremlin_python/structure/io/graphsonV3d0.py | 6 ++- .../driver/test_driver_remote_connection.py | 5 ++- .../python/tests/unit/io/test_graphbinaryV1.py | 44 +++++++--------------- .../main/python/tests/unit/io/test_graphsonV3d0.py | 21 +++++------ 5 files changed, 33 insertions(+), 47 deletions(-) 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 b9b3338d82..39a0c3a678 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 @@ -524,7 +524,9 @@ class SetDeserializer(ListIO): try: return set(the_list) except TypeError: - return set(HashableDict.of(e) for e in the_list) + log.warning("Coercing Set to list as it contains unhashable elements (e.g. dict, list). " + "See TINKERPOP-3232 for more details.") + return the_list class MapIO(_GraphBinaryTypeIO): 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 4d25816abd..c032e2fd88 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 @@ -440,14 +440,16 @@ class SetIO(_GraphSONTypeIO): See comments of TINKERPOP-1844 for more details In case the set contains non-hashable elements (e.g. dict, list), - use HashableDict.of() to make them hashable. + coerce and return a list. See TINKERPOP-3232 for more details """ new_list = [reader.to_object(obj) for obj in s] try: new_set = set(new_list) except TypeError: - new_set = set(HashableDict.of(e) for e in new_list) + log.warning("Coercing g:Set to list as it contains unhashable elements (e.g. dict, list). " + "See TINKERPOP-3232 for more details.") + return new_list if len(new_list) != len(new_set): log.warning("Coercing g:Set to list due to java numeric values. " "See TINKERPOP-1844 for more details.") diff --git a/gremlin-python/src/main/python/tests/integration/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/integration/driver/test_driver_remote_connection.py index 72f45b8aa9..77e5516614 100644 --- a/gremlin-python/src/main/python/tests/integration/driver/test_driver_remote_connection.py +++ b/gremlin-python/src/main/python/tests/integration/driver/test_driver_remote_connection.py @@ -152,11 +152,12 @@ class TestDriverRemoteConnection(object): return g = traversal().withRemote(remote_connection) # g.V().valueMap().dedup(Scope.local) returns a Set of Map results which previously - # threw TypeError because Python sets cannot contain unhashable dict elements + # threw TypeError because Python sets cannot contain unhashable dict elements. + # The Set is now coerced to a list when it contains unhashable elements. results = g.V().valueMap().dedup(Scope.local).toList() assert len(results) > 0 for r in results: - assert isinstance(r, set) + assert isinstance(r, list) def test_lambda_traversals(self, remote_connection): statics.load_statics(globals()) diff --git a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py index 30105c6659..b1234369c1 100644 --- a/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py +++ b/gremlin-python/src/main/python/tests/unit/io/test_graphbinaryV1.py @@ -24,7 +24,6 @@ import math from gremlin_python.statics import timestamp, long, bigint, BigDecimal, SingleByte, SingleChar, ByteBufferType from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path from gremlin_python.structure.io.graphbinaryV1 import GraphBinaryWriter, GraphBinaryReader -from gremlin_python.structure.io.util import HashableDict from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Merge, Direction @@ -124,62 +123,45 @@ class TestGraphSONWriter(object): assert x == output def test_set_with_unhashable_dict_elements(self): - # test that sets containing dicts can be deserialized - see TINKERPOP-3232 + # test that sets containing dicts are coerced to list - see TINKERPOP-3232 x = [{"name": "marko", "age": 29}, {"name": "josh", "age": 32}] - output = self.graphbinary_reader.read_object(self.graphbinary_writer.write_object(set( - HashableDict.of(e) for e in x))) - assert isinstance(output, set) + list_payload = self.graphbinary_writer.write_object(x) + # patch outer type from list (0x09) to set (0x0b) + set_payload = bytearray(list_payload) + set_payload[0] = 0x0b + output = self.graphbinary_reader.read_object(set_payload) + assert isinstance(output, list) assert len(output) == 2 def test_set_with_unhashable_list_elements(self): - # test that sets containing lists can be deserialized - see TINKERPOP-3232 - # build a set payload manually: write as list-of-lists, then patch the type byte to set + # test that sets containing lists are coerced to list - see TINKERPOP-3232 list_payload = self.graphbinary_writer.write_object([["marko", "josh"], ["vadas", "peter"]]) # the first byte is the DataType for list (0x09), change it to set (0x0b) set_payload = bytearray(list_payload) set_payload[0] = 0x0b output = self.graphbinary_reader.read_object(set_payload) - assert isinstance(output, set) - assert len(output) == 2 - - def test_set_with_unhashable_set_elements(self): - # test that sets containing sets can be deserialized - see TINKERPOP-3232 - # build a set-of-sets payload: write as list-of-lists, patch outer and inner type bytes - inner1 = self.graphbinary_writer.write_object(["a", "b"]) - inner2 = self.graphbinary_writer.write_object(["c", "d"]) - # patch inner payloads from list (0x09) to set (0x0b) - inner1 = bytearray(inner1) - inner1[0] = 0x0b - inner2 = bytearray(inner2) - inner2[0] = 0x0b - # build outer set: type byte (set=0x0b) + nullable (0x00) + count (2) + inner payloads - import struct - outer = bytearray([0x0b, 0x00]) + struct.pack('>i', 2) + inner1 + inner2 - output = self.graphbinary_reader.read_object(outer) - assert isinstance(output, set) + assert isinstance(output, list) assert len(output) == 2 def test_set_with_mixed_hashable_and_unhashable_elements(self): - # test that sets containing a mix of hashable and unhashable elements work - see TINKERPOP-3232 - # build payload: write a list of [string, dict, int], then patch type to set + # test that sets containing a mix of hashable and unhashable elements are coerced to list - see TINKERPOP-3232 x = ["marko", {"name": "josh"}, 42] list_payload = self.graphbinary_writer.write_object(x) set_payload = bytearray(list_payload) set_payload[0] = 0x0b output = self.graphbinary_reader.read_object(set_payload) - assert isinstance(output, set) + assert isinstance(output, list) assert len(output) == 3 def test_set_with_nested_unhashable_elements(self): - # test that sets containing dicts with list values can be deserialized - see TINKERPOP-3232 - # build payload manually since HashableDict.of() converts lists to tuples which can't be serialized + # test that sets containing dicts with list values are coerced to list - see TINKERPOP-3232 x = [{"name": "marko", "langs": ["java", "python"]}, {"name": "josh", "langs": ["gremlin"]}] list_payload = self.graphbinary_writer.write_object(x) # patch outer type from list (0x09) to set (0x0b) set_payload = bytearray(list_payload) set_payload[0] = 0x0b output = self.graphbinary_reader.read_object(set_payload) - assert isinstance(output, set) + assert isinstance(output, list) assert len(output) == 2 def test_dict(self): diff --git a/gremlin-python/src/main/python/tests/unit/io/test_graphsonV3d0.py b/gremlin-python/src/main/python/tests/unit/io/test_graphsonV3d0.py index 027fb3a4bd..3a03fff7cd 100644 --- a/gremlin-python/src/main/python/tests/unit/io/test_graphsonV3d0.py +++ b/gremlin-python/src/main/python/tests/unit/io/test_graphsonV3d0.py @@ -32,7 +32,6 @@ from gremlin_python.statics import * from gremlin_python.structure.graph import Vertex, Edge, Property, VertexProperty, Path from gremlin_python.structure.io.graphsonV3d0 import GraphSONWriter, GraphSONReader, GraphSONUtil import gremlin_python.structure.io.graphsonV3d0 -from gremlin_python.structure.io.util import HashableDict from gremlin_python.process.traversal import P, Merge, Barrier, Order, Operator, Direction from gremlin_python.process.strategies import SubgraphStrategy from gremlin_python.process.graph_traversal import __ @@ -88,48 +87,48 @@ class TestGraphSONReader: assert x.count("josh") == 3 def test_set_with_unhashable_dict_elements(self): - # test that sets containing dicts can be deserialized - see TINKERPOP-3232 + # test that sets containing dicts are coerced to list - see TINKERPOP-3232 x = self.graphson_reader.read_object( json.dumps({"@type": "g:Set", "@value": [ {"@type": "g:Map", "@value": ["name", "marko", "age", {"@type": "g:Int32", "@value": 29}]}, {"@type": "g:Map", "@value": ["name", "josh", "age", {"@type": "g:Int32", "@value": 32}]} ]})) - assert isinstance(x, set) + assert isinstance(x, list) assert len(x) == 2 def test_set_with_unhashable_list_elements(self): - # test that sets containing lists can be deserialized - see TINKERPOP-3232 + # test that sets containing lists are coerced to list - see TINKERPOP-3232 x = self.graphson_reader.read_object( json.dumps({"@type": "g:Set", "@value": [ {"@type": "g:List", "@value": ["marko", "josh"]}, {"@type": "g:List", "@value": ["vadas", "peter"]} ]})) - assert isinstance(x, set) + assert isinstance(x, list) assert len(x) == 2 def test_set_with_unhashable_set_elements(self): - # test that sets containing sets can be deserialized - see TINKERPOP-3232 + # test that sets containing sets are coerced to list - see TINKERPOP-3232 x = self.graphson_reader.read_object( json.dumps({"@type": "g:Set", "@value": [ {"@type": "g:Set", "@value": ["a", "b"]}, {"@type": "g:Set", "@value": ["c", "d"]} ]})) - assert isinstance(x, set) + assert isinstance(x, list) assert len(x) == 2 def test_set_with_mixed_hashable_and_unhashable_elements(self): - # test that sets containing a mix of hashable and unhashable elements work - see TINKERPOP-3232 + # test that sets containing a mix of hashable and unhashable elements are coerced to list - see TINKERPOP-3232 x = self.graphson_reader.read_object( json.dumps({"@type": "g:Set", "@value": [ "marko", {"@type": "g:Map", "@value": ["name", "josh"]}, {"@type": "g:Int32", "@value": 42} ]})) - assert isinstance(x, set) + assert isinstance(x, list) assert len(x) == 3 def test_set_with_nested_unhashable_elements(self): - # test that sets containing dicts with list values can be deserialized - see TINKERPOP-3232 + # test that sets containing dicts with list values are coerced to list - see TINKERPOP-3232 x = self.graphson_reader.read_object( json.dumps({"@type": "g:Set", "@value": [ {"@type": "g:Map", "@value": [ @@ -141,7 +140,7 @@ class TestGraphSONReader: "langs", {"@type": "g:List", "@value": ["gremlin"]} ]} ]})) - assert isinstance(x, set) + assert isinstance(x, list) assert len(x) == 2 def test_number_input(self):
