blob: 70b9ebf0247ebf29ae23341e79ca30b23f929098 [file]
"""
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.
"""
import calendar
import io
import logging
import math
import struct
import uuid
from collections import OrderedDict
from datetime import datetime, timedelta, timezone
from struct import pack, unpack
from aenum import Enum
from gremlin_python.process.traversal import Direction, T, Merge
from gremlin_python.statics import FloatType, BigDecimal, ShortType, IntType, LongType, BigIntType, \
DictType, SetType, SingleByte, SingleChar
from gremlin_python.structure.graph import Graph, Edge, Property, Vertex, VertexProperty, Path, CompositePDT, \
PrimitivePDT, Tree, _pdt_decorated_types
from gremlin_python.structure.io.util import HashableDict, SymbolUtil, Marker
log = logging.getLogger(__name__)
# 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()
_deserializers = {}
class DataType(Enum):
null = 0xfe
int = 0x01
long = 0x02
string = 0x03
datetime = 0x04
double = 0x07
float = 0x08
list = 0x09
map = 0x0a
set = 0x0b
uuid = 0x0c
edge = 0x0d
path = 0x0e
property = 0x0f
graph = 0x10 # not supported - no graph object in python yet
vertex = 0x11
vertexproperty = 0x12
direction = 0x18
t = 0x20
merge = 0x2e
bigdecimal = 0x22
biginteger = 0x23
byte = 0x24
binary = 0x25
short = 0x26
boolean = 0x27
tree = 0x2b
char = 0x80
duration = 0x81
composite_pdt = 0xf0
primitive_pdt = 0xf1
marker = 0xfd
NULL_BYTES = [DataType.null.value, 0x01]
# null type code as a plain int, so the per-read null check skips the aenum lookup
_NULL = DataType.null.value
def _make_packer(format_string):
packer = struct.Struct(format_string)
pack = packer.pack
unpack = lambda s: packer.unpack(s)[0]
return pack, unpack
int64_pack, int64_unpack = _make_packer('>q')
int32_pack, int32_unpack = _make_packer('>i')
int16_pack, int16_unpack = _make_packer('>h')
int8_pack, int8_unpack = _make_packer('>b')
uint64_pack, uint64_unpack = _make_packer('>Q')
uint8_pack, uint8_unpack = _make_packer('>B')
float_pack, float_unpack = _make_packer('>f')
double_pack, double_unpack = _make_packer('>d')
class GraphBinaryTypeType(type):
def __new__(mcs, name, bases, dct):
cls = super(GraphBinaryTypeType, mcs).__new__(mcs, name, bases, dct)
if not name.startswith('_'):
if cls.python_type:
_serializers[cls.python_type] = cls
if cls.graphbinary_type:
_deserializers[cls.graphbinary_type] = cls
return cls
class GraphBinaryWriter(object):
def __init__(self, serializer_map=None):
self.serializers = _serializers.copy()
if serializer_map:
self.serializers.update(serializer_map)
def write_object(self, object_data):
return self.to_dict(object_data)
def to_dict(self, obj, to_extend=None):
if to_extend is None:
to_extend = bytearray()
if obj is None:
to_extend.extend(NULL_BYTES)
return
try:
t = type(obj)
return self.serializers[t].dictify(obj, self, to_extend)
except KeyError:
for key, serializer in self.serializers.items():
if isinstance(obj, key):
return serializer.dictify(obj, self, to_extend)
if isinstance(obj, dict):
return dict((self.to_dict(k, to_extend), self.to_dict(v, to_extend)) for k, v in obj.items())
elif isinstance(obj, set):
return set([self.to_dict(o, to_extend) for o in obj])
elif isinstance(obj, list):
return [self.to_dict(o, to_extend) for o in obj]
else:
return obj
class GraphBinaryReader(object):
def __init__(self, deserializer_map=None, pdt_registry=None):
self.deserializers = _deserializers.copy()
if deserializer_map:
self.deserializers.update(deserializer_map)
self.pdt_registry = pdt_registry
# Mirror of self.deserializers keyed by int type code instead of DataType.
# Avoids the per-read DataType(bt) call, whose aenum construction negatively affects performance on large results.
self._deserializer_by_type_code = {dt.value: des.objectify for dt, des in self.deserializers.items()}
def read_object(self, b):
if b is None:
return None
if isinstance(b, bytearray):
return self.to_object(io.BytesIO(b))
return self.to_object(b)
def to_object(self, buff, data_type=None, nullable=True):
if data_type is None:
bt = uint8_unpack(buff.read(1))
if bt == _NULL:
if nullable:
buff.read(1)
return None
try:
objectify = self._deserializer_by_type_code[bt]
except KeyError:
raise ValueError("%r is not a valid DataType" % bt) from None
result = objectify(buff, self, nullable)
else:
result = self.deserializers[data_type].objectify(buff, self, nullable)
if self.pdt_registry is not None and isinstance(result, PrimitivePDT):
hydrated = self.pdt_registry.hydrate_primitive(result)
if not isinstance(hydrated, PrimitivePDT):
return hydrated
result = hydrated
if self.pdt_registry is not None and isinstance(result, CompositePDT):
hydrated = self.pdt_registry.hydrate(result)
if not isinstance(hydrated, CompositePDT):
return hydrated
result = hydrated
if isinstance(result, CompositePDT) and result.name in _pdt_decorated_types:
return self._hydrate_decorated(result)
return result
def _hydrate_decorated(self, pdt):
"""Hydrate a CompositePDT using a @provider_defined decorated class."""
cls = _pdt_decorated_types[pdt.name]
fields = {}
for k, v in pdt.fields.items():
if isinstance(v, CompositePDT) and v.name in _pdt_decorated_types:
fields[k] = self._hydrate_decorated(v)
elif self.pdt_registry is not None and isinstance(v, CompositePDT):
fields[k] = self.pdt_registry.hydrate(v)
else:
fields[k] = v
obj = cls.__new__(cls)
for k, v in fields.items():
setattr(obj, k, v)
return obj
class _GraphBinaryTypeIO(object, metaclass=GraphBinaryTypeType):
python_type = None
graphbinary_type = None
@classmethod
def prefix_bytes(cls, graphbin_type, as_value=False, nullable=True, to_extend=None, ordered=False):
if to_extend is None:
to_extend = bytearray()
if not as_value:
to_extend += uint8_pack(graphbin_type.value)
if nullable:
if ordered:
to_extend += int8_pack(2)
else:
to_extend += int8_pack(0)
return to_extend
@classmethod
def read_int(cls, buff):
return int32_unpack(buff.read(4))
@classmethod
def is_null(cls, buff, reader, else_opt, nullable=True):
return None if nullable and buff.read(1)[0] == 0x01 else else_opt(buff, reader)
def dictify(self, obj, writer, to_extend, as_value=False, nullable=True):
raise NotImplementedError()
def objectify(self, d, reader, nullable=True):
raise NotImplementedError()
class LongIO(_GraphBinaryTypeIO):
python_type = LongType
graphbinary_type = DataType.long
byte_format_pack = int64_pack
byte_format_unpack = int64_unpack
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
if obj < -9223372036854775808 or obj > 9223372036854775807:
raise Exception("Value too big, please use bigint Gremlin type")
else:
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(cls.byte_format_pack(obj))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: int64_unpack(buff.read(8)), nullable)
class IntIO(LongIO):
python_type = IntType
graphbinary_type = DataType.int
byte_format_pack = int32_pack
byte_format_unpack = int32_unpack
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: cls.read_int(b), nullable)
class ShortIO(LongIO):
python_type = ShortType
graphbinary_type = DataType.short
byte_format_pack = int16_pack
byte_format_unpack = int16_unpack
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: int16_unpack(buff.read(2)), nullable)
class BigIntIO(_GraphBinaryTypeIO):
python_type = BigIntType
graphbinary_type = DataType.biginteger
@classmethod
def write_bigint(cls, obj, to_extend):
# Compute the minimal signed two's-complement byte length, matching the
# Java reference serializer (BigInteger.toByteArray()).
bit_length = obj.bit_length() if obj >= 0 else (obj + 1).bit_length()
length = bit_length // 8 + 1
b = obj.to_bytes(length, byteorder='big', signed=True)
to_extend.extend(int32_pack(length))
to_extend.extend(b)
return to_extend
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
return cls.write_bigint(obj, to_extend)
@classmethod
def read_bigint(cls, buff):
size = cls.read_int(buff)
return int.from_bytes(buff.read(size), byteorder='big', signed=True)
@classmethod
def objectify(cls, buff, reader, nullable=False):
return cls.is_null(buff, reader, lambda b, r: cls.read_bigint(b), nullable)
def _long_bits_to_double(bits):
return unpack('d', pack('Q', bits))[0]
NAN = _long_bits_to_double(0x7ff8000000000000)
POSITIVE_INFINITY = _long_bits_to_double(0x7ff0000000000000)
NEGATIVE_INFINITY = _long_bits_to_double(0xFff0000000000000)
class FloatIO(LongIO):
python_type = FloatType
graphbinary_type = DataType.float
graphbinary_base_type = DataType.float
byte_format_pack = float_pack
byte_format_unpack = float_unpack
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
if math.isnan(obj):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(cls.byte_format_pack(NAN))
elif math.isinf(obj) and obj > 0:
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(cls.byte_format_pack(POSITIVE_INFINITY))
elif math.isinf(obj) and obj < 0:
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(cls.byte_format_pack(NEGATIVE_INFINITY))
else:
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(cls.byte_format_pack(obj))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: float_unpack(b.read(4)), nullable)
class DoubleIO(FloatIO):
"""
Floats basically just fall through to double serialization.
"""
graphbinary_type = DataType.double
graphbinary_base_type = DataType.double
byte_format_pack = double_pack
byte_format_unpack = double_unpack
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: double_unpack(b.read(8)), nullable)
class BigDecimalIO(_GraphBinaryTypeIO):
python_type = BigDecimal
graphbinary_type = DataType.bigdecimal
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int32_pack(obj.scale))
return BigIntIO.write_bigint(obj.unscaled_value, to_extend)
@classmethod
def _read(cls, buff):
scale = int32_unpack(buff.read(4))
unscaled_value = BigIntIO.read_bigint(buff)
return BigDecimal(scale, unscaled_value)
@classmethod
def objectify(cls, buff, reader, nullable=False):
return cls.is_null(buff, reader, lambda b, r: cls._read(b), nullable)
class DateTimeIO(_GraphBinaryTypeIO):
python_type = datetime
graphbinary_type = DataType.datetime
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
if obj.tzinfo is None:
raise AttributeError("Timezone information is required when constructing datetime")
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
IntIO.dictify(obj.year, writer, to_extend, True, False)
ByteIO.dictify(obj.month, writer, to_extend, True, False)
ByteIO.dictify(obj.day, writer, to_extend, True, False)
# construct time of day in nanoseconds
h = obj.time().hour
m = obj.time().minute
s = obj.time().second
ms = obj.time().microsecond
ns = round((h*60*60*1e9) + (m*60*1e9) + (s*1e9) + (ms*1e3))
LongIO.dictify(ns, writer, to_extend, True, False)
os = round(obj.utcoffset().total_seconds())
IntIO.dictify(os, writer, to_extend, True, False)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_dt, nullable)
@classmethod
def _read_dt(cls, b, r):
year = r.to_object(b, DataType.int, False)
month = r.to_object(b, DataType.byte, False)
day = r.to_object(b, DataType.byte, False)
ns = r.to_object(b, DataType.long, False)
offset = r.to_object(b, DataType.int, False)
tz = timezone(timedelta(seconds=offset))
return datetime(year, month, day, tzinfo=tz) + timedelta(microseconds=ns/1000)
class CharIO(_GraphBinaryTypeIO):
python_type = SingleChar
graphbinary_type = DataType.char
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(obj.encode("utf-8"))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_char, nullable)
@classmethod
def _read_char(cls, b, r):
max_bytes = 4
x = b.read(1)
while max_bytes > 0:
max_bytes = max_bytes - 1
try:
return x.decode("utf-8")
except UnicodeDecodeError:
x += b.read(1)
class StringIO(_GraphBinaryTypeIO):
python_type = str
graphbinary_type = DataType.string
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
str_bytes = obj.encode("utf-8")
to_extend += int32_pack(len(str_bytes))
to_extend += str_bytes
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: b.read(cls.read_int(b)).decode("utf-8"), nullable)
class ListIO(_GraphBinaryTypeIO):
python_type = list
graphbinary_type = DataType.list
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int32_pack(len(obj)))
for item in obj:
writer.to_dict(item, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
flag = 0x00
if nullable:
flag = buff.read(1)[0]
if flag == 0x01:
return None
else:
return cls._read_list(buff, reader, flag)
return cls._read_list(buff, reader, flag)
@classmethod
def _read_list(cls, b, r, flag):
size = cls.read_int(b)
the_list = []
if flag == 0x02:
while size > 0:
itm = r.read_object(b)
bulk = int64_unpack(b.read(8))
for y in range(bulk):
the_list.append(itm)
size = size - 1
else:
while size > 0:
the_list.append(r.read_object(b))
size = size - 1
return the_list
class SetDeserializer(ListIO):
python_type = SetType
graphbinary_type = DataType.set
@classmethod
def objectify(cls, buff, reader, nullable=True):
the_list = ListIO.objectify(buff, reader, nullable)
try:
return set(the_list)
except TypeError:
log.warning("Coercing Set to list as it contains unhashable elements (e.g. dict, list). "
"See TINKERPOP-3232 for more details.")
return the_list
class MapIO(_GraphBinaryTypeIO):
python_type = DictType
graphbinary_type = DataType.map
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend, ordered=isinstance(obj, OrderedDict))
to_extend.extend(int32_pack(len(obj)))
for k, v in obj.items():
writer.to_dict(k, to_extend)
writer.to_dict(v, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
flag = 0x00
if nullable:
flag = buff.read(1)[0]
if flag == 0x01:
return None
else:
return cls._read_map(buff, reader, flag)
return cls._read_map(buff, reader, flag)
@classmethod
def _read_map(cls, b, r, flag):
size = cls.read_int(b)
the_dict = OrderedDict() if flag == 0x02 else {}
while size > 0:
k = HashableDict.of(r.read_object(b))
v = r.read_object(b)
the_dict[k] = v
size = size - 1
return the_dict
class UuidIO(_GraphBinaryTypeIO):
python_type = uuid.UUID
graphbinary_type = DataType.uuid
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(obj.bytes)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: uuid.UUID(bytes=b.read(16)), nullable)
class EdgeIO(_GraphBinaryTypeIO):
python_type = Edge
graphbinary_type = DataType.edge
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
writer.to_dict(obj.id, to_extend)
# serializing labels as list according to GraphBinaryV4
if hasattr(obj, '_labels'):
ListIO.dictify(list(obj._labels), writer, to_extend, True, False)
else:
ListIO.dictify([obj.label], writer, to_extend, True, False)
writer.to_dict(obj.inV.id, to_extend)
if hasattr(obj.inV, '_labels'):
ListIO.dictify(list(obj.inV._labels), writer, to_extend, True, False)
else:
ListIO.dictify([obj.inV.label], writer, to_extend, True, False)
writer.to_dict(obj.outV.id, to_extend)
if hasattr(obj.outV, '_labels'):
ListIO.dictify(list(obj.outV._labels), writer, to_extend, True, False)
else:
ListIO.dictify([obj.outV.label], writer, to_extend, True, False)
to_extend.extend(NULL_BYTES)
to_extend.extend(NULL_BYTES)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_edge, nullable)
@classmethod
def _read_edge(cls, b, r):
edgeid = r.read_object(b)
# reading label list according to GraphBinaryV4
edge_labels = r.to_object(b, DataType.list, False)
inv_id = r.read_object(b)
inv_labels = r.to_object(b, DataType.list, False)
inv = Vertex(inv_id, labels=inv_labels)
outv_id = r.read_object(b)
outv_labels = r.to_object(b, DataType.list, False)
outv = Vertex(outv_id, labels=outv_labels)
b.read(2)
props = r.read_object(b)
# null properties are returned as empty lists
properties = [] if props is None else props
edge = Edge(edgeid, outv, edge_labels[0] if edge_labels else "edge", inv, properties, labels=edge_labels)
return edge
class PathIO(_GraphBinaryTypeIO):
python_type = Path
graphbinary_type = DataType.path
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
writer.to_dict(obj.labels, to_extend)
writer.to_dict(obj.objects, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, lambda b, r: Path(r.read_object(b), r.read_object(b)), nullable)
class TreeIO(_GraphBinaryTypeIO):
python_type = Tree
graphbinary_type = DataType.tree
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
# when as_value (a nested/bare child tree) prefix_bytes writes nothing:
# no type-id and no null flag. As a root value it writes {type-id}{null flag}.
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
root_nodes = obj.root_nodes()
to_extend.extend(int32_pack(len(root_nodes)))
for key in root_nodes:
child = obj.child_at(key)
# key is written fully-qualified (its own type-id + null flag + value)
writer.to_dict(key, to_extend)
# child is written as a BARE tree value: no type-id, no null flag
cls.dictify(child, writer, to_extend, as_value=True, nullable=False)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_tree, nullable)
@classmethod
def _read_tree(cls, b, r):
size = cls.read_int(b)
tree = Tree()
while size > 0:
key = r.read_object(b)
child = cls.objectify(b, r, False)
tree.get_or_create_child(key).add_tree(child)
size = size - 1
return tree
class PropertyIO(_GraphBinaryTypeIO):
python_type = Property
graphbinary_type = DataType.property
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
StringIO.dictify(obj.key, writer, to_extend, True, False)
writer.to_dict(obj.value, to_extend)
to_extend.extend(NULL_BYTES)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_property, nullable)
@classmethod
def _read_property(cls, b, r):
p = Property(r.to_object(b, DataType.string, False), r.read_object(b), None)
b.read(2)
return p
class TinkerGraphIO(_GraphBinaryTypeIO):
python_type = Graph
graphbinary_type = DataType.graph
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
vertices = list(obj.vertices.values())
edges = list(obj.edges.values())
IntIO.dictify(len(vertices), writer, to_extend, True, False)
for v in vertices:
writer.to_dict(v.id, to_extend)
if hasattr(v, '_labels'):
ListIO.dictify(list(v._labels), writer, to_extend, True, False)
else:
ListIO.dictify([v.label], writer, to_extend, True, False)
v_props = v.properties
IntIO.dictify(len(v_props), writer, to_extend, True, False)
for vp in v_props:
writer.to_dict(vp.id, to_extend)
ListIO.dictify([vp.label], writer, to_extend, True, False)
writer.to_dict(vp.value, to_extend)
writer.to_dict(None, to_extend)
ListIO.dictify(vp.properties, writer, to_extend, True, False)
IntIO.dictify(len(edges), writer, to_extend, True, False)
for e in edges:
writer.to_dict(e.id, to_extend)
if hasattr(e, '_labels'):
ListIO.dictify(list(e._labels), writer, to_extend, True, False)
else:
ListIO.dictify([e.label], writer, to_extend, True, False)
writer.to_dict(e.inV.id, to_extend)
writer.to_dict(None, to_extend)
writer.to_dict(e.outV.id, to_extend)
writer.to_dict(None, to_extend)
writer.to_dict(None, to_extend)
ListIO.dictify(e.properties, writer, to_extend, True, False)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_graph, nullable)
@classmethod
def _read_graph(cls, b, r):
graph = Graph()
vertex_count = r.to_object(b, DataType.int, False)
for _ in range(vertex_count):
v_id = r.read_object(b)
v_labels = r.to_object(b, DataType.list, False)
vertex = Vertex(v_id, v_labels[0] if v_labels else "vertex", labels=v_labels)
graph.vertices[v_id] = vertex
vp_count = r.to_object(b, DataType.int, False)
for _ in range(vp_count):
vp_id = r.read_object(b)
vp_label = r.to_object(b, DataType.list, False)[0]
vp_value = r.read_object(b)
r.read_object(b) # discard parent
vp = VertexProperty(vp_id, vp_label, vp_value, vertex)
vertex.properties.append(vp)
meta_props = r.to_object(b, DataType.list, False)
if meta_props:
vp.properties.extend(meta_props)
edge_count = r.to_object(b, DataType.int, False)
for _ in range(edge_count):
e_id = r.read_object(b)
e_labels = r.to_object(b, DataType.list, False)
in_v_id = r.read_object(b)
r.read_object(b) # discard in-v label
out_v_id = r.read_object(b)
r.read_object(b) # discard out-v label
r.read_object(b) # discard parent
edge = Edge(e_id, graph.vertices[out_v_id], e_labels[0] if e_labels else "edge",
graph.vertices[in_v_id], labels=e_labels)
graph.edges[e_id] = edge
edge_props = r.to_object(b, DataType.list, False)
if edge_props:
edge.properties.extend(edge_props)
return graph
class VertexIO(_GraphBinaryTypeIO):
python_type = Vertex
graphbinary_type = DataType.vertex
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
writer.to_dict(obj.id, to_extend)
# serializing labels as list according to GraphBinaryV4
if hasattr(obj, '_labels'):
ListIO.dictify(list(obj._labels), writer, to_extend, True, False)
else:
ListIO.dictify([obj.label], writer, to_extend, True, False)
to_extend.extend(NULL_BYTES)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_vertex, nullable)
@classmethod
def _read_vertex(cls, b, r):
vertex_id = r.read_object(b)
# reading label list according to GraphBinaryV4
vertex_labels = r.to_object(b, DataType.list, False)
props = r.read_object(b)
# null properties are returned as empty lists
properties = [] if props is None else props
vertex = Vertex(vertex_id, properties=properties, labels=vertex_labels)
return vertex
class VertexPropertyIO(_GraphBinaryTypeIO):
python_type = VertexProperty
graphbinary_type = DataType.vertexproperty
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
writer.to_dict(obj.id, to_extend)
# serializing label as list here for now according to GraphBinaryV4
ListIO.dictify([obj.label], writer, to_extend, True, False)
writer.to_dict(obj.value, to_extend)
to_extend.extend(NULL_BYTES)
to_extend.extend(NULL_BYTES)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_vertexproperty, nullable)
@classmethod
def _read_vertexproperty(cls, b, r):
# reading single string value for now according to GraphBinaryV4
vp = VertexProperty(r.read_object(b), r.to_object(b, DataType.list, False)[0], r.read_object(b), None)
b.read(2)
properties = r.read_object(b)
# null properties are returned as empty lists
vp.properties = [] if properties is None else properties
return vp
class _EnumIO(_GraphBinaryTypeIO):
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
StringIO.dictify(SymbolUtil.to_camel_case(str(obj.name)), writer, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_enumval, nullable)
@classmethod
def _read_enumval(cls, b, r):
enum_name = r.to_object(b)
return cls.python_type[SymbolUtil.to_snake_case(enum_name)]
class DirectionIO(_EnumIO):
graphbinary_type = DataType.direction
python_type = Direction
@classmethod
def _read_enumval(cls, b, r):
# Direction needs to retain all CAPS. note that to_/from_ are really just aliases of IN/OUT
# so they don't need to be accounted for in serialization
enum_name = r.to_object(b)
return cls.python_type[enum_name]
class TIO(_EnumIO):
graphbinary_type = DataType.t
python_type = T
class MergeIO(_EnumIO):
graphbinary_type = DataType.merge
python_type = Merge
class ByteIO(_GraphBinaryTypeIO):
python_type = SingleByte
graphbinary_type = DataType.byte
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int8_pack(obj))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader,
lambda b, r: int.__new__(SingleByte, int8_unpack(b.read(1))),
nullable)
class BinaryIO(_GraphBinaryTypeIO):
python_type = bytes
graphbinary_type = DataType.binary
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int32_pack(len(obj)))
to_extend.extend(obj)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_bytebuffer, nullable)
@classmethod
def _read_bytebuffer(cls, b, r):
size = cls.read_int(b)
return bytes(b.read(size))
class BooleanIO(_GraphBinaryTypeIO):
python_type = bool
graphbinary_type = DataType.boolean
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int8_pack(0x01 if obj else 0x00))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader,
lambda b, r: True if int8_unpack(b.read(1)) == 0x01 else False,
nullable)
class DurationIO(_GraphBinaryTypeIO):
python_type = timedelta
graphbinary_type = DataType.duration
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
LongIO.dictify(obj.seconds, writer, to_extend, True, False)
IntIO.dictify(obj.microseconds * 1000, writer, to_extend, True, False)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_duration, nullable)
@classmethod
def _read_duration(cls, b, r):
seconds = r.to_object(b, DataType.long, False)
nanos = r.to_object(b, DataType.int, False)
return timedelta(seconds=seconds, microseconds=nanos / 1000)
class MarkerIO(_GraphBinaryTypeIO):
python_type = Marker
graphbinary_type = DataType.marker
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
to_extend.extend(int8_pack(obj.get_value()))
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader,
lambda b, r: Marker.of(int8_unpack(b.read(1))),
nullable)
class CompositePDTIO(_GraphBinaryTypeIO):
python_type = CompositePDT
graphbinary_type = DataType.composite_pdt
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
StringIO.dictify(obj.name, writer, to_extend)
MapIO.dictify(obj.fields, writer, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_pdt, nullable)
@classmethod
def _read_pdt(cls, b, r):
name = r.read_object(b)
fields = r.read_object(b)
return CompositePDT(name, fields)
class PrimitivePDTIO(_GraphBinaryTypeIO):
python_type = PrimitivePDT
graphbinary_type = DataType.primitive_pdt
@classmethod
def dictify(cls, obj, writer, to_extend, as_value=False, nullable=True):
cls.prefix_bytes(cls.graphbinary_type, as_value, nullable, to_extend)
StringIO.dictify(obj.name, writer, to_extend)
StringIO.dictify(obj.value, writer, to_extend)
return to_extend
@classmethod
def objectify(cls, buff, reader, nullable=True):
return cls.is_null(buff, reader, cls._read_primitive_pdt, nullable)
@classmethod
def _read_primitive_pdt(cls, b, r):
name = r.read_object(b)
value = r.read_object(b)
return PrimitivePDT(name, value)