blob: da62bb541869532cbbfe0c0d8601c38f321c9ab9 [file] [log] [blame]
# 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
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
:class:`~pyignite.binary.GenericObjectMeta` is a metaclass used to create
classes, which objects serve as a native Python values for Ignite Complex
object data type. You can use this metaclass with your existing classes
to save and restore their selected attributes and properties to/from
Ignite caches. It is also used internally by `pyignite` to create simple
data classes “on the fly” when retrieving arbitrary Complex objects.
You can get the examples of using Complex objects in the
:ref:`complex_object_usage` section of `pyignite` documentation.
from collections import OrderedDict
import ctypes
from typing import Any
import attr
from pyignite.constants import *
from .datatypes import *
from .datatypes.base import IgniteDataTypeProps
from .exceptions import ParseError
from .utils import entity_id, hashcode, schema_id
Null, ByteObject, ShortObject, IntObject, LongObject, FloatObject,
DoubleObject, CharObject, BoolObject, UUIDObject, DateObject,
TimestampObject, TimeObject, EnumObject, BinaryEnumObject,
ByteArrayObject, ShortArrayObject, IntArrayObject, LongArrayObject,
FloatArrayObject, DoubleArrayObject, CharArrayObject, BoolArrayObject,
UUIDArrayObject, DateArrayObject, TimestampArrayObject,
TimeArrayObject, EnumArrayObject, String, StringArrayObject,
DecimalObject, DecimalArrayObject, ObjectArrayObject, CollectionObject,
MapObject, BinaryObject, WrappedDataObject,
class GenericObjectProps(IgniteDataTypeProps):
This class is mixed both to metaclass and to resulting class to make class
properties universally available. You should not subclass it directly.
def schema(self) -> OrderedDict:
""" Binary object schema. """
return self._schema.copy()
def schema_id(self) -> int:
""" Binary object schema ID. """
return schema_id(self._schema)
def __new__(cls, *args, **kwargs) -> Any:
# allow all items in Binary Object schema to be populated as optional
# arguments to `__init__()` with sensible defaults.
attributes = {}
for k, v in cls.schema.items():
attributes[k] = attr.ib(type=getattr(v, 'pythonic', type(None)), default=getattr(v, 'default', None))
attributes.update({'version': attr.ib(type=int, default=1)})
cls = attr.s(cls, these=attributes)
# skip parameters
return super().__new__(cls)
class GenericObjectPropsMeta(type, GenericObjectProps):
class GenericObjectMeta(GenericObjectPropsMeta):
Complex (or Binary) Object metaclass. It is aimed to help user create
classes, which objects could serve as a pythonic representation of the
:class:`~pyignite.datatypes.complex.BinaryObject` Ignite data type.
_schema = None
_type_name = None
version = None
def __new__(
mcs: Any, name: str, base_classes: tuple, namespace: dict, **kwargs
) -> Any:
""" Sort out class creation arguments. """
result = super().__new__(
mcs, name, (GenericObjectProps, )+base_classes, namespace
def _from_python(self, stream, save_to_buf=False):
Method for building binary representation of the Generic object
and calculating a hashcode from it.
:param self: Generic object instance,
:param stream: BinaryStream
:param save_to_buf: Optional. If True, save serialized data to buffer.
compact_footer = stream.compact_footer
# prepare header
header_class = BinaryObject.build_header()
header = header_class()
header.type_code = int.from_bytes(
header.flags = BinaryObject.USER_TYPE | BinaryObject.HAS_SCHEMA
if compact_footer:
header.flags |= BinaryObject.COMPACT_FOOTER
header.version = self.version
header.type_id = self.type_id
header.schema_id = self.schema_id
header_len = ctypes.sizeof(header_class)
initial_pos = stream.tell()
# create fields and calculate offsets
offsets = [ctypes.sizeof(header_class)]
schema_items = list(self.schema.items()) + header_len)
for field_name, field_type in schema_items:
val = getattr(self, field_name, getattr(field_type, 'default', None))
field_start_pos = stream.tell()
field_type.from_python(stream, val)
offsets.append(max(offsets) + stream.tell() - field_start_pos)
offsets = offsets[:-1]
# create footer
if max(offsets, default=0) < 255:
header.flags |= BinaryObject.OFFSET_ONE_BYTE
elif max(offsets) < 65535:
header.flags |= BinaryObject.OFFSET_TWO_BYTES
schema_class = BinaryObject.schema_type(header.flags) * len(offsets)
schema = schema_class()
if compact_footer:
for i, offset in enumerate(offsets):
schema[i] = offset
for i, offset in enumerate(offsets):
schema[i].field_id = entity_id(schema_items[i][0])
schema[i].offset = offset
# calculate size and hash code
fields_data_len = stream.tell() - initial_pos - header_len
header.schema_offset = fields_data_len + header_len
header.length = header.schema_offset + ctypes.sizeof(schema_class)
header.hash_code = stream.hashcode(initial_pos + header_len, fields_data_len)
stream.write(header) + header.schema_offset)
if save_to_buf:
self._buffer = bytes(stream.mem_view(initial_pos, stream.tell() - initial_pos))
self._hashcode = header.hash_code
def _setattr(self, attr_name: str, attr_value: Any):
# reset binary representation, if any field is changed
if attr_name in self._schema.keys():
self._buffer = None
self._hashcode = None
# `super()` is really need these parameters
super(result, self).__setattr__(attr_name, attr_value)
setattr(result, _from_python.__name__, _from_python)
setattr(result, '__setattr__', _setattr)
setattr(result, '_buffer', None)
setattr(result, '_hashcode', None)
return result
def _validate_schema(schema: dict):
for field_type in schema.values():
if field_type not in ALLOWED_FIELD_TYPES:
raise ParseError(
'Wrong binary field type: {}'.format(field_type)
def __init__(
cls, name: str, base_classes: tuple, namespace: dict,
type_name: str = None, schema: OrderedDict = None, **kwargs
Initializes binary object class.
:param type_name: (optional) binary object name. Defaults to class
:param schema: (optional) a dict of field names: field types,
:raise: ParseError if one or more binary field types
did not recognized.
cls._type_name = type_name or cls.__name__
cls._type_id = entity_id(cls._type_name)
schema = schema or OrderedDict()
cls._schema = schema
super().__init__(name, base_classes, namespace)