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:10 UTC
[tinkerpop] 01/01: TINKERPOP-2395 Support list as keys in Map for
python
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