From 4de368bc1b8bb1ebc2143eb07746c265b97b83f7 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 12 Dec 2020 14:49:40 +0100 Subject: [PATCH 01/11] sync --- autobahn/xbr/_cli.py | 10 ++++++++-- autobahn/xbr/templates/test_enum.py.jinja2 | 7 ++++++- autobahn/xbr/templates/test_obj.py.jinja2 | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/autobahn/xbr/_cli.py b/autobahn/xbr/_cli.py index ebd82bc7d..555ad9347 100644 --- a/autobahn/xbr/_cli.py +++ b/autobahn/xbr/_cli.py @@ -1021,11 +1021,17 @@ def _main(): # com.things.home.device.HomeDeviceVendor => com.things.home.device modulename = '.'.join(metadata.name.split('.')[0:-1]) - is_first = modulename not in code_modules - is_first_by_category = (modulename, category) not in is_first_by_category_modules metadata.modulename = modulename + + # com.things.home.device.HomeDeviceVendor => HomeDeviceVendor metadata.classname = metadata.name.split('.')[-1].strip() + # com.things.home.device => device + metadata.module_relimport = modulename.split('.')[-1] + + is_first = modulename not in code_modules + is_first_by_category = (modulename, category) not in is_first_by_category_modules + if is_first_by_category: is_first_by_category_modules[(modulename, category)] = True diff --git a/autobahn/xbr/templates/test_enum.py.jinja2 b/autobahn/xbr/templates/test_enum.py.jinja2 index 6c290a9c1..c20bf28ba 100644 --- a/autobahn/xbr/templates/test_enum.py.jinja2 +++ b/autobahn/xbr/templates/test_enum.py.jinja2 @@ -1 +1,6 @@ -# FIXME: add enum (type) level unit tests +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 %} diff --git a/autobahn/xbr/templates/test_obj.py.jinja2 b/autobahn/xbr/templates/test_obj.py.jinja2 index cabff87a5..e5fc036c4 100644 --- a/autobahn/xbr/templates/test_obj.py.jinja2 +++ b/autobahn/xbr/templates/test_obj.py.jinja2 @@ -20,7 +20,7 @@ def builder(): {% endif %} -from .device import {{ metadata.classname }} +from .{{ metadata.module_relimport }} import {{ metadata.classname }} def fill_{{ metadata.classname }}(obj: {{ metadata.classname }}): From 2e24e5733768950a5cc7825ceba1e15859ae0008 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 12 Dec 2020 17:01:09 +0100 Subject: [PATCH 02/11] sync --- autobahn/xbr/templates/obj.py.jinja2 | 56 ++++++++++++++++++++++- autobahn/xbr/templates/test_obj.py.jinja2 | 20 ++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/autobahn/xbr/templates/obj.py.jinja2 b/autobahn/xbr/templates/obj.py.jinja2 index b14e1ed95..85efccc22 100644 --- a/autobahn/xbr/templates/obj.py.jinja2 +++ b/autobahn/xbr/templates/obj.py.jinja2 @@ -48,7 +48,61 @@ class {{ metadata.classname }}(object): :returns: Generic object that can be serialized to bytes using eg ``json.dumps``. """ - obj = { {% for field_name in metadata.fields_by_id %}'{{ metadata.fields[field_name].name }}': self.{{ metadata.fields[field_name].name }}, {% endfor %} } + 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, + {% else %} + raise NotImplementedError('implement processing of FlatBuffers type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }})) + {% endif %} + {% endfor %} + } + return obj + + @staticmethod + def parse(data: Dict) -> object: + """ + """ + 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') + {% else %} + raise NotImplementedError('implement processing of FlatBuffers type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }})) + {% endif %} + {% endfor %} return obj def __str__(self) -> str: diff --git a/autobahn/xbr/templates/test_obj.py.jinja2 b/autobahn/xbr/templates/test_obj.py.jinja2 index e5fc036c4..06fff4077 100644 --- a/autobahn/xbr/templates/test_obj.py.jinja2 +++ b/autobahn/xbr/templates/test_obj.py.jinja2 @@ -3,6 +3,7 @@ import os import random import timeit import uuid +import cbor2 import txaio txaio.use_twisted() # noqa @@ -163,3 +164,22 @@ def test_{{ metadata.classname }}_roundtrip_perf({{ metadata.classname }}_obj, b 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_name in metadata.fields_by_id %} + assert _obj.{{ metadata.fields[field_name].name }} == {{ metadata.classname }}_obj.{{ metadata.fields[field_name].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'.format({{ metadata.classname }}, len(data))) + _obj_raw = cbor2.loads(data) + _obj = {{ metadata.classname }}_obj.parse(_obj_raw) + {% for field_name in metadata.fields_by_id %} + assert _obj.{{ metadata.fields[field_name].name }} == {{ metadata.classname }}_obj.{{ metadata.fields[field_name].name }} + {% endfor %} From c21f7d011d73f2e953b678baa3ad97a840c74e52 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sat, 12 Dec 2020 18:22:39 +0100 Subject: [PATCH 03/11] sync --- autobahn/xbr/templates/obj.py.jinja2 | 166 +++++++++++----------- autobahn/xbr/templates/test_obj.py.jinja2 | 26 +++- 2 files changed, 107 insertions(+), 85 deletions(-) diff --git a/autobahn/xbr/templates/obj.py.jinja2 b/autobahn/xbr/templates/obj.py.jinja2 index 85efccc22..9229e62f7 100644 --- a/autobahn/xbr/templates/obj.py.jinja2 +++ b/autobahn/xbr/templates/obj.py.jinja2 @@ -31,88 +31,6 @@ class {{ metadata.classname }}(object): {% endfor %} - @classmethod - def GetRootAs{{ metadata.classname }}(cls, buf, offset): - n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) - x = {{ metadata.classname }}() - x.Init(buf, n + offset) - return x - - # HomeDevice - def Init(self, buf, pos): - self._tab = flatbuffers.table.Table(buf, pos) - - 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 eg ``json.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, - {% else %} - raise NotImplementedError('implement processing of FlatBuffers type "{}"'.format({{ metadata.fields[field_name].type.map('python', metadata.fields[field_name].attrs, True) }})) - {% endif %} - {% endfor %} - } - return obj - - @staticmethod - def parse(data: Dict) -> object: - """ - """ - 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') - {% else %} - raise NotImplementedError('implement processing of FlatBuffers type "{}"'.format({{ 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 eg logging. - - :returns: String representation of this object. - """ - return '\n{}\n'.format(pprint.pformat(self.marshal())) - {% 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) }}: @@ -204,15 +122,95 @@ class {{ metadata.classname }}(object): {% endfor %} @staticmethod - def cast(buf: bytes) -> object: + def parse(data: Dict) -> object: + """ + Parse generic, native language object into a typed, native language object. + + :param data: Generic native language object to parse, eg 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') + {% else %} + raise NotImplementedError('implement processing 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 eg ``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, + {% else %} + raise NotImplementedError('implement processing of FlatBuffers type "{}"'.format({{ 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 eg logging. + + :returns: String representation of this object. + """ + return '\n{}\n'.format(pprint.pformat(self.marshal())) + + @staticmethod + def cast(buf: bytes, offset: int=0) -> object: """ 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. """ - return {{ metadata.classname }}.GetRootAs{{ metadata.classname }}(buf, 0) + 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): """ diff --git a/autobahn/xbr/templates/test_obj.py.jinja2 b/autobahn/xbr/templates/test_obj.py.jinja2 index 06fff4077..e9a76b051 100644 --- a/autobahn/xbr/templates/test_obj.py.jinja2 +++ b/autobahn/xbr/templates/test_obj.py.jinja2 @@ -9,17 +9,28 @@ 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 }} @@ -177,9 +188,22 @@ def test_{{ metadata.classname }}_marshal_parse({{ metadata.classname }}_obj, bu def test_{{ metadata.classname }}_marshal_cbor_parse({{ metadata.classname }}_obj, builder): obj = {{ metadata.classname }}_obj.marshal() data = cbor2.dumps(obj) - print('Serialized {} to {} bytes'.format({{ metadata.classname }}, len(data))) + print('serialized {} to {} bytes (cbor)'.format({{ metadata.classname }}, len(data))) _obj_raw = cbor2.loads(data) _obj = {{ metadata.classname }}_obj.parse(_obj_raw) {% for field_name in metadata.fields_by_id %} assert _obj.{{ metadata.fields[field_name].name }} == {{ metadata.classname }}_obj.{{ metadata.fields[field_name].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_name in metadata.fields_by_id %} + assert obj2.{{ metadata.fields[field_name].name }} == {{ metadata.classname }}_obj.{{ metadata.fields[field_name].name }} + {% endfor %} From 26ef363389221ac99c8a4766477bd6e2fa17ded5 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 00:49:54 +0100 Subject: [PATCH 04/11] sync --- autobahn/xbr/_cli.py | 6 +- autobahn/xbr/_schema.py | 148 ++++++++++++++--------- autobahn/xbr/templates/service.py.jinja2 | 28 ++++- 3 files changed, 121 insertions(+), 61 deletions(-) diff --git a/autobahn/xbr/_cli.py b/autobahn/xbr/_cli.py index 555ad9347..aa85f8259 100644 --- a/autobahn/xbr/_cli.py +++ b/autobahn/xbr/_cli.py @@ -1019,14 +1019,14 @@ def _main(): # pprint(item.marshal()) metadata = item - # com.things.home.device.HomeDeviceVendor => com.things.home.device + # com.example.device.HomeDeviceVendor => com.example.device modulename = '.'.join(metadata.name.split('.')[0:-1]) metadata.modulename = modulename - # com.things.home.device.HomeDeviceVendor => HomeDeviceVendor + # com.example.device.HomeDeviceVendor => HomeDeviceVendor metadata.classname = metadata.name.split('.')[-1].strip() - # com.things.home.device => device + # com.example.device => device metadata.module_relimport = modulename.split('.')[-1] is_first = modulename not in code_modules diff --git a/autobahn/xbr/_schema.py b/autobahn/xbr/_schema.py index 2c4ca9ed7..efdc0b3f0 100644 --- a/autobahn/xbr/_schema.py +++ b/autobahn/xbr/_schema.py @@ -354,10 +354,13 @@ def parse_fields(obj): fields_by_id = {} for j in range(obj.FieldsLength()): fbs_field = obj.Fields(j) + field_name = fbs_field.Name() if field_name: field_name = field_name.decode('utf8') + field_id = int(fbs_field.Id()) + fbs_field_type = fbs_field.Type() field_type = FbsType(basetype=fbs_field_type.BaseType(), element=fbs_field_type.Element(), @@ -383,6 +386,79 @@ def parse_fields(obj): return fields, fields_by_id +def parse_calls(svc_obj): + calls = {} + calls_by_id = {} + for j in range(svc_obj.CallsLength()): + fbs_call = svc_obj.Calls(j) + + call_name = fbs_call.Name() + if call_name: + call_name = call_name.decode('utf8') + + # FIXME: schema reflection.RPCCall lacks "Id" (!) + # call_id = int(fbs_call.Id()) + call_id = j + + fbs_call_req = fbs_call.Request() + call_req_name = fbs_call_req.Name() + if call_req_name: + call_req_name = call_req_name.decode('utf8') + call_req_is_struct = fbs_call_req.IsStruct() + call_req_min_align = fbs_call_req.Minalign() + call_req_bytesize = fbs_call_req.Bytesize() + call_req_docs = parse_docs(fbs_call_req) + call_req_attrs = parse_attr(fbs_call_req) + call_req_fields, call_fields_by_id = parse_fields(fbs_call_req) + call_req = FbsObject(name=call_req_name, + fields=call_req_fields, + fields_by_id=call_fields_by_id, + is_struct=call_req_is_struct, + min_align=call_req_min_align, + bytesize=call_req_bytesize, + attrs=call_req_attrs, + docs=call_req_docs) + + fbs_call_resp = fbs_call.Response() + call_resp_name = fbs_call_resp.Name() + if call_resp_name: + call_resp_name = call_resp_name.decode('utf8') + call_resp_is_struct = fbs_call_resp.IsStruct() + call_resp_min_align = fbs_call_resp.Minalign() + call_resp_bytesize = fbs_call_resp.Bytesize() + call_resp_docs = parse_docs(fbs_call_resp) + call_resp_attrs = parse_attr(fbs_call_resp) + call_resp_fields, call_resp_fields_by_id = parse_fields(fbs_call_resp) + call_resp = FbsObject(name=call_resp_name, + fields=call_resp_fields, + fields_by_id=call_resp_fields_by_id, + is_struct=call_resp_is_struct, + min_align=call_resp_min_align, + bytesize=call_resp_bytesize, + attrs=call_resp_attrs, + docs=call_resp_docs) + + call_docs = parse_docs(fbs_call) + call_attrs = parse_attr(fbs_call) + call = FbsRPCCall(name=call_name, + id=call_id, + request=call_req, + response=call_resp, + docs=call_docs, + attrs=call_attrs) + + assert call_name not in calls, 'call "{}" with id "{}" already in calls {}'.format(call_name, call_id, sorted(calls.keys())) + calls[call_name] = call + assert call_id not in calls_by_id, 'call "{}" with id " {}" already in calls {}'.format(call_name, call_id, sorted(calls.keys())) + calls_by_id[call_id] = call_name + + res = [] + for _, value in sorted(calls_by_id.items()): + res.append(value) + calls_by_id = res + return calls, calls_by_id + + class FbsObject(object): def __init__(self, name: str, @@ -477,11 +553,13 @@ def parse(fbs_obj): class FbsRPCCall(object): def __init__(self, name: str, + id: int, request: FbsObject, response: FbsObject, docs: str, attrs: Dict[str, FbsAttribute]): self._name = name + self._id = id self._request = request self._response = response self._docs = docs @@ -491,6 +569,10 @@ def __init__(self, def name(self): return self._name + @property + def id(self): + return self._id + @property def request(self): return self._request @@ -528,10 +610,12 @@ class FbsService(object): def __init__(self, name: str, calls: Dict[str, FbsRPCCall], + calls_by_id: Dict[int, str], attrs: Dict[str, FbsAttribute], docs: str): self._name = name self._calls = calls + self._calls_by_id = calls_by_id self._attrs = attrs self._docs = docs @@ -543,6 +627,10 @@ def name(self): def calls(self): return self._calls + @property + def calls_by_id(self): + return self._calls_by_id + @property def attrs(self): return self._attrs @@ -861,65 +949,11 @@ def load(filename) -> object: if svc_name: svc_name = svc_name.decode('utf8') - calls = {} - for j in range(svc_obj.CallsLength()): - fbs_call = svc_obj.Calls(j) - - call_name = fbs_call.Name() - if call_name: - call_name = call_name.decode('utf8') - - fbs_call_req = fbs_call.Request() - call_req_name = fbs_call_req.Name() - if call_req_name: - call_req_name = call_req_name.decode('utf8') - call_req_is_struct = fbs_call_req.IsStruct() - call_req_min_align = fbs_call_req.Minalign() - call_req_bytesize = fbs_call_req.Bytesize() - call_req_docs = parse_docs(fbs_call_req) - call_req_attrs = parse_attr(fbs_call_req) - call_req_fields, call_fields_by_id = parse_fields(fbs_call_req) - call_req = FbsObject(name=call_req_name, - fields=call_req_fields, - fields_by_id=call_fields_by_id, - is_struct=call_req_is_struct, - min_align=call_req_min_align, - bytesize=call_req_bytesize, - attrs=call_req_attrs, - docs=call_req_docs) - - fbs_call_resp = fbs_call.Response() - call_resp_name = fbs_call_resp.Name() - if call_resp_name: - call_resp_name = call_resp_name.decode('utf8') - call_resp_is_struct = fbs_call_resp.IsStruct() - call_resp_min_align = fbs_call_resp.Minalign() - call_resp_bytesize = fbs_call_resp.Bytesize() - call_resp_docs = parse_docs(fbs_call_resp) - call_resp_attrs = parse_attr(fbs_call_resp) - call_resp_fields, call_resp_fields_by_id = parse_fields(fbs_call_resp) - call_resp = FbsObject(name=call_resp_name, - fields=call_resp_fields, - fields_by_id=call_resp_fields_by_id, - is_struct=call_resp_is_struct, - min_align=call_resp_min_align, - bytesize=call_resp_bytesize, - attrs=call_resp_attrs, - docs=call_resp_docs) - - call_docs = parse_docs(fbs_call) - call_attrs = parse_attr(fbs_call) - call = FbsRPCCall(name=call_name, - request=call_req, - response=call_resp, - docs=call_docs, - attrs=call_attrs) - assert call_name not in calls - calls[call_name] = call - docs = parse_docs(svc_obj) attrs = parse_attr(svc_obj) - service = FbsService(name=svc_name, calls=calls, attrs=attrs, docs=docs) + calls, calls_by_id = parse_calls(svc_obj) + + service = FbsService(name=svc_name, calls=calls, calls_by_id=calls_by_id, attrs=attrs, docs=docs) assert svc_name not in services services[svc_name] = service diff --git a/autobahn/xbr/templates/service.py.jinja2 b/autobahn/xbr/templates/service.py.jinja2 index b70889608..699dbac0e 100644 --- a/autobahn/xbr/templates/service.py.jinja2 +++ b/autobahn/xbr/templates/service.py.jinja2 @@ -1,6 +1,32 @@ {% if is_first_by_category %} ## -## serivices types (aka "APIs") +## service types (aka "APIs") ## +import abc +from autobahn.wamp.interfaces import ISession {% endif %} +class {{ metadata.classname }}(abc.ABC): + """ + {{ metadata.docs }} + + @interface: "{{ metadata.attrs.uuid }}" + """ + __slots__ = [{% for call_name in metadata.calls_by_id %}'{{ metadata.calls[call_name].name }}', {% endfor %}] + + {% for call_name in metadata.calls_by_id %} + {% if metadata.calls[call_name].attrs['type'] == 'topic' %} + @abc.abstractmethod + def publish_{{ call_name }}(self, session: ISession, device: HomeDevice): + """ + Publish: {{ metadata.calls[call_name].docs }} + """ + + @abc.abstractmethod + def receive_{{ call_name }}(self, session: ISession, device: HomeDevice): + """ + Receive: {{ metadata.calls[call_name].docs }} + """ + + {% endif %} + {% endfor %} From cd1081f17af9ebb017c4596909041cb39c4be44e Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 02:05:52 +0100 Subject: [PATCH 05/11] sync --- autobahn/xbr/__init__.py | 2 +- autobahn/xbr/_interfaces.py | 6 +++ autobahn/xbr/templates/service.py.jinja2 | 55 +++++++++++++++++++++--- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/autobahn/xbr/__init__.py b/autobahn/xbr/__init__.py index 17606c047..fe9e4479f 100644 --- a/autobahn/xbr/__init__.py +++ b/autobahn/xbr/__init__.py @@ -36,7 +36,7 @@ from autobahn.xbr._abi import XBR_TOKEN_ABI, XBR_NETWORK_ABI, XBR_MARKET_ABI, XBR_CATALOG_ABI, XBR_CHANNEL_ABI # noqa from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR, XBR_DEBUG_NETWORK_ADDR, XBR_DEBUG_MARKET_ADDR, XBR_DEBUG_CATALOG_ADDR, XBR_DEBUG_CHANNEL_ADDR # noqa from autobahn.xbr._abi import XBR_DEBUG_TOKEN_ADDR_SRC, XBR_DEBUG_NETWORK_ADDR_SRC, XBR_DEBUG_MARKET_ADDR_SRC, XBR_DEBUG_CATALOG_ADDR_SRC, XBR_DEBUG_CHANNEL_ADDR_SRC # noqa - from autobahn.xbr._interfaces import IMarketMaker, IProvider, IConsumer, ISeller, IBuyer # noqa + from autobahn.xbr._interfaces import IMarketMaker, IProvider, IConsumer, ISeller, IBuyer, IDelegate # noqa from autobahn.xbr._util import make_w3, pack_uint256, unpack_uint256, with_0x, without_0x # noqa from autobahn.xbr._eip712_member_register import sign_eip712_member_register, recover_eip712_member_register # noqa diff --git a/autobahn/xbr/_interfaces.py b/autobahn/xbr/_interfaces.py index f49d40958..7bec28acf 100644 --- a/autobahn/xbr/_interfaces.py +++ b/autobahn/xbr/_interfaces.py @@ -180,3 +180,9 @@ async def unwrap(self, key_id, enc_ser, ciphertext): :returns: The unwrapped application payload. :rtype: object """ + + +class IDelegate(ISeller, IBuyer): + """ + XBR Delegate interface. + """ diff --git a/autobahn/xbr/templates/service.py.jinja2 b/autobahn/xbr/templates/service.py.jinja2 index 699dbac0e..afe98ce29 100644 --- a/autobahn/xbr/templates/service.py.jinja2 +++ b/autobahn/xbr/templates/service.py.jinja2 @@ -3,27 +3,70 @@ ## service types (aka "APIs") ## import abc +from autobahn.wamp.types import PublishOptions +from autobahn.wamp.request import Publication from autobahn.wamp.interfaces import ISession +from autobahn.xbr import IDelegate + {% endif %} -class {{ metadata.classname }}(abc.ABC): +class {{ metadata.classname }}(object): """ {{ metadata.docs }} @interface: "{{ metadata.attrs.uuid }}" """ - __slots__ = [{% for call_name in metadata.calls_by_id %}'{{ metadata.calls[call_name].name }}', {% endfor %}] + __slots__ = ['_x_api_id', '_x_session', '_x_delegate', {% for call_name in metadata.calls_by_id %}'{{ metadata.calls[call_name].name }}', {% endfor %}] + + def __init__(self): + self._x_api_id = uuid.UUID('{{ metadata.attrs.uuid }}').bytes + self._x_session = None + self._x_delegate = None + + def session(self) -> Optional[ISession]: + return self._x_session + + def delegate(self) -> Optional[IDelegate]: + return self._x_delegate + + def prefix(self) -> Optional[str]: + return self._x_prefix + + async def attach(self, session: ISession, delegate: IDelegate, prefix: str): + assert self._x_session is None + assert self._x_seller is None + assert self._x_prefix is None + self._x_session = session + self._x_delegate = delegate + self._x_prefix = prefix + + async def detach(self): + assert self._x_session is not None + assert self._x_delegate is not None + assert self._x_prefix is not None + if self._x_session.is_attached(): + await self._x_session.leave() + self._x_session = None + self._x_delegate = None + self._x_prefix = None {% for call_name in metadata.calls_by_id %} {% if metadata.calls[call_name].attrs['type'] == 'topic' %} - @abc.abstractmethod - def publish_{{ call_name }}(self, session: ISession, device: HomeDevice): + async def publish_{{ call_name }}(self, device: HomeDevice) -> Publication: """ Publish: {{ metadata.calls[call_name].docs }} """ + assert self._x_session is not None and self._x_session.is_attached() + assert self._x_seller is not None + + topic = '{}.{{ call_name }}'.format(self._x_prefix) + payload = device.marshal() + key_id, enc_ser, ciphertext = await self._x_delegate.wrap(self._x_api_id, topic, payload) + pub = await self._x_session.publish(topic, key_id, enc_ser, ciphertext, + options=PublishOptions(acknowledge=True)) + return pub - @abc.abstractmethod - def receive_{{ call_name }}(self, session: ISession, device: HomeDevice): + def receive_{{ call_name }}(self, device: HomeDevice): """ Receive: {{ metadata.calls[call_name].docs }} """ From 46a67396e29878d03746bcd4f4b5eb6a74c829e1 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 17:48:36 +0100 Subject: [PATCH 06/11] sync --- README.rst | 2 +- autobahn/xbr/templates/service.py.jinja2 | 74 ++++++++++++++++++++++-- docs/changelog.rst | 4 ++ setup.py | 8 +-- tox.ini | 4 +- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/README.rst b/README.rst index d25e961a9..8d944e6a4 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ implementations of - `The WebSocket Protocol `__ - `The Web Application Messaging Protocol (WAMP) `__ -for Python 3.5+ and running on `Twisted `__ and `asyncio `__. +for Python 3.6+ and running on `Twisted `__ and `asyncio `__. You can use **Autobahn\|Python** to create clients and servers in Python speaking just plain WebSocket or WAMP. diff --git a/autobahn/xbr/templates/service.py.jinja2 b/autobahn/xbr/templates/service.py.jinja2 index afe98ce29..16614d676 100644 --- a/autobahn/xbr/templates/service.py.jinja2 +++ b/autobahn/xbr/templates/service.py.jinja2 @@ -3,7 +3,7 @@ ## service types (aka "APIs") ## import abc -from autobahn.wamp.types import PublishOptions +from autobahn.wamp.types import PublishOptions, SubscribeOptions from autobahn.wamp.request import Publication from autobahn.wamp.interfaces import ISession from autobahn.xbr import IDelegate @@ -16,31 +16,94 @@ class {{ metadata.classname }}(object): @interface: "{{ metadata.attrs.uuid }}" """ - __slots__ = ['_x_api_id', '_x_session', '_x_delegate', {% for call_name in metadata.calls_by_id %}'{{ metadata.calls[call_name].name }}', {% endfor %}] + __slots__ = [ + 'log', + '_x_api_id', + '_x_session', + '_x_delegate', + ] - def __init__(self): + def __init__(self, log=None): + """ + + :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 }}').bytes self._x_session = None self._x_delegate = None def session(self) -> Optional[ISession]: + """ + WAMP session this API is attached to. + """ return self._x_session def delegate(self) -> Optional[IDelegate]: + """ + XBR (buyer/seller) delegate this API is attached to. + """ return self._x_delegate def prefix(self) -> Optional[str]: + """ + WAMP URI prefix under which this API is instantiated. + """ return self._x_prefix + def is_attched(self) -> bool: + """ + Flag indicating whether this API instance is currently attached to a session/delegate. + """ + if self._x_session is None and self._x_seller is None and self._x_prefix is None: + return False + elif self._x_session is not None and self._x_seller is not None and self._x_prefix is not None: + return True + else: + assert False, 'logic error - should not arrive here' + async def attach(self, session: ISession, delegate: IDelegate, prefix: str): + """ + 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: XBR (buyer/seller) delegate used by this API instance. + :param prefix: WAMP URI prefix under which this API instance is attached to. + """ assert self._x_session is None assert self._x_seller is None assert self._x_prefix is None + self._x_session = session self._x_delegate = delegate self._x_prefix = prefix + {% for call_name in metadata.calls_by_id %} + {% if metadata.calls[call_name].attrs['type'] == 'topic' %} + + async def do_receive_{{ call_name }}(key_id, enc_ser, ciphertext, details=None): + print('Received event {}, encrypted with key_id={}'.format(details.publication, uuid.UUID(bytes=key_id))) + try: + payload = await self._x_delegate.unwrap(key_id, enc_ser, ciphertext) + obj = HomeDevice.parse(payload) + except: + self.log.failure() + else: + print('Unencrypted event payload: {}'.format(pformat(obj))) + self.receive_{{ call_name }}(obj) + + topic = '{}.{{ call_name }}'.format(self._x_prefix) + await self.subscribe(do_receive_{{ call_name }}, topic, options=SubscribeOptions(details=True)) + {% endif %} + {% endfor %} async def detach(self): + """ + Detach this API instance from the session and delegate. + """ assert self._x_session is not None assert self._x_delegate is not None assert self._x_prefix is not None @@ -54,7 +117,7 @@ class {{ metadata.classname }}(object): {% if metadata.calls[call_name].attrs['type'] == 'topic' %} async def publish_{{ call_name }}(self, device: HomeDevice) -> Publication: """ - Publish: {{ metadata.calls[call_name].docs }} + Publish - {{ metadata.calls[call_name].docs }} """ assert self._x_session is not None and self._x_session.is_attached() assert self._x_seller is not None @@ -68,8 +131,9 @@ class {{ metadata.classname }}(object): def receive_{{ call_name }}(self, device: HomeDevice): """ - Receive: {{ metadata.calls[call_name].docs }} + Receive - {{ metadata.calls[call_name].docs }} """ + raise NotImplementedError('event handler for "{{ call_name }}" not implemented') {% endif %} {% endfor %} diff --git a/docs/changelog.rst b/docs/changelog.rst index 1d7a40e0b..d0f172990 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,10 @@ Changelog ------- * new: CLI commands for WAMP IDL (`xbrnetwork describe-schema / codegen-schema`) +* new: add eth address helpers (#1413) +* new: cryptosign authextra allow arbitrary keys (#1411) +* fix: adapt to planet api prefix change (#1408) +* fix: Type check improve (#1405) 20.7.1 ------ diff --git a/setup.py b/setup.py index 5bfa07e79..7158447ca 100644 --- a/setup.py +++ b/setup.py @@ -254,8 +254,8 @@ def run_tests(self): url='http://crossbar.io/autobahn', platforms='Any', install_requires=[ - 'txaio>=20.3.1', # MIT license - 'cryptography>=2.7', # BSD *or* Apache license + 'txaio>=20.4.1', # MIT license + 'cryptography>=2.9.2', # BSD *or* Apache license ], extras_require={ 'all': extras_require_all, @@ -285,7 +285,7 @@ def run_tests(self): zip_safe=False, - python_requires='>=3.5', + python_requires='>=3.6', # http://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=["License :: OSI Approved :: MIT License", @@ -296,10 +296,10 @@ def run_tests(self): "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Programming Language :: Python :: Implementation :: Jython", diff --git a/tox.ini b/tox.ini index fd27a8243..2344c016b 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = coverage # CPython - py35-{tw189,tw1910,twtrunk,asyncio} + py36-{tw189,tw1910,twtrunk,asyncio} py37-{tw189,tw1910,twtrunk,asyncio} py38-{tw189,tw1910,twtrunk,asyncio} py39-{tw189,tw1910,twtrunk,asyncio} @@ -30,7 +30,7 @@ deps = {tw189,tw1910,twtrunk}: pytest-twisted ; asyncio dependencies - py35-asyncio: pytest_asyncio + py36-asyncio: pytest_asyncio py37-asyncio: pytest_asyncio py38-asyncio: pytest_asyncio pypy3-asyncio: pytest_asyncio From 2ac64055fbed0d15748261809ce00657b31cce08 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 18:11:53 +0100 Subject: [PATCH 07/11] fix ci --- .travis.yml | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b6ef5b68..f74f05a87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,66 +44,66 @@ matrix: - TOX_ENV=flake8 # - # CPython 3.5 + # CPython 3.6 # - - python: "3.5" + - python: "3.6" env: - - TOX_ENV=py35-tw189 + - TOX_ENV=py36-tw189 - - python: "3.5" + - python: "3.6" env: - - TOX_ENV=py35-tw1910 + - TOX_ENV=py36-tw1910 - - python: "3.5" + - python: "3.6" env: - - TOX_ENV=py35-twtrunk + - TOX_ENV=py36-twtrunk - - python: "3.5" + - python: "3.6" env: - - TOX_ENV=py35-asyncio + - TOX_ENV=py36-asyncio # - # CPython 3.8 + # CPython 3.9 # - - python: "3.8" + - python: "3.9" env: - - TOX_ENV=py38-tw189 + - TOX_ENV=py39-tw189 - - python: "3.8" + - python: "3.9" env: - - TOX_ENV=py38-tw1910 + - TOX_ENV=py39-tw1910 - - python: "3.8" + - python: "3.9" env: - - TOX_ENV=py38-twtrunk + - TOX_ENV=py39-twtrunk - - python: "3.8" + - python: "3.9" env: - - TOX_ENV=py38-asyncio + - TOX_ENV=py39-asyncio # # PyPy3 # - - python: "pypy3.5" + - python: "pypy3.7" env: - TOX_ENV=pypy3-tw189 - - python: "pypy3.5" + - python: "pypy3.7" env: - TOX_ENV=pypy3-tw1910 - - python: "pypy3.5" + - python: "pypy3.7" env: - TOX_ENV=pypy3-twtrunk - - python: "pypy3.5" + - python: "pypy3.7" env: - TOX_ENV=pypy3-asyncio # - # Coverage (run under CPython 3.5) + # Coverage (run under CPython 3.8) # - - python: "3.5" + - python: "3.8" skip_cleanup: true env: TOX_ENV=coverage @@ -125,7 +125,7 @@ matrix: allow_failures: - env: TOX_ENV=coverage - os: osx - - python: "pypy3.5" + - python: "pypy3.7" # CI notifications notifications: From 65d3f1b21ecf11dce06b01af2ffc93eda7a2ff65 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 18:35:28 +0100 Subject: [PATCH 08/11] fix ci --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index f74f05a87..10018240e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -44,23 +44,23 @@ matrix: - TOX_ENV=flake8 # - # CPython 3.6 + # CPython 3.7 # - - python: "3.6" + - python: "3.7" env: - - TOX_ENV=py36-tw189 + - TOX_ENV=py37-tw189 - - python: "3.6" + - python: "3.7" env: - - TOX_ENV=py36-tw1910 + - TOX_ENV=py37-tw1910 - - python: "3.6" + - python: "3.7" env: - - TOX_ENV=py36-twtrunk + - TOX_ENV=py37-twtrunk - - python: "3.6" + - python: "3.7" env: - - TOX_ENV=py36-asyncio + - TOX_ENV=py37-asyncio # # CPython 3.9 From c0ef97beb79d3273485e1425adf0f22a67ab2430 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 19:25:47 +0100 Subject: [PATCH 09/11] fix ci --- .travis.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 10018240e..b8110473c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python -dist: xenial +dist: bionic sudo: true addons: @@ -84,19 +84,19 @@ matrix: # # PyPy3 # - - python: "pypy3.7" + - python: "pypy3.6-7.3.1" env: - TOX_ENV=pypy3-tw189 - - python: "pypy3.7" + - python: "pypy3.6-7.3.1" env: - TOX_ENV=pypy3-tw1910 - - python: "pypy3.7" + - python: "pypy3.6-7.3.1" env: - TOX_ENV=pypy3-twtrunk - - python: "pypy3.7" + - python: "pypy3.6-7.3.1" env: - TOX_ENV=pypy3-asyncio @@ -125,7 +125,7 @@ matrix: allow_failures: - env: TOX_ENV=coverage - os: osx - - python: "pypy3.7" + - python: "pypy3.6" # CI notifications notifications: From 4bf4eb2982286498724d48c000429f5dc182de34 Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 20:01:46 +0100 Subject: [PATCH 10/11] fix ci --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b8110473c..ee517a5e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,9 +123,8 @@ matrix: script: pwd && ls -la && sh .travis-deploy.sh allow_failures: - - env: TOX_ENV=coverage - - os: osx - - python: "pypy3.6" + - env: TOX_ENV=py39-asyncio TOX_ENV=coverage + # CI notifications notifications: From b19f1aca05c0288251002a380028094f6d58fcad Mon Sep 17 00:00:00 2001 From: Tobias Oberstein Date: Sun, 13 Dec 2020 20:03:11 +0100 Subject: [PATCH 11/11] fix ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ee517a5e2..ae7c1cde6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,7 +123,7 @@ matrix: script: pwd && ls -la && sh .travis-deploy.sh allow_failures: - - env: TOX_ENV=py39-asyncio TOX_ENV=coverage + - env: TOX_ENV=py39-asyncio # CI notifications