Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ This release also includes changes from <<release-3-4-3, 3.4.3>>.
* Configured GraphSON 3.0 as the default setting for the `GraphSONMapper`.
* Added `JavascriptTranslator` for Java.
* Added `DotNetTranslator` for Java.
* Added Groovy `Translator` for Python.
* Fixed bug in `PythonTranslator` for processing `TraversalStrategy` instances in GraphBinary.
* Fixed bug in bytecode `Bindings` where calling `of()` prior to calling a child traversal in the same parent would cause the initial binding to be lost.
* Bumped to tornado 6.1 for gremlinpython.
Expand Down
18 changes: 16 additions & 2 deletions docs/src/reference/the-traversal.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -4286,11 +4286,11 @@ columns represent the language that Gremlin can be generated in:
[width="100%",cols="<,^,^,^,^,^",options="header"]
|=========================================================
| |Java |Groovy |Javascript |.NET |Python
|*Java* |- |X |X | |X
|*Java* |- |X |X |X |X
|*Groovy* | |X |X | |X
|*Javascript* | |X |- | |
|*.NET* | | | |- |
|*Python* | | | | |-
|*Python* | |X | | |-
|=========================================================

Each programming language has its own API for translation, but the pattern is quite similar from one to the next:
Expand Down Expand Up @@ -4333,6 +4333,20 @@ const translator = new gremlin.process.Translator('g');
console.log(translator.translate(t));
// OUTPUT: g.V().has('person','name','marko').where(__.in('knows')).values('age')
----
[source,python]
----
from gremlin_python.process.translator import *

g = ...
t = (g.V().has('person','name','marko').
where(__.in_("knows")).
values("age"))

# Groovy
translator = Translator().of('g');
print(translator.translate(t.bytecode));
# OUTPUT: g.V().has('person','name','marko').where(__.in('knows')).values('age')
----

The JVM-based translator has the added option of parameter extraction, where the translation process will attempt to
identify opportunities to generate an output that would replace constant values with parameters. The parameters would
Expand Down
171 changes: 171 additions & 0 deletions gremlin-python/src/main/python/gremlin_python/process/translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#
# 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 that can turn traversals back into Gremlin Groovy format text queries.
Those queries can then be run in the Gremlin console or using the GLV submit(<String>) API or
sent to any TinkerPop compliant HTTP endpoint.
"""
__author__ = 'Kelvin R. Lawrence (gfxman)'

from gremlin_python.process.graph_traversal import __
from gremlin_python.process.anonymous_traversal import traversal
from gremlin_python.process.traversal import *
from gremlin_python.driver.driver_remote_connection import DriverRemoteConnection
from gremlin_python.process.strategies import *
from datetime import datetime


class Translator:
"""
Turn a bytecode object back into a textual query (Gremlin Groovy script).
"""

# Dictionary used to reverse-map token IDs to strings
options = {
WithOptions.tokens: 'tokens',
WithOptions.none: 'none',
WithOptions.ids:'ids',
WithOptions.labels: 'labels',
WithOptions.keys: 'keys',
WithOptions.values: 'values',
WithOptions.all: 'all',
WithOptions.indexer: 'indexer',
WithOptions.list: 'list',
WithOptions.map: 'map'
}

def __init__(self, traversal_source=None):
self.traversal_source = traversal_source

def get_traversal_source(self):
return self.traversal_source

def get_target_language(self):
return "gremlin-groovy"

def of(self,traversal_source):
self.traversal_source = traversal_source
return self

# Do any needed special processing for the representation
# of strings and dates.
def fixup(self, v):
if type(v) == str:
return f'\'{v}\''
elif type(v) == datetime:
return self.process_date(v)
else:
return str(v)

# Turn a Python datetime into the equivalent new Date(...)
def process_date(self, date):
y = date.year - 1900
mo = date.month
d = date.day
h = date.hour
mi = date.minute
s = date.second
return f'new Date({y},{mo},{d},{h},{mi},{s})'

# Do special processing needed to format predicates that come in
# such as "gt(a)" correctly.
def process_predicate(self, p):
res = str(p).split('(')[0] + '('

if type(p.value) == list:
res += '['
for v in p.value:
res += self.fixup(v) + ','
res = res[0:-1] + ']'
else:
res += self.fixup(p.value)
if p.other is not None:
res+= ',' + self.fixup(p.other)
res += ')'
return res

# Special processing to handle strategies
def process_strategy(self, s):
c = 0
res = f'new {str(s)}('
for key in s.configuration:
res += ',' if c > 0 else ''
res += key + ':'
val = s.configuration[key]
if isinstance(val, Traversal):
res += self.translate(val.bytecode,child=True)
else:
res += self.fixup(val)
c += 1
res += ')'
return res
pass

# Main driver of the translation. Different parts of
# a Traversal are handled appropriately.
def do_translation(self, step):
script = ''
params = step[1:]
script += '.' + step[0] + '('
if len(params) > 0:
c = 0
with_opts = False
for p in params:
script += ',' if c > 0 else ''
if with_opts:
script += f'WithOptions.{self.options[p]}'
elif type(p) == Bytecode:
script += self.translate(p, True)
elif type(p) == P:
script += self.process_predicate(p)
elif type(p) in [Cardinality, Pop]:
tmp = str(p)
script += tmp[0:-1] if tmp.endswith('_') else tmp
elif type(p) in [ReadOnlyStrategy, SubgraphStrategy, VertexProgramStrategy,
OptionsStrategy, PartitionStrategy]:
script += self.process_strategy(p)
elif type(p) == datetime:
script += self.process_date(p)
elif p == WithOptions.tokens:
script += 'WithOptions.tokens'
with_opts = True
elif type(p) == str:
script += f'\'{p}\''
else:
script += str(p)
c += 1
script += ')'
return script

# Translation starts here. There are two main parts to a
# traversal. Source instructions such as "withSideEffect"
# and "withStrategies", and step instructions such as
# "addV" and "repeat". If child is True we will generate
# anonymous traversal style syntax.
def translate(self, bytecode, child=False):
script = '__' if child else self.traversal_source

for step in bytecode.source_instructions:
script += self.do_translation(step)

for step in bytecode.step_instructions:
script += self.do_translation(step)

return script
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,6 @@ def outside(*args):
return P.outside(*args)


def test(*args):
return P.test(*args)


def within(*args):
return P.within(*args)

Expand Down Expand Up @@ -351,8 +347,6 @@ def without(*args):

statics.add_static('outside', outside)

statics.add_static('test', test)

statics.add_static('within', within)

statics.add_static('without', without)
Expand Down
Loading