This commit is contained in:
2024-12-17 14:36:15 -08:00
parent b2dbf46d28
commit 06d106de53
17731 changed files with 3037186 additions and 144 deletions

View File

@@ -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 %}

View File

@@ -0,0 +1,5 @@
# Python module "{{ modulename }}"
from . import {{ ', '.join(imports) }}
__all__ = [{{ ', '.join(imports) }}]

View File

@@ -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()

View File

@@ -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)

View File

@@ -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 %}

View File

@@ -0,0 +1 @@
# FIXME: add module level unit tests

View File

@@ -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 %}

View File

@@ -0,0 +1 @@
# FIXME: add service level unit tests

View File

@@ -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()