RAHHH
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{% if is_first_by_category %}
|
||||
##
|
||||
## enum types
|
||||
##
|
||||
|
||||
{% endif%}
|
||||
class {{ metadata.classname }}(object):
|
||||
"""
|
||||
{{ metadata.docs }}
|
||||
"""
|
||||
{% for value_name in metadata.values %}
|
||||
{{ value_name }}: int = {{ metadata.values[value_name].value }}
|
||||
"""
|
||||
{{ metadata.values[value_name].docs }}
|
||||
"""
|
||||
{% endfor %}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Python module "{{ modulename }}"
|
||||
|
||||
from . import {{ ', '.join(imports) }}
|
||||
|
||||
__all__ = [{{ ', '.join(imports) }}]
|
||||
@@ -0,0 +1,360 @@
|
||||
{% if is_first_by_category %}
|
||||
##
|
||||
## object types
|
||||
##
|
||||
|
||||
{% endif %}
|
||||
{% if render_imports %}
|
||||
import uuid
|
||||
import pprint
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
from autobahn.wamp.request import Publication, Subscription, Registration
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
{% endif %}
|
||||
|
||||
|
||||
class {{ metadata.classname }}(object):
|
||||
"""
|
||||
{{ metadata.docs }}
|
||||
"""
|
||||
__slots__ = ['_tab', {% for field in metadata.fields_by_id %}'_{{ field.name }}', {% endfor %}]
|
||||
|
||||
def __init__(self, {% for field in metadata.fields_by_id %}{{ field.name }}: {{ field.type.map('python', field.attrs, required=False, objtype_as_string=True) }} = None, {% endfor %}):
|
||||
# the underlying FlatBuffers vtable
|
||||
self._tab = None
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
# {{ field.docs }}
|
||||
self._{{ field.name }}: {{ field.type.map('python', field.attrs, required=False, objtype_as_string=True) }} = {{ field.name }}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
{% for field in metadata.fields_by_id %}
|
||||
if other.{{ field.name }} != self.{{ field.name }}:
|
||||
return False
|
||||
{% endfor %}
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
@property
|
||||
def {{ field.name }}(self) -> {{ field.type.map('python', field.attrs, required=False, objtype_as_string=True) }}:
|
||||
"""
|
||||
{{ field.docs }}
|
||||
"""
|
||||
if self._{{ field.name }} is None and self._tab:
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset({{ field.offset }}))
|
||||
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
# access type "string" attribute:
|
||||
value = ''
|
||||
if o != 0:
|
||||
_value = self._tab.String(o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value.decode('utf8')
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
# access type "bytes" attribute:
|
||||
value = b''
|
||||
if o != 0:
|
||||
_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
_value = memoryview(self._tab.Bytes)[_off:_off + _len]
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) in ['int', 'float', 'double'] %}
|
||||
# access type "int|float|double" attribute:
|
||||
value = 0
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.{{ FbsType.FBS2FLAGS[field.type.basetype] }}, o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
# access type "bool" attribute:
|
||||
value = False
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
# access type "uuid.UUID" attribute:
|
||||
value = uuid.UUID(bytes=b'\x00' * 16)
|
||||
if o != 0:
|
||||
_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
_value = memoryview(self._tab.Bytes)[_off:_off + _len]
|
||||
if _value is not None:
|
||||
value = uuid.UUID(bytes=bytes(_value))
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
# access type "np.datetime64" attribute:
|
||||
value = np.datetime64(0, 'ns')
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
if value is not None:
|
||||
value = np.datetime64(_value, 'ns')
|
||||
|
||||
{% elif field.type.basetype == FbsType.Vector %}
|
||||
# access type "Vector" attribute:
|
||||
value = []
|
||||
if o != 0:
|
||||
_start_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
for j in range(_len):
|
||||
_off = _start_off + flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
|
||||
_off = self._tab.Indirect(_off)
|
||||
{% if False and field.type.element == FbsType.Obj %}
|
||||
_value = {{ field.type.element.split('.')[-1] }}.cast(self._tab.Bytes, _off)
|
||||
{% else %}
|
||||
# FIXME [8]
|
||||
_value = {{ field.type.element }}()
|
||||
{% endif %}
|
||||
value.append(_value)
|
||||
|
||||
{% elif field.type.basetype == FbsType.Obj %}
|
||||
# access type "Object" attribute:
|
||||
|
||||
{% if field.type.objtype %}
|
||||
value = {{ field.type.objtype.split('.')[-1] }}()
|
||||
if o != 0:
|
||||
_off = self._tab.Indirect(o + self._tab.Pos)
|
||||
value = {{ field.type.objtype.split('.')[-1] }}.cast(self._tab.Bytes, _off)
|
||||
{% else %}
|
||||
# FIXME [9]: objtype of field "{{ field.name }}" is None
|
||||
value = ''
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
# FIXME [5]
|
||||
raise NotImplementedError('implement processing [5] of FlatBuffers type "{}"'.format({{ field.type.map('python', field.attrs, True) }}))
|
||||
{% endif %}
|
||||
assert value is not None
|
||||
self._{{ field.name }} = value
|
||||
return self._{{ field.name }}
|
||||
|
||||
@{{ field.name }}.setter
|
||||
def {{ field.name }}(self, value: {{ field.type.map('python', field.attrs, required=False, objtype_as_string=True) }}):
|
||||
if value is not None:
|
||||
self._{{ field.name }} = value
|
||||
else:
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
# set default value on type "string" attribute:
|
||||
self._{{ field.name }} = ''
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
# set default value on type "bytes" attribute:
|
||||
self._{{ field.name }} = b''
|
||||
{% elif field.type.map('python', field.attrs, True) in ['int', 'float', 'double'] %}
|
||||
# set default value on type "int|float|double" attribute:
|
||||
self._{{ field.name }} = 0
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
# set default value on type "bool" attribute:
|
||||
self._{{ field.name }} = False
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
# set default value on type "uuid.UUID" attribute:
|
||||
self._{{ field.name }} = uuid.UUID(bytes=b'\x00' * 16)
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
# set default value on type "np.datetime64" attribute:
|
||||
self._{{ field.name }} = np.datetime64(0, 'ns')
|
||||
# set default value on type "List" attribute:
|
||||
{% elif field.type.basetype == FbsType.Vector %}
|
||||
self._{{ field.name }} = []
|
||||
# set default value on type "Object" attribute:
|
||||
{% elif field.type.basetype == FbsType.Obj %}
|
||||
self._{{ field.name }} = {{ field.type.map('python', field.attrs, True) }}()
|
||||
{% else %}
|
||||
# FIXME [6]
|
||||
raise NotImplementedError('implement processing [2] of FlatBuffers type "{}", basetype {}'.format({{ field.type.map('python', field.attrs, True) }}, {{ field.type.basetype }}))
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@staticmethod
|
||||
def parse(data: Dict) -> '{{ metadata.classname }}':
|
||||
"""
|
||||
Parse generic, native language object into a typed, native language object.
|
||||
|
||||
:param data: Generic native language object to parse, e.g. output of ``cbor2.loads``.
|
||||
|
||||
:returns: Typed object of this class.
|
||||
"""
|
||||
# FIXME
|
||||
# for key in data.keys():
|
||||
# assert key in {{ metadata.fields.keys() }}
|
||||
obj = {{ metadata.classname }}()
|
||||
{% for field in metadata.fields_by_id %}
|
||||
if '{{ field.name }}' in data:
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == str), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
obj.{{ field.name }} = data['{{ field.name }}']
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == bytes), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
obj.{{ field.name }} = data['{{ field.name }}']
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'int' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == int), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
obj.{{ field.name }} = data['{{ field.name }}']
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'float' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == float), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
obj.{{ field.name }} = data['{{ field.name }}']
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == bool), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
obj.{{ field.name }} = data['{{ field.name }}']
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
assert (data['{{ field.name }}'] is None or (type(data['{{ field.name }}']) == bytes and len(data['{{ field.name }}']) == 16)), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
if data['{{ field.name }}'] is not None:
|
||||
obj.{{ field.name }} = uuid.UUID(bytes=data['{{ field.name }}'])
|
||||
else:
|
||||
obj.{{ field.name }} = None
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == int), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
if data['{{ field.name }}'] is not None:
|
||||
obj.{{ field.name }} = np.datetime64(data['{{ field.name }}'], 'ns')
|
||||
else:
|
||||
obj.{{ field.name }} = np.datetime64(0, 'ns')
|
||||
|
||||
{% elif field.type.basetype == FbsType.Vector %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == list), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
_value = []
|
||||
for v in data['{{ field.name }}']:
|
||||
{% if False and field.type.element == FbsType.Obj %}
|
||||
# FIXME
|
||||
_value.append({{ field.type.objtype.split('.')[-1] }}.parse(v))
|
||||
{% else %}
|
||||
_value.append(v)
|
||||
{% endif %}
|
||||
obj.{{ field.name }} = _value
|
||||
|
||||
{% elif field.type.basetype == FbsType.Obj %}
|
||||
assert (data['{{ field.name }}'] is None or type(data['{{ field.name }}']) == dict), '{} has wrong type {}'.format('{{ field.name }}', type(data['{{ field.name }}']))
|
||||
_value = {{ field.type.map('python', field.attrs, True) }}.parse(data['{{ field.name }}'])
|
||||
obj.{{ field.name }} = _value
|
||||
|
||||
{% else %}
|
||||
# FIXME [3]
|
||||
raise NotImplementedError('implement processing [3] of FlatBuffers type "{}"'.format({{ field.type.map('python', field.attrs, True) }}))
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
return obj
|
||||
|
||||
def marshal(self) -> Dict:
|
||||
"""
|
||||
Marshal all data contained in this typed native object into a generic object.
|
||||
|
||||
:returns: Generic object that can be serialized to bytes using e.g. ``cbor2.dumps``.
|
||||
"""
|
||||
obj = {
|
||||
{% for field in metadata.fields_by_id %}
|
||||
|
||||
{% if field.type.map('python', field.attrs, True) in ['str', 'bytes', 'int', 'long', 'float', 'double', 'bool'] %}
|
||||
'{{ field.name }}': self.{{ field.name }},
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
'{{ field.name }}': self.{{ field.name }}.bytes if self.{{ field.name }} is not None else None,
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
'{{ field.name }}': int(self.{{ field.name }}) if self.{{ field.name }} is not None else None,
|
||||
|
||||
{% elif field.type.basetype == FbsType.Vector %}
|
||||
{% if field.type.element == FbsType.Obj %}
|
||||
'{{ field.name }}': [o.marshal() for o in self.{{ field.name }}] if self.{{ field.name }} is not None else None,
|
||||
{% else %}
|
||||
'{{ field.name }}': self.{{ field.name }},
|
||||
{% endif %}
|
||||
|
||||
{% elif field.type.basetype == FbsType.Obj %}
|
||||
'{{ field.name }}': self.{{ field.name }}.marshal() if self.{{ field.name }} is not None else None,
|
||||
|
||||
{% else %}
|
||||
# FIXME [4]: implement processing [4] of FlatBuffers type "{{ field.type | string }}" (Python type "{{ field.type.map('python', field.attrs, True) }}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
return obj
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return string representation of this object, suitable for e.g. logging.
|
||||
|
||||
:returns: String representation of this object.
|
||||
"""
|
||||
return '\n{}\n'.format(pprint.pformat(self.marshal()))
|
||||
|
||||
@staticmethod
|
||||
def cast(buf: bytes, offset: int = 0) -> '{{ metadata.classname }}':
|
||||
"""
|
||||
Cast a FlatBuffers raw input buffer as a typed object of this class.
|
||||
|
||||
:param buf: The raw input buffer to cast.
|
||||
:param offset: Offset into raw buffer from which to cast flatbuffers from.
|
||||
|
||||
:returns: New native object that wraps the FlatBuffers raw buffer.
|
||||
"""
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = {{ metadata.classname }}()
|
||||
x._tab = flatbuffers.table.Table(buf, n + offset)
|
||||
return x
|
||||
|
||||
def build(self, builder):
|
||||
"""
|
||||
Build a FlatBuffers raw output buffer from this typed object.
|
||||
|
||||
:returns: Constructs the FlatBuffers using the builder and
|
||||
returns ``builder.EndObject()``.
|
||||
"""
|
||||
# first, write all string|bytes|etc typed attribute values (in order) to the buffer
|
||||
{% for field in metadata.fields_by_id %}
|
||||
{% if field.type.map('python', field.attrs, True) in ['str', 'bytes'] %}
|
||||
_{{ field.name }} = self.{{ field.name }}
|
||||
if _{{ field.name }}:
|
||||
_{{ field.name }} = builder.CreateString(_{{ field.name }})
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
_{{ field.name }} = self.{{ field.name }}.bytes if self.{{ field.name }} else None
|
||||
if _{{ field.name }}:
|
||||
_{{ field.name }} = builder.CreateString(_{{ field.name }})
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
# now start a new object in the buffer and write the actual object attributes (in field
|
||||
# order) to the buffer
|
||||
builder.StartObject({{ metadata.fields_by_id|length }})
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
|
||||
{% if field.type.map('python', field.attrs, True) in ['str', 'bytes', 'uuid.UUID'] %}
|
||||
if _{{ field.name }}:
|
||||
builder.PrependUOffsetTRelativeSlot({{ field.id }}, flatbuffers.number_types.UOffsetTFlags.py_type(_{{ field.name }}), 0)
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
if self.{{ field.name }}:
|
||||
builder.PrependUint64Slot({{ field.id }}, int(self.{{ field.name }}), 0)
|
||||
|
||||
{% elif field.type.map('python', field.attrs, True) in ['bool', 'int', 'float'] %}
|
||||
if self.{{ field.name }}:
|
||||
builder.{{ FbsType.FBS2PREPEND[field.type.basetype] }}({{ field.id }}, self.{{ field.name }}, 0)
|
||||
|
||||
{% else %}
|
||||
# FIXME [1]
|
||||
# raise NotImplementedError('implement builder [1] for type "{}"'.format({{ field.type.map('python', field.attrs, True) }}))
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
return builder.EndObject()
|
||||
@@ -0,0 +1,253 @@
|
||||
{% if is_first_by_category %}
|
||||
##
|
||||
## service types (aka "APIs")
|
||||
##
|
||||
from uuid import UUID
|
||||
from typing import List
|
||||
|
||||
import txaio
|
||||
from txaio.interfaces import ILogger
|
||||
|
||||
from autobahn.wamp.types import PublishOptions, SubscribeOptions, EventDetails, CallOptions, CallDetails, RegisterOptions
|
||||
from autobahn.wamp.request import Publication, Subscription, Registration
|
||||
from autobahn.wamp.interfaces import ISession
|
||||
from autobahn.xbr import IDelegate
|
||||
|
||||
Oid = UUID
|
||||
Oids = List[UUID]
|
||||
Void = type(None)
|
||||
|
||||
|
||||
{% endif %}
|
||||
class {{ metadata.classname }}(object):
|
||||
"""
|
||||
{{ metadata.docs }}
|
||||
|
||||
Interface UUID: ``{{ metadata.attrs.uuid }}``
|
||||
"""
|
||||
__slots__ = [
|
||||
'log',
|
||||
'_x_api_id',
|
||||
'_x_prefix',
|
||||
'_x_session',
|
||||
'_x_delegate',
|
||||
'_x_regs',
|
||||
'_x_subs',
|
||||
]
|
||||
|
||||
def __init__(self, prefix: str, log: Optional[ILogger]=None):
|
||||
"""
|
||||
|
||||
:param prefix: The URI prefix under which this API will be instantiated under on the realm joined.
|
||||
:param log: If provided, log to this logger, else create a new one internally.
|
||||
"""
|
||||
if log:
|
||||
self.log = log
|
||||
else:
|
||||
import txaio
|
||||
self.log = txaio.make_logger()
|
||||
self._x_api_id = uuid.UUID('{{ metadata.attrs.uuid }}')
|
||||
self._x_prefix = prefix
|
||||
self._x_session = None
|
||||
self._x_delegate = None
|
||||
self._x_regs = None
|
||||
self._x_subs = None
|
||||
|
||||
@property
|
||||
def api(self) -> uuid.UUID:
|
||||
"""
|
||||
Interface UUID of this API (``{{ metadata.attrs.uuid }}``).
|
||||
"""
|
||||
return self._x_api_id
|
||||
|
||||
@property
|
||||
def prefix(self) -> str:
|
||||
"""
|
||||
WAMP URI prefix under which this API is instantiated.
|
||||
"""
|
||||
return self._x_prefix
|
||||
|
||||
# WAMP PubSub part of the API:
|
||||
|
||||
{% for call_name in metadata.calls_by_id %}
|
||||
{% if metadata.calls[call_name].attrs['type'] == 'topic' %}
|
||||
async def publish_{{ call_name }}(self, evt: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, options: Optional[PublishOptions] = None) -> Optional[Publication]:
|
||||
"""
|
||||
As an **interface provider**, publish event:
|
||||
|
||||
{{ metadata.calls[call_name].docs }}
|
||||
|
||||
:param evt: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
||||
:returns: When doing an acknowledged publish, the WAMP publication is returned.
|
||||
"""
|
||||
assert self._x_session and self._x_session.is_attached()
|
||||
|
||||
topic = '{}.{{ call_name }}'.format(self._x_prefix)
|
||||
payload = evt.marshal()
|
||||
if self._x_delegate:
|
||||
key_id, enc_ser, ciphertext = await self._x_delegate.wrap(self._x_api_id, topic, payload)
|
||||
if options.acknowledge:
|
||||
pub = await self._x_session.publish(topic, key_id, enc_ser, ciphertext, options=options)
|
||||
else:
|
||||
self._x_session.publish(topic, key_id, enc_ser, ciphertext, options=options)
|
||||
pub = None
|
||||
else:
|
||||
if options.acknowledge:
|
||||
pub = await self._x_session.publish(topic, payload, options=options)
|
||||
else:
|
||||
self._x_session.publish(topic, payload, options=options)
|
||||
pub = None
|
||||
return pub
|
||||
|
||||
def receive_{{ call_name }}(self, evt: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, details: Optional[EventDetails] = None):
|
||||
"""
|
||||
As an **interface consumer**, receive event:
|
||||
|
||||
{{ metadata.calls[call_name].docs }}
|
||||
|
||||
:param evt: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
||||
"""
|
||||
raise NotImplementedError('event handler for "{{ call_name }}" not implemented')
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# WAMP RPC part of the API:
|
||||
|
||||
{% for call_name in metadata.calls_by_id %}
|
||||
{% if metadata.calls[call_name].attrs['type'] == 'procedure' %}
|
||||
async def call_{{ call_name }}(self, req: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, options: Optional[CallOptions] = None) -> {{ repo.objs[metadata.calls[call_name].response.name].map('python', required=False, objtype_as_string=True) }}:
|
||||
"""
|
||||
As an **interface consumer**, call procedure:
|
||||
|
||||
{{ metadata.calls[call_name].docs }}
|
||||
|
||||
:param req: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
||||
:returns: {{ repo.objs[metadata.calls[call_name].response.name].docs }}
|
||||
"""
|
||||
assert self._x_session and self._x_session.is_attached()
|
||||
|
||||
procedure = '{}.{{ call_name }}'.format(self._x_prefix)
|
||||
payload = req.marshal()
|
||||
if self._x_delegate:
|
||||
key_id, enc_ser, ciphertext = await self._x_delegate.wrap(self._x_api_id, procedure, payload)
|
||||
result = await self._x_session.call(procedure, key_id, enc_ser, ciphertext, options=options)
|
||||
else:
|
||||
result = await self._x_session.call(procedure, payload, options=options)
|
||||
return result
|
||||
|
||||
def invoke_{{ call_name }}(self, req: {{ repo.objs[metadata.calls[call_name].request.name].map('python', required=False, objtype_as_string=True) }}, details: Optional[CallDetails] = None) -> {{ repo.objs[metadata.calls[call_name].response.name].map('python', required=False, objtype_as_string=True) }}:
|
||||
"""
|
||||
As an **interface provider**, process call invocation:
|
||||
|
||||
{{ metadata.calls[call_name].docs }}
|
||||
|
||||
:param req: {{ repo.objs[metadata.calls[call_name].request.name].docs }}
|
||||
:returns: {{ repo.objs[metadata.calls[call_name].response.name].docs }}
|
||||
"""
|
||||
raise NotImplementedError('call invocation handler for "{{ call_name }}" not implemented')
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
@property
|
||||
def session(self) -> Optional[ISession]:
|
||||
"""
|
||||
WAMP session this API is attached to.
|
||||
"""
|
||||
return self._x_session
|
||||
|
||||
@property
|
||||
def delegate(self) -> Optional[IDelegate]:
|
||||
"""
|
||||
XBR (buyer/seller) delegate this API is attached to.
|
||||
"""
|
||||
return self._x_delegate
|
||||
|
||||
async def attach(self, session: ISession, delegate: Optional[IDelegate]):
|
||||
"""
|
||||
Attach this API instance with the given session and delegate, and under the given WAMP URI prefix.
|
||||
|
||||
:param session: WAMP session this API instance is attached to.
|
||||
:param delegate: If using end-to-end data encryption, XBR ("buyer/seller") delegate used by this API instance.
|
||||
"""
|
||||
assert self._x_session is None and session.is_attached()
|
||||
|
||||
self._x_session = session
|
||||
self._x_delegate = delegate
|
||||
|
||||
# WAMP PubSub part of the API:
|
||||
|
||||
subscriptions = []
|
||||
{% for call_name in metadata.calls_by_id %}
|
||||
{% if metadata.calls[call_name].attrs['type'] == 'topic' %}
|
||||
if self._x_delegate:
|
||||
async def do_receive_{{ call_name }}(key_id, enc_ser, ciphertext, details=None):
|
||||
try:
|
||||
payload = await self._x_delegate.unwrap(key_id, enc_ser, ciphertext)
|
||||
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(payload)
|
||||
except:
|
||||
self.log.failure()
|
||||
else:
|
||||
self.receive_{{ call_name }}(obj, details=details)
|
||||
else:
|
||||
def do_receive_{{ call_name }}(evt, details=None):
|
||||
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(evt)
|
||||
self.receive_{{ call_name }}(obj, details=details)
|
||||
|
||||
topic = '{}.{{ call_name }}'.format(self._x_prefix)
|
||||
sub = await self._x_session.subscribe(do_receive_{{ call_name }}, topic, options=SubscribeOptions(details=True))
|
||||
subscriptions.append(sub)
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
for sub in subscriptions:
|
||||
self.log.info('Subscription {} created for "{}"'.format(sub.id, sub.topic))
|
||||
self._x_subs = subscriptions
|
||||
|
||||
# WAMP RPC part of the API:
|
||||
|
||||
registrations = []
|
||||
{% for call_name in metadata.calls_by_id %}
|
||||
{% if metadata.calls[call_name].attrs['type'] == 'procedure' %}
|
||||
if self._x_delegate:
|
||||
async def do_invoke_{{ call_name }}(key_id, enc_ser, ciphertext, details=None):
|
||||
try:
|
||||
payload = await self._x_delegate.unwrap(key_id, enc_ser, ciphertext)
|
||||
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(payload)
|
||||
except:
|
||||
self.log.failure()
|
||||
else:
|
||||
self.invoke_{{ call_name }}(obj, details=details)
|
||||
else:
|
||||
def do_invoke_{{ call_name }}(req, details=None):
|
||||
obj = {{ repo.objs[metadata.calls[call_name].request.name].map('python') }}.parse(req)
|
||||
self.invoke_{{ call_name }}(obj, details=details)
|
||||
|
||||
procedure = '{}.{{ call_name }}'.format(self._x_prefix)
|
||||
reg = await self._x_session.register(do_invoke_{{ call_name }}, procedure, options=RegisterOptions(details=True))
|
||||
registrations.append(reg)
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
for reg in registrations:
|
||||
self.log.info('Registration {} created for "{}"'.format(reg.id, reg.procedure))
|
||||
self._x_regs = registrations
|
||||
|
||||
def detach(self):
|
||||
"""
|
||||
Detach this API instance from the session and delegate.
|
||||
"""
|
||||
assert self._x_session is not None
|
||||
|
||||
dl = []
|
||||
if self._x_session and self._x_session.is_attached():
|
||||
for reg in self._x_regs:
|
||||
dl.append(reg.unregister())
|
||||
for sub in self._x_subs:
|
||||
dl.append(sub.unsubscribe())
|
||||
|
||||
self._x_session = None
|
||||
self._x_delegate = None
|
||||
|
||||
return txaio.gather(dl, consume_exceptions=True)
|
||||
@@ -0,0 +1,6 @@
|
||||
from .{{ metadata.module_relimport }} import {{ metadata.classname }}
|
||||
|
||||
def test_{{ metadata.classname }}():
|
||||
{% for value_name in metadata.values %}
|
||||
assert {{ metadata.classname }}.{{ value_name }} == {{ metadata.values[value_name].value }}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1 @@
|
||||
# FIXME: add module level unit tests
|
||||
@@ -0,0 +1,220 @@
|
||||
{% if render_imports %}
|
||||
|
||||
import os
|
||||
import random
|
||||
import timeit
|
||||
import uuid
|
||||
import cbor2
|
||||
|
||||
import txaio
|
||||
txaio.use_twisted() # noqa
|
||||
|
||||
from autobahn import util
|
||||
from autobahn.wamp.serializer import JsonObjectSerializer, MsgPackObjectSerializer, \
|
||||
CBORObjectSerializer, UBJSONObjectSerializer
|
||||
|
||||
import flatbuffers
|
||||
import pytest
|
||||
import numpy as np
|
||||
from txaio import time_ns
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def builder():
|
||||
_builder = flatbuffers.Builder(0)
|
||||
return _builder
|
||||
|
||||
|
||||
_SERIALIZERS = [
|
||||
JsonObjectSerializer(),
|
||||
MsgPackObjectSerializer(),
|
||||
CBORObjectSerializer(),
|
||||
UBJSONObjectSerializer(),
|
||||
]
|
||||
|
||||
{% endif %}
|
||||
from {{ metadata.module_relimport }} import {{ metadata.classname }}
|
||||
|
||||
|
||||
def fill_{{ metadata.classname }}(obj: {{ metadata.classname }}):
|
||||
{% if metadata.fields_by_id|length == 0 %}
|
||||
# class has no fields
|
||||
pass
|
||||
{% else %}
|
||||
{% for field in metadata.fields_by_id %}
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
obj.{{ field.name }} = util.generate_activation_code()
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
obj.{{ field.name }} = os.urandom(32)
|
||||
{% elif field.type.map('python', field.attrs, True) in ['int', 'long'] %}
|
||||
# FIXME: enum vs int
|
||||
# obj.{{ field.name }} = random.randint(0, 2**31 - 1)
|
||||
obj.{{ field.name }} = random.randint(0, 3)
|
||||
{% elif field.type.map('python', field.attrs, True) in ['float', 'double'] %}
|
||||
obj.{{ field.name }} = random.random()
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
obj.{{ field.name }} = random.random() > 0.5
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
obj.{{ field.name }} = uuid.uuid4()
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
obj.{{ field.name }} = np.datetime64(time_ns(), 'ns')
|
||||
{% else %}
|
||||
obj.{{ field.name }} = None
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
def fill_{{ metadata.classname }}_empty(obj: {{ metadata.classname }}):
|
||||
{% if metadata.fields_by_id|length == 0 %}
|
||||
# class has no fields
|
||||
pass
|
||||
{% else %}
|
||||
{% for field in metadata.fields_by_id %}
|
||||
obj.{{ field.name }} = None
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def {{ metadata.classname }}_obj():
|
||||
_obj: {{ metadata.classname }} = {{ metadata.classname }}()
|
||||
fill_{{ metadata.classname }}(_obj)
|
||||
return _obj
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_roundtrip({{ metadata.classname }}_obj, builder):
|
||||
# serialize to bytes (flatbuffers) from python object
|
||||
obj = {{ metadata.classname }}_obj.build(builder)
|
||||
builder.Finish(obj)
|
||||
data = builder.Output()
|
||||
|
||||
# check length of serialized object data
|
||||
print('{} serialized object length = {} bytes'.format('{{ metadata.classname }}', len(data)))
|
||||
|
||||
# create python object from bytes (flatbuffers)
|
||||
_obj: {{ metadata.classname }} = {{ metadata.classname }}_obj.cast(data)
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
assert _obj.{{ field.name }} == {{ metadata.classname }}_obj.{{ field.name }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_empty(builder):
|
||||
empty_obj = {{ metadata.classname }}()
|
||||
fill_{{ metadata.classname }}_empty(empty_obj)
|
||||
|
||||
# check the object was initialized correctly
|
||||
{% for field in metadata.fields_by_id %}
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
assert empty_obj.{{ field.name }} == ''
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
assert empty_obj.{{ field.name }} == b''
|
||||
{% elif field.type.map('python', field.attrs, True) in ['int', 'long'] %}
|
||||
assert empty_obj.{{ field.name }} == 0
|
||||
{% elif field.type.map('python', field.attrs, True) in ['float', 'double'] %}
|
||||
assert empty_obj.{{ field.name }} == 0.0
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
assert empty_obj.{{ field.name }} is False
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
assert empty_obj.{{ field.name }} == uuid.UUID(bytes=b'\0'*16)
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
assert empty_obj.{{ field.name }} == np.datetime64(0, 'ns')
|
||||
{% else %}
|
||||
assert empty_obj.{{ field.name }} is None
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
# serialize to bytes (flatbuffers) from python object
|
||||
obj = empty_obj.build(builder)
|
||||
builder.Finish(obj)
|
||||
data = builder.Output()
|
||||
|
||||
# check length of serialized object data
|
||||
print('{} serialized object length = {} bytes'.format('{{ metadata.classname }}', len(data)))
|
||||
|
||||
# create python object from bytes (flatbuffers)
|
||||
_obj: {{ metadata.classname }} = {{ metadata.classname }}.cast(data)
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
{% if field.type.map('python', field.attrs, True) == 'str' %}
|
||||
assert _obj.{{ field.name }} == ''
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bytes' %}
|
||||
assert _obj.{{ field.name }} == b''
|
||||
{% elif field.type.map('python', field.attrs, True) in ['int', 'long'] %}
|
||||
assert _obj.{{ field.name }} == 0
|
||||
{% elif field.type.map('python', field.attrs, True) in ['float', 'double'] %}
|
||||
assert _obj.{{ field.name }} == 0.0
|
||||
{% elif field.type.map('python', field.attrs, True) == 'bool' %}
|
||||
assert _obj.{{ field.name }} is False
|
||||
{% elif field.type.map('python', field.attrs, True) == 'uuid.UUID' %}
|
||||
assert _obj.{{ field.name }} == uuid.UUID(bytes=b'\0'*16)
|
||||
{% elif field.type.map('python', field.attrs, True) == 'np.datetime64' %}
|
||||
assert _obj.{{ field.name }} == np.datetime64(0, 'ns')
|
||||
{% else %}
|
||||
assert _obj.{{ field.name }} is None
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_roundtrip_perf({{ metadata.classname }}_obj, builder):
|
||||
obj = {{ metadata.classname }}_obj.build(builder)
|
||||
builder.Finish(obj)
|
||||
data = builder.Output()
|
||||
scratch = {'value': 0}
|
||||
|
||||
def loop():
|
||||
_obj: {{ metadata.classname }} = {{ metadata.classname }}.cast(data)
|
||||
{% for field in metadata.fields_by_id %}
|
||||
assert _obj.{{ field.name }} == {{ metadata.classname }}_obj.{{ field.name }}
|
||||
{% endfor %}
|
||||
scratch['value'] += 1
|
||||
|
||||
loop_n = 7
|
||||
loop_m = 20000
|
||||
samples = []
|
||||
print('measuring:')
|
||||
for i in range(loop_n):
|
||||
secs = timeit.timeit(loop, number=loop_m)
|
||||
ops = round(float(loop_m) / secs, 1)
|
||||
samples.append(ops)
|
||||
print('{} objects/sec performance'.format(ops))
|
||||
|
||||
samples = sorted(samples)
|
||||
ops50 = samples[int(len(samples) / 2)]
|
||||
print('RESULT: {} objects/sec median performance'.format(ops50))
|
||||
|
||||
assert ops50 > 1000
|
||||
print(scratch['value'])
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_marshal_parse({{ metadata.classname }}_obj, builder):
|
||||
obj = {{ metadata.classname }}_obj.marshal()
|
||||
_obj = {{ metadata.classname }}_obj.parse(obj)
|
||||
{% for field in metadata.fields_by_id %}
|
||||
assert _obj.{{ field.name }} == {{ metadata.classname }}_obj.{{ field.name }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_marshal_cbor_parse({{ metadata.classname }}_obj, builder):
|
||||
obj = {{ metadata.classname }}_obj.marshal()
|
||||
data = cbor2.dumps(obj)
|
||||
print('serialized {} to {} bytes (cbor)'.format({{ metadata.classname }}, len(data)))
|
||||
_obj_raw = cbor2.loads(data)
|
||||
_obj = {{ metadata.classname }}_obj.parse(_obj_raw)
|
||||
{% for field in metadata.fields_by_id %}
|
||||
assert _obj.{{ field.name }} == {{ metadata.classname }}_obj.{{ field.name }}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
def test_{{ metadata.classname }}_ab_serializer_roundtrip({{ metadata.classname }}_obj, builder):
|
||||
obj = {{ metadata.classname }}_obj.marshal()
|
||||
for ser in _SERIALIZERS:
|
||||
data = ser.serialize(obj)
|
||||
print('serialized {} to {} bytes ({})'.format({{ metadata.classname }}, len(data), ser.NAME))
|
||||
msg2 = ser.unserialize(data)[0]
|
||||
obj2 = {{ metadata.classname }}.parse(msg2)
|
||||
|
||||
{% for field in metadata.fields_by_id %}
|
||||
assert obj2.{{ field.name }} == {{ metadata.classname }}_obj.{{ field.name }}
|
||||
{% endfor %}
|
||||
@@ -0,0 +1 @@
|
||||
# FIXME: add service level unit tests
|
||||
@@ -0,0 +1,342 @@
|
||||
{% if is_first_by_category %}
|
||||
##
|
||||
## object types
|
||||
##
|
||||
|
||||
{% endif %}
|
||||
{% if render_imports %}
|
||||
import uuid
|
||||
import pprint
|
||||
from typing import Dict, List, Optional, TypeVar
|
||||
|
||||
from autobahn.wamp.request import Publication, Subscription, Registration
|
||||
|
||||
import flatbuffers
|
||||
from flatbuffers.compat import import_numpy
|
||||
np = import_numpy()
|
||||
|
||||
{% endif %}
|
||||
|
||||
# https://stackoverflow.com/a/46064289/884770
|
||||
T_{{ metadata.classname }} = TypeVar('T_{{ metadata.classname }}', bound='{{ metadata.classname }}')
|
||||
|
||||
|
||||
class {{ metadata.classname }}(object):
|
||||
"""
|
||||
{{ metadata.docs }}
|
||||
"""
|
||||
__slots__ = ['_tab', {% for field_name in metadata.fields_by_id %}'_{{ metadata.fields[field_name].name }}', {% endfor %}]
|
||||
|
||||
def __init__(self):
|
||||
# the underlying FlatBuffers vtable
|
||||
self._tab = None
|
||||
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
# {{ metadata.fields[field_name]['docs'] }}
|
||||
self._{{ metadata.fields[field_name].name }} = None
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
@property
|
||||
def {{ metadata.fields[field_name].name }}(self) -> {{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}:
|
||||
"""
|
||||
{{ metadata.fields[field_name]['docs'] }}
|
||||
"""
|
||||
if self._{{ metadata.fields[field_name].name }} is None and self._tab:
|
||||
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset({{ metadata.fields[field_name].offset }}))
|
||||
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'str' %}
|
||||
# access type "string" attribute:
|
||||
value = ''
|
||||
if o != 0:
|
||||
_value = self._tab.String(o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value.decode('utf8')
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bytes' %}
|
||||
# access type "bytes" attribute:
|
||||
value = b''
|
||||
if o != 0:
|
||||
_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
_value = memoryview(self._tab.Bytes)[_off:_off + _len]
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['int', 'float', 'double'] %}
|
||||
# access type "int|float|double" attribute:
|
||||
value = 0
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.{{ FbsType.FBS2FLAGS[metadata.fields[field_name].type.basetype] }}, o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bool' %}
|
||||
# access type "bool" attribute:
|
||||
value = False
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)
|
||||
if _value is not None:
|
||||
value = _value
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'uuid.UUID' %}
|
||||
# access type "uuid.UUID" attribute:
|
||||
value = uuid.UUID(bytes=b'\x00' * 16)
|
||||
if o != 0:
|
||||
_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
_value = memoryview(self._tab.Bytes)[_off:_off + _len]
|
||||
if _value is not None:
|
||||
value = uuid.UUID(bytes=bytes(_value))
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'np.datetime64' %}
|
||||
# access type "np.datetime64" attribute:
|
||||
value = np.datetime64(0, 'ns')
|
||||
if o != 0:
|
||||
_value = self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
|
||||
if value is not None:
|
||||
value = np.datetime64(_value, 'ns')
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Vector %}
|
||||
# access type "Vector" attribute:
|
||||
value = []
|
||||
if o != 0:
|
||||
_start_off = self._tab.Vector(o)
|
||||
_len = self._tab.VectorLen(o)
|
||||
for j in range(_len):
|
||||
_off = _start_off + flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
|
||||
_off = self._tab.Indirect(_off)
|
||||
{% if metadata.fields[field_name].type.element == FbsType.Obj %}
|
||||
_value = {{ metadata.fields[field_name].type.objtype.split('.')[-1] }}.cast(self._tab.Bytes, _off)
|
||||
{% else %}
|
||||
# FIXME [8]
|
||||
{% endif %}
|
||||
value.append(_value)
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Obj %}
|
||||
# access type "Object" attribute:
|
||||
value = {{ metadata.fields[field_name].type.objtype.split('.')[-1] }}()
|
||||
if o != 0:
|
||||
_off = self._tab.Indirect(o + self._tab.Pos)
|
||||
value = {{ metadata.fields[field_name].type.objtype.split('.')[-1] }}.cast(self._tab.Bytes, _off)
|
||||
|
||||
{% else %}
|
||||
# FIXME [5]
|
||||
raise NotImplementedError('implement processing [5] of FlatBuffers type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}))
|
||||
{% endif %}
|
||||
assert value is not None
|
||||
self._{{ metadata.fields[field_name].name }} = value
|
||||
return self._{{ metadata.fields[field_name].name }}
|
||||
|
||||
@{{ metadata.fields[field_name].name }}.setter
|
||||
def {{ metadata.fields[field_name].name }}(self, value: Optional[{{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}]):
|
||||
if value is not None:
|
||||
self._{{ metadata.fields[field_name].name }} = value
|
||||
else:
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'str' %}
|
||||
# set default value on type "string" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = ''
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bytes' %}
|
||||
# set default value on type "bytes" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = b''
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['int', 'float', 'double'] %}
|
||||
# set default value on type "int|float|double" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = 0
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bool' %}
|
||||
# set default value on type "bool" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = False
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'uuid.UUID' %}
|
||||
# set default value on type "uuid.UUID" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = uuid.UUID(bytes=b'\x00' * 16)
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'np.datetime64' %}
|
||||
# set default value on type "np.datetime64" attribute:
|
||||
self._{{ metadata.fields[field_name].name }} = np.datetime64(0, 'ns')
|
||||
# set default value on type "List" attribute:
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Vector %}
|
||||
self._{{ metadata.fields[field_name].name }} = []
|
||||
# set default value on type "Object" attribute:
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Obj %}
|
||||
self._{{ metadata.fields[field_name].name }} = {{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}()
|
||||
{% else %}
|
||||
# FIXME [6]
|
||||
raise NotImplementedError('implement processing [2] of FlatBuffers type "{}", basetype {}'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}, {{ metadata.fields[field_name].type.basetype }}))
|
||||
{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
@staticmethod
|
||||
def parse(data: Dict) -> T_{{ metadata.classname }}:
|
||||
"""
|
||||
Parse generic, native language object into a typed, native language object.
|
||||
|
||||
:param data: Generic native language object to parse, e.g. output of ``cbor2.loads``.
|
||||
|
||||
:returns: Typed object of this class.
|
||||
"""
|
||||
for key in data.keys():
|
||||
assert key in {{ metadata.fields_by_id }}
|
||||
obj = {{ metadata.classname }}()
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
if '{{ field_name }}' in data:
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'str' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == str), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
obj.{{ metadata.fields[field_name].name }} = data['{{ field_name }}']
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bytes' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == bytes), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
obj.{{ metadata.fields[field_name].name }} = data['{{ field_name }}']
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'int' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == int), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
obj.{{ metadata.fields[field_name].name }} = data['{{ field_name }}']
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'float' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == float), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
obj.{{ metadata.fields[field_name].name }} = data['{{ field_name }}']
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'bool' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == bool), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
obj.{{ metadata.fields[field_name].name }} = data['{{ field_name }}']
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'uuid.UUID' %}
|
||||
assert (data['{{ field_name }}'] is None or (type(data['{{ field_name }}']) == bytes and len(data['{{ field_name }}']) == 16)), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
if data['{{ field_name }}'] is not None:
|
||||
obj.{{ metadata.fields[field_name].name }} = uuid.UUID(bytes=data['{{ field_name }}'])
|
||||
else:
|
||||
obj.{{ metadata.fields[field_name].name }} = None
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'np.datetime64' %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == int), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
if data['{{ field_name }}'] is not None:
|
||||
obj.{{ metadata.fields[field_name].name }} = np.datetime64(data['{{ field_name }}'], 'ns')
|
||||
else:
|
||||
obj.{{ metadata.fields[field_name].name }} = np.datetime64(0, 'ns')
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Vector %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == list), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
_value = []
|
||||
for v in data['{{ field_name }}']:
|
||||
{% if metadata.fields[field_name].type.element == FbsType.Obj %}
|
||||
_value.append({{ metadata.fields[field_name].type.objtype.split('.')[-1] }}.parse(v))
|
||||
{% else %}
|
||||
_value.append(v)
|
||||
{% endif %}
|
||||
obj.{{ metadata.fields[field_name].name }} = _value
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Obj %}
|
||||
assert (data['{{ field_name }}'] is None or type(data['{{ field_name }}']) == dict), '{} has wrong type {}'.format('{{ field_name }}', type(data['{{ field_name }}']))
|
||||
_value = {{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}.parse(data['{{ field_name }}'])
|
||||
obj.{{ metadata.fields[field_name].name }} = _value
|
||||
|
||||
{% else %}
|
||||
# FIXME [3]
|
||||
raise NotImplementedError('implement processing [3] of FlatBuffers type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}))
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
return obj
|
||||
|
||||
def marshal(self) -> Dict:
|
||||
"""
|
||||
Marshal all data contained in this typed native object into a generic object.
|
||||
|
||||
:returns: Generic object that can be serialized to bytes using e.g. ``cbor2.dumps``.
|
||||
"""
|
||||
obj = {
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['str', 'bytes', 'int', 'long', 'float', 'double', 'bool'] %}
|
||||
'{{ metadata.fields[field_name].name }}': self.{{ metadata.fields[field_name].name }},
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'uuid.UUID' %}
|
||||
'{{ metadata.fields[field_name].name }}': self.{{ metadata.fields[field_name].name }}.bytes if self.{{ metadata.fields[field_name].name }} is not None else None,
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'np.datetime64' %}
|
||||
'{{ metadata.fields[field_name].name }}': int(self.{{ metadata.fields[field_name].name }}) if self.{{ metadata.fields[field_name].name }} is not None else None,
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Vector %}
|
||||
{% if metadata.fields[field_name].type.element == FbsType.Obj %}
|
||||
'{{ metadata.fields[field_name].name }}': [o.marshal() for o in self.{{ metadata.fields[field_name].name }}] if self.{{ metadata.fields[field_name].name }} is not None else None,
|
||||
{% else %}
|
||||
'{{ metadata.fields[field_name].name }}': self.{{ metadata.fields[field_name].name }},
|
||||
{% endif %}
|
||||
|
||||
{% elif metadata.fields[field_name].type.basetype == FbsType.Obj %}
|
||||
'{{ metadata.fields[field_name].name }}': self.{{ metadata.fields[field_name].name }}.marshal() if self.{{ metadata.fields[field_name].name }} is not None else None,
|
||||
|
||||
{% else %}
|
||||
# FIXME [4]: implement processing [4] of FlatBuffers type "{{ metadata.fields[field_name].type | string }}" (Python type "{{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
return obj
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
Return string representation of this object, suitable for e.g. logging.
|
||||
|
||||
:returns: String representation of this object.
|
||||
"""
|
||||
return '\n{}\n'.format(pprint.pformat(self.marshal()))
|
||||
|
||||
@staticmethod
|
||||
def cast(buf: bytes, offset: int = 0) -> T_{{ metadata.classname }}:
|
||||
"""
|
||||
Cast a FlatBuffers raw input buffer as a typed object of this class.
|
||||
|
||||
:param buf: The raw input buffer to cast.
|
||||
:param offset: Offset into raw buffer from which to cast flatbuffers from.
|
||||
|
||||
:returns: New native object that wraps the FlatBuffers raw buffer.
|
||||
"""
|
||||
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
|
||||
x = {{ metadata.classname }}()
|
||||
x._tab = flatbuffers.table.Table(buf, n + offset)
|
||||
return x
|
||||
|
||||
def build(self, builder):
|
||||
"""
|
||||
Build a FlatBuffers raw output buffer from this typed object.
|
||||
|
||||
:returns: Constructs the FlatBuffers using the builder and
|
||||
returns ``builder.EndObject()``.
|
||||
"""
|
||||
# first, write all string|bytes|etc typed attribute values (in order) to the buffer
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['str', 'bytes'] %}
|
||||
_{{ field_name }} = self.{{ field_name }}
|
||||
if _{{ field_name }}:
|
||||
_{{ field_name }} = builder.CreateString(_{{ field_name }})
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'uuid.UUID' %}
|
||||
_{{ field_name }} = self.{{ field_name }}.bytes if self.{{ field_name }} else None
|
||||
if _{{ field_name }}:
|
||||
_{{ field_name }} = builder.CreateString(_{{ field_name }})
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
# now start a new object in the buffer and write the actual object attributes (in field
|
||||
# order) to the buffer
|
||||
builder.StartObject({{ metadata.fields_by_id|length }})
|
||||
|
||||
{% for field_name in metadata.fields_by_id %}
|
||||
|
||||
{% if metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['str', 'bytes', 'uuid.UUID'] %}
|
||||
if _{{ field_name }}:
|
||||
builder.PrependUOffsetTRelativeSlot({{ metadata.fields[field_name].id }}, flatbuffers.number_types.UOffsetTFlags.py_type(_{{ field_name }}), 0)
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) == 'np.datetime64' %}
|
||||
if self.{{ field_name }}:
|
||||
builder.PrependUint64Slot({{ metadata.fields[field_name].id }}, int(self.{{ field_name }}), 0)
|
||||
|
||||
{% elif metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) in ['bool', 'int', 'float'] %}
|
||||
if self.{{ field_name }}:
|
||||
builder.{{ FbsType.FBS2PREPEND[metadata.fields[field_name].type.basetype] }}({{ metadata.fields[field_name].id }}, self.{{ field_name }}, 0)
|
||||
|
||||
{% else %}
|
||||
# FIXME [1]
|
||||
# raise NotImplementedError('implement builder [1] for type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }}))
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
return builder.EndObject()
|
||||
Reference in New Issue
Block a user