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 2021/01/27 01:41:09 UTC

[tinkerpop] branch TINKERPOP-2395 created (now 761f4dc)

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

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


      at 761f4dc  TINKERPOP-2395 Support list as keys in Map for python

This branch includes the following new commits:

     new 761f4dc  TINKERPOP-2395 Support list as keys in Map for python

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[tinkerpop] 01/01: TINKERPOP-2395 Support list as keys in Map for python

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 761f4dc30aef596d4b9dc060af57ea231c82fa0b
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Jan 26 20:40:26 2021 -0500

    TINKERPOP-2395 Support list as keys in Map for python
---
 CHANGELOG.asciidoc                                  |  2 +-
 docs/src/upgrade/release-3.5.x.asciidoc             |  9 +++++----
 .../Gherkin/GherkinTestRunner.cs                    |  4 ++--
 .../Gherkin/IgnoreException.cs                      |  7 ++++++-
 .../gremlin-javascript/test/cucumber/gremlin.js     |  1 +
 .../main/python/gremlin_python/structure/io/util.py |  5 ++++-
 .../src/main/python/radish/feature_steps.py         |  4 +++-
 gremlin-python/src/main/python/radish/gremlin.py    |  1 +
 .../tests/driver/test_driver_remote_connection.py   |  3 +++
 gremlin-test/features/sideEffect/GroupCount.feature | 13 ++++++++++++-
 .../traversal/step/sideEffect/GroupCountTest.java   | 21 +++++++++++++++++++++
 11 files changed, 59 insertions(+), 11 deletions(-)

diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index ef9d2dc..486765b 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -32,7 +32,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
 * 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`.
 * Gremlin.Net driver no longer supports skipping deserialization by default. Users can however create their own `IMessageSerializer` if they need this functionality.
-* Supported deserialization of `dict` as a key in a `dict` for Python.
+* Supported deserialization of `dict` and `list` 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 01296aa..8e5618d 100644
--- a/docs/src/upgrade/release-3.5.x.asciidoc
+++ b/docs/src/upgrade/release-3.5.x.asciidoc
@@ -354,9 +354,9 @@ 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:
+In Gremlin it is common to return a `dict` or `list` as a key value in another `dict`. The problem for Python is that
+these values are not hashable and will result in an error. By introducing a `HashableDict` and `Tuple` for those keys
+(respectively), it is now possible to return these types of results and not have to work around them:
 
 [source,text]
 ----
@@ -364,7 +364,8 @@ return these types of results and not have to work around them:
 {{<T.id: 1>: 1, <T.label: 4>: 'person', 'name': 'marko'}: 1}
 ----
 
-See: link:https://issues.apache.org/jira/browse/TINKERPOP-2395[TINKERPOP-2395]
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2395[TINKERPOP-2395],
+link:https://issues.apache.org/jira/browse/TINKERPOP-2407[TINKERPOP-2407]
 
 ==== Gremlin Server Audit Logging
 
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
index 2649e68..e596448 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
@@ -41,8 +41,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
             new Dictionary<string, IgnoreReason>
             {
                 // Add here the name of scenarios to ignore and the reason, e.g.:
-                { "g_V_group_byXageX", IgnoreReason.NullKeysInMapNotSupported }
-                //{ "g_V_properties_propertiesXstartTimeX_drop", IgnoreReason.NoReason },
+                { "g_V_group_byXageX", IgnoreReason.NullKeysInMapNotSupported },
+                { "g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX", IgnoreReason.ArrayKeysInMapNotAssertingInGherkin }
             };
         
         private static class Keywords
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
index 0e25440..9579626 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs
@@ -55,6 +55,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
         /// <summary>
         /// C# does not allow a `null` value to be used as a key.
         /// </summary>
-        NullKeysInMapNotSupported
+        NullKeysInMapNotSupported,
+        
+        /// <summary>
+        /// C# array equality is by reference not contents so the gherkin setup won't assert properly
+        /// </summary>
+        ArrayKeysInMapNotAssertingInGherkin
     }
 }
\ No newline at end of file
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 43545bd..d393d47 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -651,6 +651,7 @@ const gremlins = {
     g_V_outXcreatedX_groupCountXxX_capXxX: [function({g}) { return g.V().out("created").groupCount("x").cap("x") }], 
     g_V_groupCount_byXbothE_countX: [function({g}) { return g.V().groupCount().by(__.bothE().count()) }], 
     g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX: [function({g}) { return g.V().both().groupCount("a").out().cap("a").select(Column.keys).unfold().both().groupCount("a").cap("a") }], 
+    g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX: [function({g}) { return g.V().has("person","name","marko").both("knows").groupCount().by(__.values("name").fold()) }], 
     g_VX1X_out_injectXv2X_name: [function({g, v1Id, v2}) { return g.V(v1Id).out().inject(v2).values("name") }], 
     g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path: [function({g, v1Id, c}) { return g.V(v1Id).out().values("name").inject("daniel").as("a").map(c).path() }], 
     g_VX1X_injectXg_VX4XX_out_name: [function({g, v1Id, v4}) { return g.V(v1Id).inject(v4).out().values("name") }], 
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
index 86fd72a..e57a7e9 100644
--- a/gremlin-python/src/main/python/gremlin_python/structure/io/util.py
+++ b/gremlin-python/src/main/python/gremlin_python/structure/io/util.py
@@ -32,6 +32,9 @@ class HashableDict(dict):
 
         new_o = HashableDict()
         for k, v in o.items():
-            new_o[k] = cls.of(v)
+            if isinstance(k, (set, list)):
+                new_o[tuple(k)] = cls.of(v)
+            else:
+                new_o[k] = cls.of(v)
         return new_o
 
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py b/gremlin-python/src/main/python/radish/feature_steps.py
index 826cc75..fe1ea44 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -143,7 +143,9 @@ def _convert(val, ctx):
     if isinstance(val, dict):                                            # convert dictionary keys/values
         n = {}
         for key, value in val.items():
-            n[_convert(key, ctx)] = _convert(value, ctx)
+            k = _convert(key, ctx)
+            # convert to tuple key if list/set as neither are hashable
+            n[tuple(k) if isinstance(k, (set, list)) else k] = _convert(value, ctx)
         return n
     elif isinstance(val, str) and re.match(r"^l\[.*\]$", val):           # parse list
         return [] if val == "l[]" else list(map((lambda x: _convert(x, ctx)), val[2:-1].split(",")))
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 5e16f57..45a10bb 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -636,6 +636,7 @@ world.gremlins = {
     'g_V_outXcreatedX_groupCountXxX_capXxX': [(lambda g:g.V().out('created').groupCount('x').cap('x'))], 
     'g_V_groupCount_byXbothE_countX': [(lambda g:g.V().groupCount().by(__.bothE().count()))], 
     'g_V_both_groupCountXaX_out_capXaX_selectXkeysX_unfold_both_groupCountXaX_capXaX': [(lambda g:g.V().both().groupCount('a').out().cap('a').select(Column.keys).unfold().both().groupCount('a').cap('a'))], 
+    'g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX': [(lambda g:g.V().has('person','name','marko').both('knows').groupCount().by(__.name.fold()))], 
     'g_VX1X_out_injectXv2X_name': [(lambda g, v1Id=None,v2=None:g.V(v1Id).out().inject(v2).name)], 
     'g_VX1X_out_name_injectXdanielX_asXaX_mapXlengthX_path': [(lambda g, v1Id=None,c=None:g.V(v1Id).out().name.inject('daniel').as_('a').map(c).path())], 
     'g_VX1X_injectXg_VX4XX_out_name': [(lambda g, v1Id=None,v4=None:g.V(v1Id).inject(v4).out().name)], 
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 bb7793f..4c95509 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
@@ -104,6 +104,9 @@ class TestDriverRemoteConnection(object):
         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
+        if not isinstance(remote_connection._client._message_serializer, GraphSONSerializersV2d0):
+            results = g.V().has('person', 'name', 'marko').both('knows').groupCount().by(__.values('name').fold()).next()
+            assert {tuple(['vadas']): 1, tuple(['josh']): 1} == results
 
     def test_lambda_traversals(self, remote_connection):
         statics.load_statics(globals())
diff --git a/gremlin-test/features/sideEffect/GroupCount.feature b/gremlin-test/features/sideEffect/GroupCount.feature
index 40978cb..53acd6d 100644
--- a/gremlin-test/features/sideEffect/GroupCount.feature
+++ b/gremlin-test/features/sideEffect/GroupCount.feature
@@ -190,4 +190,15 @@ Feature: Step - groupCount()
     When iterated to list
     Then the result should be unordered
       | result |
-      | m[{"v[marko]":"d[6].l","v[vadas]":"d[2].l","v[lop]":"d[6].l","v[josh]":"d[6].l","v[ripple]":"d[2].l","v[peter]":"d[2].l"}] |
\ No newline at end of file
+      | m[{"v[marko]":"d[6].l","v[vadas]":"d[2].l","v[lop]":"d[6].l","v[josh]":"d[6].l","v[ripple]":"d[2].l","v[peter]":"d[2].l"}] |
+
+  Scenario: g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX
+    Given the modern graph
+    And the traversal of
+      """
+      g.V().has("person", "name", "marko").both("knows").groupCount().by(__.values("name").fold())
+      """
+    When iterated to list
+    Then the result should be unordered
+      | result |
+      | m[{"l[josh]":"d[1].l","l[vadas]":"d[1].l"}] |
\ No newline at end of file
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
index 34011ac..1051e2a 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/GroupCountTest.java
@@ -30,7 +30,9 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static org.apache.tinkerpop.gremlin.LoadGraphWith.GraphData.MODERN;
@@ -76,6 +78,8 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
 
     public abstract Traversal<Vertex, String> get_g_V_both_groupCountXaX_byXlabelX_asXbX_barrier_whereXselectXaX_selectXsoftwareX_isXgtX2XXX_selectXbX_name();
 
+    public abstract Traversal<Vertex, Map<Object, Long>> get_g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX();
+
     @Test
     @LoadGraphWith(MODERN)
     public void g_V_outXcreatedX_groupCount_byXnameX() {
@@ -258,6 +262,18 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
         checkSideEffects(traversal.asAdmin().getSideEffects(), "a", HashMap.class);
     }
 
+    @Test
+    @LoadGraphWith(MODERN)
+    public void g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX() {
+        final Traversal<Vertex, Map<Object,Long>> traversal = get_g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX();
+        printTraversalForm(traversal);
+        final Map<Object,Long> map = traversal.next();
+        assertFalse(traversal.hasNext());
+        assertEquals(2, map.size());
+        assertEquals(1L, map.get(Collections.singletonList("josh")).longValue());
+        assertEquals(1L, map.get(Collections.singletonList("vadas")).longValue());
+    }
+
     public static class Traversals extends GroupCountTest {
 
         @Override
@@ -326,5 +342,10 @@ public abstract class GroupCountTest extends AbstractGremlinProcessTest {
         public Traversal<Vertex, String> get_g_V_both_groupCountXaX_byXlabelX_asXbX_barrier_whereXselectXaX_selectXsoftwareX_isXgtX2XXX_selectXbX_name() {
             return g.V().both().groupCount("a").by(T.label).as("b").barrier().where(__.select("a").select("software").is(gt(2))).select("b").values("name");
         }
+
+        @Override
+        public Traversal<Vertex, Map<Object, Long>> get_g_V_hasXperson_name_markoX_bothXknowsX_groupCount_byXvaluesXnameX_foldX() {
+            return g.V().has("person", "name", "marko").both("knows").groupCount().by(__.values("name").fold());
+        }
     }
 }
\ No newline at end of file