Spaces:
Running
Running
# Copyright DataStax, Inc. | |
# | |
# Licensed 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. | |
import datetime | |
import base64 | |
import uuid | |
import re | |
import json | |
from decimal import Decimal | |
from collections import OrderedDict | |
import logging | |
import itertools | |
from functools import partial | |
import ipaddress | |
from cassandra.cqltypes import cql_types_from_string | |
from cassandra.metadata import UserType | |
from cassandra.util import Polygon, Point, LineString, Duration | |
from cassandra.datastax.graph.types import Vertex, VertexProperty, Edge, Path, T | |
__all__ = ['GraphSON1Serializer', 'GraphSON1Deserializer', 'GraphSON1TypeDeserializer', | |
'GraphSON2Serializer', 'GraphSON2Deserializer', 'GraphSON2Reader', | |
'GraphSON3Serializer', 'GraphSON3Deserializer', 'GraphSON3Reader', | |
'to_bigint', 'to_int', 'to_double', 'to_float', 'to_smallint', | |
'BooleanTypeIO', 'Int16TypeIO', 'Int32TypeIO', 'DoubleTypeIO', | |
'FloatTypeIO', 'UUIDTypeIO', 'BigDecimalTypeIO', 'DurationTypeIO', 'InetTypeIO', | |
'InstantTypeIO', 'LocalDateTypeIO', 'LocalTimeTypeIO', 'Int64TypeIO', 'BigIntegerTypeIO', | |
'LocalDateTypeIO', 'PolygonTypeIO', 'PointTypeIO', 'LineStringTypeIO', 'BlobTypeIO', | |
'GraphSON3Serializer', 'GraphSON3Deserializer', 'UserTypeIO', 'TypeWrapperTypeIO'] | |
""" | |
Supported types: | |
DSE Graph GraphSON 2.0 GraphSON 3.0 | Python Driver | |
------------ | -------------- | -------------- | ------------ | |
text | string | string | str | |
boolean | | | bool | |
bigint | g:Int64 | g:Int64 | long | |
int | g:Int32 | g:Int32 | int | |
double | g:Double | g:Double | float | |
float | g:Float | g:Float | float | |
uuid | g:UUID | g:UUID | UUID | |
bigdecimal | gx:BigDecimal | gx:BigDecimal | Decimal | |
duration | gx:Duration | N/A | timedelta (Classic graph only) | |
DSE Duration | N/A | dse:Duration | Duration (Core graph only) | |
inet | gx:InetAddress | gx:InetAddress | str (unicode), IPV4Address/IPV6Address (PY3) | |
timestamp | gx:Instant | gx:Instant | datetime.datetime | |
date | gx:LocalDate | gx:LocalDate | datetime.date | |
time | gx:LocalTime | gx:LocalTime | datetime.time | |
smallint | gx:Int16 | gx:Int16 | int | |
varint | gx:BigInteger | gx:BigInteger | long | |
date | gx:LocalDate | gx:LocalDate | Date | |
polygon | dse:Polygon | dse:Polygon | Polygon | |
point | dse:Point | dse:Point | Point | |
linestring | dse:Linestring | dse:LineString | LineString | |
blob | dse:Blob | dse:Blob | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3) | |
blob | gx:ByteBuffer | gx:ByteBuffer | bytearray, buffer (PY2), memoryview (PY3), bytes (PY3) | |
list | N/A | g:List | list (Core graph only) | |
map | N/A | g:Map | dict (Core graph only) | |
set | N/A | g:Set | set or list (Core graph only) | |
Can return a list due to numerical values returned by Java | |
tuple | N/A | dse:Tuple | tuple (Core graph only) | |
udt | N/A | dse:UDT | class or namedtuple (Core graph only) | |
""" | |
MAX_INT32 = 2 ** 32 - 1 | |
MIN_INT32 = -2 ** 31 | |
log = logging.getLogger(__name__) | |
class _GraphSONTypeType(type): | |
"""GraphSONType metaclass, required to create a class property.""" | |
def graphson_type(cls): | |
return "{0}:{1}".format(cls.prefix, cls.graphson_base_type) | |
class GraphSONTypeIO(object, metaclass=_GraphSONTypeType): | |
"""Represent a serializable GraphSON type""" | |
prefix = 'g' | |
graphson_base_type = None | |
cql_type = None | |
def definition(cls, value, writer=None): | |
return {'cqlType': cls.cql_type} | |
def serialize(cls, value, writer=None): | |
return str(value) | |
def deserialize(cls, value, reader=None): | |
return value | |
def get_specialized_serializer(cls, value): | |
return cls | |
class TextTypeIO(GraphSONTypeIO): | |
cql_type = 'text' | |
class BooleanTypeIO(GraphSONTypeIO): | |
graphson_base_type = None | |
cql_type = 'boolean' | |
def serialize(cls, value, writer=None): | |
return bool(value) | |
class IntegerTypeIO(GraphSONTypeIO): | |
def serialize(cls, value, writer=None): | |
return value | |
def get_specialized_serializer(cls, value): | |
if type(value) is int and (value > MAX_INT32 or value < MIN_INT32): | |
return Int64TypeIO | |
return Int32TypeIO | |
class Int16TypeIO(IntegerTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'Int16' | |
cql_type = 'smallint' | |
class Int32TypeIO(IntegerTypeIO): | |
graphson_base_type = 'Int32' | |
cql_type = 'int' | |
class Int64TypeIO(IntegerTypeIO): | |
graphson_base_type = 'Int64' | |
cql_type = 'bigint' | |
def deserialize(cls, value, reader=None): | |
return value | |
class FloatTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Float' | |
cql_type = 'float' | |
def serialize(cls, value, writer=None): | |
return value | |
def deserialize(cls, value, reader=None): | |
return float(value) | |
class DoubleTypeIO(FloatTypeIO): | |
graphson_base_type = 'Double' | |
cql_type = 'double' | |
class BigIntegerTypeIO(IntegerTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'BigInteger' | |
class LocalDateTypeIO(GraphSONTypeIO): | |
FORMAT = '%Y-%m-%d' | |
prefix = 'gx' | |
graphson_base_type = 'LocalDate' | |
cql_type = 'date' | |
def serialize(cls, value, writer=None): | |
return value.isoformat() | |
def deserialize(cls, value, reader=None): | |
try: | |
return datetime.datetime.strptime(value, cls.FORMAT).date() | |
except ValueError: | |
# negative date | |
return value | |
class InstantTypeIO(GraphSONTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'Instant' | |
cql_type = 'timestamp' | |
def serialize(cls, value, writer=None): | |
if isinstance(value, datetime.datetime): | |
value = datetime.datetime(*value.utctimetuple()[:6]).replace(microsecond=value.microsecond) | |
else: | |
value = datetime.datetime.combine(value, datetime.datetime.min.time()) | |
return "{0}Z".format(value.isoformat()) | |
def deserialize(cls, value, reader=None): | |
try: | |
d = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%fZ') | |
except ValueError: | |
d = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%SZ') | |
return d | |
class LocalTimeTypeIO(GraphSONTypeIO): | |
FORMATS = [ | |
'%H:%M', | |
'%H:%M:%S', | |
'%H:%M:%S.%f' | |
] | |
prefix = 'gx' | |
graphson_base_type = 'LocalTime' | |
cql_type = 'time' | |
def serialize(cls, value, writer=None): | |
return value.strftime(cls.FORMATS[2]) | |
def deserialize(cls, value, reader=None): | |
dt = None | |
for f in cls.FORMATS: | |
try: | |
dt = datetime.datetime.strptime(value, f) | |
break | |
except ValueError: | |
continue | |
if dt is None: | |
raise ValueError('Unable to decode LocalTime: {0}'.format(value)) | |
return dt.time() | |
class BlobTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'Blob' | |
cql_type = 'blob' | |
def serialize(cls, value, writer=None): | |
value = base64.b64encode(value) | |
value = value.decode('utf-8') | |
return value | |
def deserialize(cls, value, reader=None): | |
return bytearray(base64.b64decode(value)) | |
class ByteBufferTypeIO(BlobTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'ByteBuffer' | |
class UUIDTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'UUID' | |
cql_type = 'uuid' | |
def deserialize(cls, value, reader=None): | |
return uuid.UUID(value) | |
class BigDecimalTypeIO(GraphSONTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'BigDecimal' | |
cql_type = 'bigdecimal' | |
def deserialize(cls, value, reader=None): | |
return Decimal(value) | |
class DurationTypeIO(GraphSONTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'Duration' | |
cql_type = 'duration' | |
_duration_regex = re.compile(r""" | |
^P((?P<days>\d+)D)? | |
T((?P<hours>\d+)H)? | |
((?P<minutes>\d+)M)? | |
((?P<seconds>[0-9.]+)S)?$ | |
""", re.VERBOSE) | |
_duration_format = "P{days}DT{hours}H{minutes}M{seconds}S" | |
_seconds_in_minute = 60 | |
_seconds_in_hour = 60 * _seconds_in_minute | |
_seconds_in_day = 24 * _seconds_in_hour | |
def serialize(cls, value, writer=None): | |
total_seconds = int(value.total_seconds()) | |
days, total_seconds = divmod(total_seconds, cls._seconds_in_day) | |
hours, total_seconds = divmod(total_seconds, cls._seconds_in_hour) | |
minutes, total_seconds = divmod(total_seconds, cls._seconds_in_minute) | |
total_seconds += value.microseconds / 1e6 | |
return cls._duration_format.format( | |
days=int(days), hours=int(hours), minutes=int(minutes), seconds=total_seconds | |
) | |
def deserialize(cls, value, reader=None): | |
duration = cls._duration_regex.match(value) | |
if duration is None: | |
raise ValueError('Invalid duration: {0}'.format(value)) | |
duration = {k: float(v) if v is not None else 0 | |
for k, v in duration.groupdict().items()} | |
return datetime.timedelta(days=duration['days'], hours=duration['hours'], | |
minutes=duration['minutes'], seconds=duration['seconds']) | |
class DseDurationTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'Duration' | |
cql_type = 'duration' | |
def serialize(cls, value, writer=None): | |
return { | |
'months': value.months, | |
'days': value.days, | |
'nanos': value.nanoseconds | |
} | |
def deserialize(cls, value, reader=None): | |
return Duration( | |
reader.deserialize(value['months']), | |
reader.deserialize(value['days']), | |
reader.deserialize(value['nanos']) | |
) | |
class TypeWrapperTypeIO(GraphSONTypeIO): | |
def definition(cls, value, writer=None): | |
return {'cqlType': value.type_io.cql_type} | |
def serialize(cls, value, writer=None): | |
return value.type_io.serialize(value.value) | |
def deserialize(cls, value, reader=None): | |
return value.type_io.deserialize(value.value) | |
class PointTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'Point' | |
cql_type = "org.apache.cassandra.db.marshal.PointType" | |
def deserialize(cls, value, reader=None): | |
return Point.from_wkt(value) | |
class LineStringTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'LineString' | |
cql_type = "org.apache.cassandra.db.marshal.LineStringType" | |
def deserialize(cls, value, reader=None): | |
return LineString.from_wkt(value) | |
class PolygonTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'Polygon' | |
cql_type = "org.apache.cassandra.db.marshal.PolygonType" | |
def deserialize(cls, value, reader=None): | |
return Polygon.from_wkt(value) | |
class InetTypeIO(GraphSONTypeIO): | |
prefix = 'gx' | |
graphson_base_type = 'InetAddress' | |
cql_type = 'inet' | |
class VertexTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Vertex' | |
def deserialize(cls, value, reader=None): | |
vertex = Vertex(id=reader.deserialize(value["id"]), | |
label=value["label"] if "label" in value else "vertex", | |
type='vertex', | |
properties={}) | |
# avoid the properties processing in Vertex.__init__ | |
vertex.properties = reader.deserialize(value.get('properties', {})) | |
return vertex | |
class VertexPropertyTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'VertexProperty' | |
def deserialize(cls, value, reader=None): | |
return VertexProperty(label=value['label'], | |
value=reader.deserialize(value["value"]), | |
properties=reader.deserialize(value.get('properties', {}))) | |
class EdgeTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Edge' | |
def deserialize(cls, value, reader=None): | |
in_vertex = Vertex(id=reader.deserialize(value["inV"]), | |
label=value['inVLabel'], | |
type='vertex', | |
properties={}) | |
out_vertex = Vertex(id=reader.deserialize(value["outV"]), | |
label=value['outVLabel'], | |
type='vertex', | |
properties={}) | |
return Edge( | |
id=reader.deserialize(value["id"]), | |
label=value["label"] if "label" in value else "vertex", | |
type='edge', | |
properties=reader.deserialize(value.get("properties", {})), | |
inV=in_vertex, | |
inVLabel=value['inVLabel'], | |
outV=out_vertex, | |
outVLabel=value['outVLabel'] | |
) | |
class PropertyTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Property' | |
def deserialize(cls, value, reader=None): | |
return {value["key"]: reader.deserialize(value["value"])} | |
class PathTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Path' | |
def deserialize(cls, value, reader=None): | |
labels = [set(label) for label in reader.deserialize(value['labels'])] | |
objects = [obj for obj in reader.deserialize(value['objects'])] | |
p = Path(labels, []) | |
p.objects = objects # avoid the object processing in Path.__init__ | |
return p | |
class TraversalMetricsTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'TraversalMetrics' | |
def deserialize(cls, value, reader=None): | |
return reader.deserialize(value) | |
class MetricsTypeIO(GraphSONTypeIO): | |
graphson_base_type = 'Metrics' | |
def deserialize(cls, value, reader=None): | |
return reader.deserialize(value) | |
class JsonMapTypeIO(GraphSONTypeIO): | |
"""In GraphSON2, dict are simply serialized as json map""" | |
def serialize(cls, value, writer=None): | |
out = {} | |
for k, v in value.items(): | |
out[k] = writer.serialize(v, writer) | |
return out | |
class MapTypeIO(GraphSONTypeIO): | |
"""In GraphSON3, dict has its own type""" | |
graphson_base_type = 'Map' | |
cql_type = 'map' | |
def definition(cls, value, writer=None): | |
out = OrderedDict([('cqlType', cls.cql_type)]) | |
out['definition'] = [] | |
for k, v in value.items(): | |
# we just need the first pair to write the def | |
out['definition'].append(writer.definition(k)) | |
out['definition'].append(writer.definition(v)) | |
break | |
return out | |
def serialize(cls, value, writer=None): | |
out = [] | |
for k, v in value.items(): | |
out.append(writer.serialize(k, writer)) | |
out.append(writer.serialize(v, writer)) | |
return out | |
def deserialize(cls, value, reader=None): | |
out = {} | |
a, b = itertools.tee(value) | |
for key, val in zip( | |
itertools.islice(a, 0, None, 2), | |
itertools.islice(b, 1, None, 2) | |
): | |
out[reader.deserialize(key)] = reader.deserialize(val) | |
return out | |
class ListTypeIO(GraphSONTypeIO): | |
"""In GraphSON3, list has its own type""" | |
graphson_base_type = 'List' | |
cql_type = 'list' | |
def definition(cls, value, writer=None): | |
out = OrderedDict([('cqlType', cls.cql_type)]) | |
out['definition'] = [] | |
if value: | |
out['definition'].append(writer.definition(value[0])) | |
return out | |
def serialize(cls, value, writer=None): | |
return [writer.serialize(v, writer) for v in value] | |
def deserialize(cls, value, reader=None): | |
return [reader.deserialize(obj) for obj in value] | |
class SetTypeIO(GraphSONTypeIO): | |
"""In GraphSON3, set has its own type""" | |
graphson_base_type = 'Set' | |
cql_type = 'set' | |
def definition(cls, value, writer=None): | |
out = OrderedDict([('cqlType', cls.cql_type)]) | |
out['definition'] = [] | |
for v in value: | |
# we only take into account the first value for the definition | |
out['definition'].append(writer.definition(v)) | |
break | |
return out | |
def serialize(cls, value, writer=None): | |
return [writer.serialize(v, writer) for v in value] | |
def deserialize(cls, value, reader=None): | |
lst = [reader.deserialize(obj) for obj in value] | |
s = set(lst) | |
if len(s) != len(lst): | |
log.warning("Coercing g:Set to list due to numerical values returned by Java. " | |
"See TINKERPOP-1844 for details.") | |
return lst | |
return s | |
class BulkSetTypeIO(GraphSONTypeIO): | |
graphson_base_type = "BulkSet" | |
def deserialize(cls, value, reader=None): | |
out = [] | |
a, b = itertools.tee(value) | |
for val, bulk in zip( | |
itertools.islice(a, 0, None, 2), | |
itertools.islice(b, 1, None, 2) | |
): | |
val = reader.deserialize(val) | |
bulk = reader.deserialize(bulk) | |
for n in range(bulk): | |
out.append(val) | |
return out | |
class TupleTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'Tuple' | |
cql_type = 'tuple' | |
def definition(cls, value, writer=None): | |
out = OrderedDict() | |
out['cqlType'] = cls.cql_type | |
serializers = [writer.get_serializer(s) for s in value] | |
out['definition'] = [s.definition(v, writer) for v, s in zip(value, serializers)] | |
return out | |
def serialize(cls, value, writer=None): | |
out = cls.definition(value, writer) | |
out['value'] = [writer.serialize(v, writer) for v in value] | |
return out | |
def deserialize(cls, value, reader=None): | |
return tuple(reader.deserialize(obj) for obj in value['value']) | |
class UserTypeIO(GraphSONTypeIO): | |
prefix = 'dse' | |
graphson_base_type = 'UDT' | |
cql_type = 'udt' | |
FROZEN_REMOVAL_REGEX = re.compile(r'frozen<"*([^"]+)"*>') | |
def cql_types_from_string(cls, typ): | |
# sanitizing: remove frozen references and double quotes... | |
return cql_types_from_string( | |
re.sub(cls.FROZEN_REMOVAL_REGEX, r'\1', typ) | |
) | |
def get_udt_definition(cls, value, writer): | |
user_type_name = writer.user_types[type(value)] | |
keyspace = writer.context['graph_name'] | |
return writer.context['cluster'].metadata.keyspaces[keyspace].user_types[user_type_name] | |
def is_collection(cls, typ): | |
return typ in ['list', 'tuple', 'map', 'set'] | |
def is_udt(cls, typ, writer): | |
keyspace = writer.context['graph_name'] | |
if keyspace in writer.context['cluster'].metadata.keyspaces: | |
return typ in writer.context['cluster'].metadata.keyspaces[keyspace].user_types | |
return False | |
def field_definition(cls, types, writer, name=None): | |
""" | |
Build the udt field definition. This is required when we have a complex udt type. | |
""" | |
index = -1 | |
out = [OrderedDict() if name is None else OrderedDict([('fieldName', name)])] | |
while types: | |
index += 1 | |
typ = types.pop(0) | |
if index > 0: | |
out.append(OrderedDict()) | |
if cls.is_udt(typ, writer): | |
keyspace = writer.context['graph_name'] | |
udt = writer.context['cluster'].metadata.keyspaces[keyspace].user_types[typ] | |
out[index].update(cls.definition(udt, writer)) | |
elif cls.is_collection(typ): | |
out[index]['cqlType'] = typ | |
definition = cls.field_definition(types, writer) | |
out[index]['definition'] = definition if isinstance(definition, list) else [definition] | |
else: | |
out[index]['cqlType'] = typ | |
return out if len(out) > 1 else out[0] | |
def definition(cls, value, writer=None): | |
udt = value if isinstance(value, UserType) else cls.get_udt_definition(value, writer) | |
return OrderedDict([ | |
('cqlType', cls.cql_type), | |
('keyspace', udt.keyspace), | |
('name', udt.name), | |
('definition', [ | |
cls.field_definition(cls.cql_types_from_string(typ), writer, name=name) | |
for name, typ in zip(udt.field_names, udt.field_types)]) | |
]) | |
def serialize(cls, value, writer=None): | |
udt = cls.get_udt_definition(value, writer) | |
out = cls.definition(value, writer) | |
out['value'] = [] | |
for name, typ in zip(udt.field_names, udt.field_types): | |
out['value'].append(writer.serialize(getattr(value, name), writer)) | |
return out | |
def deserialize(cls, value, reader=None): | |
udt_class = reader.context['cluster']._user_types[value['keyspace']][value['name']] | |
kwargs = zip( | |
list(map(lambda v: v['fieldName'], value['definition'])), | |
[reader.deserialize(v) for v in value['value']] | |
) | |
return udt_class(**dict(kwargs)) | |
class TTypeIO(GraphSONTypeIO): | |
prefix = 'g' | |
graphson_base_type = 'T' | |
def deserialize(cls, value, reader=None): | |
return T.name_to_value[value] | |
class _BaseGraphSONSerializer(object): | |
_serializers = OrderedDict() | |
def register(cls, type, serializer): | |
cls._serializers[type] = serializer | |
def get_type_definitions(cls): | |
return cls._serializers.copy() | |
def get_serializer(cls, value): | |
""" | |
Get the serializer for a python object. | |
:param value: The python object. | |
""" | |
# The serializer matching logic is as follow: | |
# 1. Try to find the python type by direct access. | |
# 2. Try to find the first serializer by class inheritance. | |
# 3. If no serializer found, return the raw value. | |
# Note that when trying to find the serializer by class inheritance, | |
# the order that serializers are registered is important. The use of | |
# an OrderedDict is to avoid the difference between executions. | |
serializer = None | |
try: | |
serializer = cls._serializers[type(value)] | |
except KeyError: | |
for key, serializer_ in cls._serializers.items(): | |
if isinstance(value, key): | |
serializer = serializer_ | |
break | |
if serializer: | |
# A serializer can have specialized serializers (e.g for Int32 and Int64, so value dependant) | |
serializer = serializer.get_specialized_serializer(value) | |
return serializer | |
def serialize(cls, value, writer=None): | |
""" | |
Serialize a python object to GraphSON. | |
e.g 'P42DT10H5M37S' | |
e.g. {'key': value} | |
:param value: The python object to serialize. | |
:param writer: A graphson serializer for recursive types (Optional) | |
""" | |
serializer = cls.get_serializer(value) | |
if serializer: | |
return serializer.serialize(value, writer or cls) | |
return value | |
class GraphSON1Serializer(_BaseGraphSONSerializer): | |
""" | |
Serialize python objects to graphson types. | |
""" | |
# When we fall back to a superclass's serializer, we iterate over this map. | |
# We want that iteration order to be consistent, so we use an OrderedDict, | |
# not a dict. | |
_serializers = OrderedDict([ | |
(str, TextTypeIO), | |
(bool, BooleanTypeIO), | |
(bytearray, ByteBufferTypeIO), | |
(Decimal, BigDecimalTypeIO), | |
(datetime.date, LocalDateTypeIO), | |
(datetime.time, LocalTimeTypeIO), | |
(datetime.timedelta, DurationTypeIO), | |
(datetime.datetime, InstantTypeIO), | |
(uuid.UUID, UUIDTypeIO), | |
(Polygon, PolygonTypeIO), | |
(Point, PointTypeIO), | |
(LineString, LineStringTypeIO), | |
(dict, JsonMapTypeIO), | |
(float, FloatTypeIO) | |
]) | |
GraphSON1Serializer.register(ipaddress.IPv4Address, InetTypeIO) | |
GraphSON1Serializer.register(ipaddress.IPv6Address, InetTypeIO) | |
GraphSON1Serializer.register(memoryview, ByteBufferTypeIO) | |
GraphSON1Serializer.register(bytes, ByteBufferTypeIO) | |
class _BaseGraphSONDeserializer(object): | |
_deserializers = {} | |
def get_type_definitions(cls): | |
return cls._deserializers.copy() | |
def register(cls, graphson_type, serializer): | |
cls._deserializers[graphson_type] = serializer | |
def get_deserializer(cls, graphson_type): | |
try: | |
return cls._deserializers[graphson_type] | |
except KeyError: | |
raise ValueError('Invalid `graphson_type` specified: {}'.format(graphson_type)) | |
def deserialize(cls, graphson_type, value): | |
""" | |
Deserialize a `graphson_type` value to a python object. | |
:param graphson_base_type: The graphson graphson_type. e.g. 'gx:Instant' | |
:param value: The graphson value to deserialize. | |
""" | |
return cls.get_deserializer(graphson_type).deserialize(value) | |
class GraphSON1Deserializer(_BaseGraphSONDeserializer): | |
""" | |
Deserialize graphson1 types to python objects. | |
""" | |
_TYPES = [UUIDTypeIO, BigDecimalTypeIO, InstantTypeIO, BlobTypeIO, ByteBufferTypeIO, | |
PointTypeIO, LineStringTypeIO, PolygonTypeIO, LocalDateTypeIO, | |
LocalTimeTypeIO, DurationTypeIO, InetTypeIO] | |
_deserializers = { | |
t.graphson_type: t | |
for t in _TYPES | |
} | |
def deserialize_date(cls, value): | |
return cls._deserializers[LocalDateTypeIO.graphson_type].deserialize(value) | |
def deserialize_time(cls, value): | |
return cls._deserializers[LocalTimeTypeIO.graphson_type].deserialize(value) | |
def deserialize_timestamp(cls, value): | |
return cls._deserializers[InstantTypeIO.graphson_type].deserialize(value) | |
def deserialize_duration(cls, value): | |
return cls._deserializers[DurationTypeIO.graphson_type].deserialize(value) | |
def deserialize_int(cls, value): | |
return int(value) | |
deserialize_smallint = deserialize_int | |
deserialize_varint = deserialize_int | |
def deserialize_bigint(cls, value): | |
return cls.deserialize_int(value) | |
def deserialize_double(cls, value): | |
return float(value) | |
deserialize_float = deserialize_double | |
def deserialize_uuid(cls, value): | |
return cls._deserializers[UUIDTypeIO.graphson_type].deserialize(value) | |
def deserialize_decimal(cls, value): | |
return cls._deserializers[BigDecimalTypeIO.graphson_type].deserialize(value) | |
def deserialize_blob(cls, value): | |
return cls._deserializers[ByteBufferTypeIO.graphson_type].deserialize(value) | |
def deserialize_point(cls, value): | |
return cls._deserializers[PointTypeIO.graphson_type].deserialize(value) | |
def deserialize_linestring(cls, value): | |
return cls._deserializers[LineStringTypeIO.graphson_type].deserialize(value) | |
def deserialize_polygon(cls, value): | |
return cls._deserializers[PolygonTypeIO.graphson_type].deserialize(value) | |
def deserialize_inet(cls, value): | |
return value | |
def deserialize_boolean(cls, value): | |
return value | |
# TODO Remove in the next major | |
GraphSON1TypeDeserializer = GraphSON1Deserializer | |
GraphSON1TypeSerializer = GraphSON1Serializer | |
class GraphSON2Serializer(_BaseGraphSONSerializer): | |
TYPE_KEY = "@type" | |
VALUE_KEY = "@value" | |
_serializers = GraphSON1Serializer.get_type_definitions() | |
def serialize(self, value, writer=None): | |
""" | |
Serialize a type to GraphSON2. | |
e.g {'@type': 'gx:Duration', '@value': 'P2DT4H'} | |
:param value: The python object to serialize. | |
""" | |
serializer = self.get_serializer(value) | |
if not serializer: | |
raise ValueError("Unable to find a serializer for value of type: ".format(type(value))) | |
val = serializer.serialize(value, writer or self) | |
if serializer is TypeWrapperTypeIO: | |
graphson_base_type = value.type_io.graphson_base_type | |
graphson_type = value.type_io.graphson_type | |
else: | |
graphson_base_type = serializer.graphson_base_type | |
graphson_type = serializer.graphson_type | |
if graphson_base_type is None: | |
out = val | |
else: | |
out = {self.TYPE_KEY: graphson_type} | |
if val is not None: | |
out[self.VALUE_KEY] = val | |
return out | |
GraphSON2Serializer.register(int, IntegerTypeIO) | |
class GraphSON2Deserializer(_BaseGraphSONDeserializer): | |
_TYPES = GraphSON1Deserializer._TYPES + [ | |
Int16TypeIO, Int32TypeIO, Int64TypeIO, DoubleTypeIO, FloatTypeIO, | |
BigIntegerTypeIO, VertexTypeIO, VertexPropertyTypeIO, EdgeTypeIO, | |
PathTypeIO, PropertyTypeIO, TraversalMetricsTypeIO, MetricsTypeIO] | |
_deserializers = { | |
t.graphson_type: t | |
for t in _TYPES | |
} | |
class GraphSON2Reader(object): | |
""" | |
GraphSON2 Reader that parse json and deserialize to python objects. | |
""" | |
def __init__(self, context, extra_deserializer_map=None): | |
""" | |
:param extra_deserializer_map: map from GraphSON type tag to deserializer instance implementing `deserialize` | |
""" | |
self.context = context | |
self.deserializers = GraphSON2Deserializer.get_type_definitions() | |
if extra_deserializer_map: | |
self.deserializers.update(extra_deserializer_map) | |
def read(self, json_data): | |
""" | |
Read and deserialize ``json_data``. | |
""" | |
return self.deserialize(json.loads(json_data)) | |
def deserialize(self, obj): | |
""" | |
Deserialize GraphSON type-tagged dict values into objects mapped in self.deserializers | |
""" | |
if isinstance(obj, dict): | |
try: | |
des = self.deserializers[obj[GraphSON2Serializer.TYPE_KEY]] | |
return des.deserialize(obj[GraphSON2Serializer.VALUE_KEY], self) | |
except KeyError: | |
pass | |
# list and map are treated as normal json objs (could be isolated deserializers) | |
return {self.deserialize(k): self.deserialize(v) for k, v in obj.items()} | |
elif isinstance(obj, list): | |
return [self.deserialize(o) for o in obj] | |
else: | |
return obj | |
class TypeIOWrapper(object): | |
"""Used to force a graphson type during serialization""" | |
type_io = None | |
value = None | |
def __init__(self, type_io, value): | |
self.type_io = type_io | |
self.value = value | |
def _wrap_value(type_io, value): | |
return TypeIOWrapper(type_io, value) | |
to_bigint = partial(_wrap_value, Int64TypeIO) | |
to_int = partial(_wrap_value, Int32TypeIO) | |
to_smallint = partial(_wrap_value, Int16TypeIO) | |
to_double = partial(_wrap_value, DoubleTypeIO) | |
to_float = partial(_wrap_value, FloatTypeIO) | |
class GraphSON3Serializer(GraphSON2Serializer): | |
_serializers = GraphSON2Serializer.get_type_definitions() | |
context = None | |
"""A dict of the serialization context""" | |
def __init__(self, context): | |
self.context = context | |
self.user_types = None | |
def definition(self, value): | |
serializer = self.get_serializer(value) | |
return serializer.definition(value, self) | |
def get_serializer(self, value): | |
"""Custom get_serializer to support UDT/Tuple""" | |
serializer = super(GraphSON3Serializer, self).get_serializer(value) | |
is_namedtuple_udt = serializer is TupleTypeIO and hasattr(value, '_fields') | |
if not serializer or is_namedtuple_udt: | |
# Check if UDT | |
if self.user_types is None: | |
try: | |
user_types = self.context['cluster']._user_types[self.context['graph_name']] | |
self.user_types = dict(map(reversed, user_types.items())) | |
except KeyError: | |
self.user_types = {} | |
serializer = UserTypeIO if (is_namedtuple_udt or (type(value) in self.user_types)) else serializer | |
return serializer | |
GraphSON3Serializer.register(dict, MapTypeIO) | |
GraphSON3Serializer.register(list, ListTypeIO) | |
GraphSON3Serializer.register(set, SetTypeIO) | |
GraphSON3Serializer.register(tuple, TupleTypeIO) | |
GraphSON3Serializer.register(Duration, DseDurationTypeIO) | |
GraphSON3Serializer.register(TypeIOWrapper, TypeWrapperTypeIO) | |
class GraphSON3Deserializer(GraphSON2Deserializer): | |
_TYPES = GraphSON2Deserializer._TYPES + [MapTypeIO, ListTypeIO, | |
SetTypeIO, TupleTypeIO, | |
UserTypeIO, DseDurationTypeIO, | |
TTypeIO, BulkSetTypeIO] | |
_deserializers = {t.graphson_type: t for t in _TYPES} | |
class GraphSON3Reader(GraphSON2Reader): | |
""" | |
GraphSON3 Reader that parse json and deserialize to python objects. | |
""" | |
def __init__(self, context, extra_deserializer_map=None): | |
""" | |
:param context: A dict of the context, mostly used as context for udt deserialization. | |
:param extra_deserializer_map: map from GraphSON type tag to deserializer instance implementing `deserialize` | |
""" | |
self.context = context | |
self.deserializers = GraphSON3Deserializer.get_type_definitions() | |
if extra_deserializer_map: | |
self.deserializers.update(extra_deserializer_map) | |