Repository: tinkerpop Updated Branches: refs/heads/TINKERPOP-1827 dba15319f -> 7e9d527f3 (forced update)
Implemented support for missing core GraphSON types in gremlin-python Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/3b651ff5 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/3b651ff5 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/3b651ff5 Branch: refs/heads/TINKERPOP-1827 Commit: 3b651ff58cdf210a63a0101d0a311c7076de2b0e Parents: f2b4fb9 Author: davebshow <davebs...@gmail.com> Authored: Wed Nov 1 16:31:10 2017 -0700 Committer: davebshow <davebs...@gmail.com> Committed: Wed Nov 1 16:31:10 2017 -0700 ---------------------------------------------------------------------- .../src/main/jython/gremlin_python/statics.py | 9 +++ .../gremlin_python/structure/io/graphson.py | 71 ++++++++++++++++- .../jython/tests/structure/io/test_graphson.py | 81 +++++++++++++++++++- 3 files changed, 156 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/3b651ff5/gremlin-python/src/main/jython/gremlin_python/statics.py ---------------------------------------------------------------------- diff --git a/gremlin-python/src/main/jython/gremlin_python/statics.py b/gremlin-python/src/main/jython/gremlin_python/statics.py index a1abf8e..f98347e 100644 --- a/gremlin-python/src/main/jython/gremlin_python/statics.py +++ b/gremlin-python/src/main/jython/gremlin_python/statics.py @@ -36,6 +36,15 @@ else: from types import LongType from types import TypeType + +class timestamp(float): + """ + In Python a timestamp is simply a float. This dummy class (similar to long), allows users to wrap a float + in a GLV script to make sure the value is serialized as a GraphSON timestamp. + """ + pass + + staticMethods = {} staticEnums = {} default_lambda_language = "gremlin-python" http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/3b651ff5/gremlin-python/src/main/jython/gremlin_python/structure/io/graphson.py ---------------------------------------------------------------------- diff --git a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphson.py b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphson.py index 0daeffa..04085ee 100644 --- a/gremlin-python/src/main/jython/gremlin_python/structure/io/graphson.py +++ b/gremlin-python/src/main/jython/gremlin_python/structure/io/graphson.py @@ -16,11 +16,15 @@ KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ''' -from aenum import Enum +import datetime import json -import six +import time +import uuid from collections import OrderedDict +import six +from aenum import Enum + from gremlin_python import statics from gremlin_python.statics import FloatType, FunctionType, IntType, LongType, TypeType from gremlin_python.process.traversal import Binding, Bytecode, P, Traversal, Traverser, TraversalStrategy @@ -298,6 +302,69 @@ class TypeSerializer(_GraphSONTypeIO): return writer.toDict(typ()) +class UUIDIO(_GraphSONTypeIO): + python_type = uuid.UUID + graphson_type = "g:UUID" + graphson_base_type = "UUID" + + @classmethod + def dictify(cls, obj, writer): + return GraphSONUtil.typedValue(cls.graphson_base_type, str(obj)) + + @classmethod + def objectify(cls, d, reader): + return cls.python_type(d) + + +class DateIO(_GraphSONTypeIO): + python_type = datetime.datetime + graphson_type = "g:Date" + graphson_base_type = "Date" + + @classmethod + def dictify(cls, obj, writer): + # Java timestamp expects miliseconds + if six.PY3: + pts = obj.timestamp() + else: + # Hack for legacy Python + # Taken from: + # https://github.com/jaraco/backports.datetime_timestamp/blob/master/backports/datetime_timestamp/__init__.py + pts = time.mktime((obj.year, obj.month, obj.day, + obj.hour, obj.minute, obj.second, + -1, -1, -1)) + obj.microsecond / 1e6 + + # Have to use int because of legacy Python + ts = int(round(pts * 1000)) + return GraphSONUtil.typedValue(cls.graphson_base_type, ts) + + @classmethod + def objectify(cls, ts, reader): + # Python timestamp expects seconds + return datetime.datetime.fromtimestamp(ts / 1000.0) + + +# Based on current implementation, this class must always be declared before FloatIO. +# Seems pretty fragile for future maintainers. Maybe look into this. +class TimestampIO(_GraphSONTypeIO): + """A timestamp in Python is type float""" + python_type = statics.timestamp + graphson_type = "g:Timestamp" + graphson_base_type = "Timestamp" + + @classmethod + def dictify(cls, obj, writer): + # Java timestamp expects milliseconds integer + # Have to use int because of legacy Python + ts = int(round(obj * 1000)) + return GraphSONUtil.typedValue(cls.graphson_base_type, ts) + + @classmethod + def objectify(cls, ts, reader): + # Python timestamp expects seconds + return cls.python_type(ts / 1000.0) + + class _NumberIO(_GraphSONTypeIO): @classmethod def dictify(cls, n, writer): http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/3b651ff5/gremlin-python/src/main/jython/tests/structure/io/test_graphson.py ---------------------------------------------------------------------- diff --git a/gremlin-python/src/main/jython/tests/structure/io/test_graphson.py b/gremlin-python/src/main/jython/tests/structure/io/test_graphson.py index d80d045..75e7136 100644 --- a/gremlin-python/src/main/jython/tests/structure/io/test_graphson.py +++ b/gremlin-python/src/main/jython/tests/structure/io/test_graphson.py @@ -18,16 +18,20 @@ under the License. ''' __author__ = 'Marko A. Rodriguez (http://markorodriguez.com)' - +import datetime import json +import uuid + from mock import Mock import six from gremlin_python.statics import * -from gremlin_python.structure.graph import Vertex, Edge, VertexProperty, Property +from gremlin_python.structure.graph import ( + Vertex, Edge, VertexProperty, Property, Graph) from gremlin_python.structure.graph import Path -from gremlin_python.structure.io.graphson import GraphSONWriter, GraphSONReader, GraphSONUtil +from gremlin_python.structure.io.graphson import ( + GraphSONWriter, GraphSONReader, GraphSONUtil) import gremlin_python.structure.io.graphson from gremlin_python.process.traversal import P from gremlin_python.process.strategies import SubgraphStrategy @@ -121,6 +125,19 @@ class TestGraphSONReader(object): serdes.objectify.assert_called_once_with(value, reader) assert o is serdes.objectify() + def test_datetime(self): + dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Date", "@value": 1481750076295})) + assert isinstance(dt, datetime.datetime) + + def test_timestamp(self): + dt = self.graphson_reader.readObject(json.dumps({"@type": "g:Timestamp", "@value": 1481750076295})) + assert isinstance(dt, timestamp) + + def test_uuid(self): + prop = self.graphson_reader.readObject( + json.dumps({'@type': 'g:UUID', '@value': "41d2e28a-20a4-4ab0-b379-d810dede3786"})) + assert isinstance(prop, uuid.UUID) + class TestGraphSONWriter(object): graphson_writer = GraphSONWriter() @@ -224,3 +241,61 @@ class TestGraphSONWriter(object): property = self.graphson_reader.readObject(self.graphson_writer.writeObject(Property("age", 32.2))) assert "age" == property.key assert 32.2 == property.value + + def test_datetime(self): + expected = json.dumps({"@type": "g:Date", "@value": 1481750076295}, separators=(',', ':')) + dt = datetime.datetime.fromtimestamp(1481750076295 / 1000.0) + output = self.graphson_writer.writeObject(dt) + assert expected == output + + def test_timestamp(self): + expected = json.dumps({"@type": "g:Timestamp", "@value": 1481750076295}, separators=(',', ':')) + ts = timestamp(1481750076295 / 1000.0) + output = self.graphson_writer.writeObject(ts) + assert expected == output + + def test_uuid(self): + expected = json.dumps({'@type': 'g:UUID', '@value': "41d2e28a-20a4-4ab0-b379-d810dede3786"}, separators=(',', ':')) + prop = uuid.UUID("41d2e28a-20a4-4ab0-b379-d810dede3786") + output = self.graphson_writer.writeObject(prop) + assert expected == output + +class TestFunctionalGraphSONIO(object): + """Functional IO tests""" + + def test_timestamp(self, remote_connection): + g = Graph().traversal().withRemote(remote_connection) + ts = timestamp(1481750076295 / 1000) + resp = g.addV('test_vertex').property('ts', ts) + resp = resp.toList() + vid = resp[0].id + try: + ts_prop = g.V(vid).properties('ts').toList()[0] + assert isinstance(ts_prop.value, timestamp) + assert ts_prop.value == ts + finally: + g.V(vid).drop().iterate() + + def test_datetime(self, remote_connection): + g = Graph().traversal().withRemote(remote_connection) + dt = datetime.datetime.fromtimestamp(1481750076295 / 1000) + resp = g.addV('test_vertex').property('dt', dt).toList() + vid = resp[0].id + try: + dt_prop = g.V(vid).properties('dt').toList()[0] + assert isinstance(dt_prop.value, datetime.datetime) + assert dt_prop.value == dt + finally: + g.V(vid).drop().iterate() + + def test_uuid(self, remote_connection): + g = Graph().traversal().withRemote(remote_connection) + uid = uuid.UUID("41d2e28a-20a4-4ab0-b379-d810dede3786") + resp = g.addV('test_vertex').property('uuid', uid).toList() + vid = resp[0].id + try: + uid_prop = g.V(vid).properties('uuid').toList()[0] + assert isinstance(uid_prop.value, uuid.UUID) + assert uid_prop.value == uid + finally: + g.V(vid).drop().iterate()