mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-30 03:03:30 +08:00
Protocol no longer needs to be passed when instantiating message senders/receivers
This commit is contained in:
parent
43788993e6
commit
91a40f1062
1
.idea/dictionaries/ericf.xml
generated
1
.idea/dictionaries/ericf.xml
generated
@ -310,6 +310,7 @@
|
||||
<w>cbtn</w>
|
||||
<w>cbtnoffs</w>
|
||||
<w>ccfgs</w>
|
||||
<w>ccind</w>
|
||||
<w>ccode</w>
|
||||
<w>ccompiler</w>
|
||||
<w>cdrk</w>
|
||||
|
||||
@ -159,6 +159,7 @@
|
||||
<w>cbgn</w>
|
||||
<w>cbtnoffs</w>
|
||||
<w>ccdd</w>
|
||||
<w>ccind</w>
|
||||
<w>ccontext</w>
|
||||
<w>ccylinder</w>
|
||||
<w>cend</w>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2021-09-22 for Ballistica version 1.6.5 build 20393</em></h4>
|
||||
<h4><em>last updated on 2021-09-23 for Ballistica version 1.6.5 build 20393</em></h4>
|
||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||
<hr>
|
||||
|
||||
@ -78,6 +78,10 @@ class _TResp3(Message):
|
||||
class _TestMessageSender(MessageSender):
|
||||
"""Protocol-specific sender."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
protocol = TEST_PROTOCOL
|
||||
super().__init__(protocol)
|
||||
|
||||
def __get__(self,
|
||||
obj: Any,
|
||||
type_in: Any = None) -> _BoundTestMessageSender:
|
||||
@ -129,6 +133,10 @@ class _TestSyncMessageReceiver(MessageReceiver):
|
||||
|
||||
is_async = False
|
||||
|
||||
def __init__(self) -> None:
|
||||
protocol = TEST_PROTOCOL
|
||||
super().__init__(protocol)
|
||||
|
||||
def __get__(
|
||||
self,
|
||||
obj: Any,
|
||||
@ -180,6 +188,10 @@ class _TestAsyncMessageReceiver(MessageReceiver):
|
||||
|
||||
is_async = True
|
||||
|
||||
def __init__(self) -> None:
|
||||
protocol = TEST_PROTOCOL
|
||||
super().__init__(protocol)
|
||||
|
||||
def __get__(
|
||||
self,
|
||||
obj: Any,
|
||||
@ -256,8 +268,15 @@ def test_protocol_creation() -> None:
|
||||
|
||||
def test_sender_module_embedded() -> None:
|
||||
"""Test generation of protocol-specific sender modules for typing/etc."""
|
||||
smod = TEST_PROTOCOL.create_sender_module('TestMessageSender',
|
||||
private=True)
|
||||
# NOTE: Ideally we should be testing efro.message.create_sender_module()
|
||||
# here, but it requires us to pass code which imports this test module
|
||||
# to get at the protocol, and that currently fails in our static mypy
|
||||
# tests.
|
||||
smod = TEST_PROTOCOL.do_create_sender_module(
|
||||
'TestMessageSender',
|
||||
'protocol = TEST_PROTOCOL',
|
||||
private=True,
|
||||
)
|
||||
|
||||
# Clip everything up to our first class declaration.
|
||||
lines = smod.splitlines()
|
||||
@ -279,9 +298,16 @@ def test_sender_module_embedded() -> None:
|
||||
|
||||
def test_receiver_module_sync_embedded() -> None:
|
||||
"""Test generation of protocol-specific sender modules for typing/etc."""
|
||||
smod = TEST_PROTOCOL.create_receiver_module('TestSyncMessageReceiver',
|
||||
is_async=False,
|
||||
private=True)
|
||||
# NOTE: Ideally we should be testing efro.message.create_receiver_module()
|
||||
# here, but it requires us to pass code which imports this test module
|
||||
# to get at the protocol, and that currently fails in our static mypy
|
||||
# tests.
|
||||
smod = TEST_PROTOCOL.do_create_receiver_module(
|
||||
'TestSyncMessageReceiver',
|
||||
'protocol = TEST_PROTOCOL',
|
||||
is_async=False,
|
||||
private=True,
|
||||
)
|
||||
|
||||
# Clip everything up to our first class declaration.
|
||||
lines = smod.splitlines()
|
||||
@ -304,9 +330,16 @@ def test_receiver_module_sync_embedded() -> None:
|
||||
|
||||
def test_receiver_module_async_embedded() -> None:
|
||||
"""Test generation of protocol-specific sender modules for typing/etc."""
|
||||
smod = TEST_PROTOCOL.create_receiver_module('TestAsyncMessageReceiver',
|
||||
is_async=True,
|
||||
private=True)
|
||||
# NOTE: Ideally we should be testing efro.message.create_receiver_module()
|
||||
# here, but it requires us to pass code which imports this test module
|
||||
# to get at the protocol, and that currently fails in our static mypy
|
||||
# tests.
|
||||
smod = TEST_PROTOCOL.do_create_receiver_module(
|
||||
'TestAsyncMessageReceiver',
|
||||
'protocol = TEST_PROTOCOL',
|
||||
is_async=True,
|
||||
private=True,
|
||||
)
|
||||
|
||||
# Clip everything up to our first class declaration.
|
||||
lines = smod.splitlines()
|
||||
@ -339,7 +372,7 @@ def test_receiver_creation() -> None:
|
||||
class _TestClassR:
|
||||
"""Test class incorporating receive functionality."""
|
||||
|
||||
receiver = _TestSyncMessageReceiver(TEST_PROTOCOL)
|
||||
receiver = _TestSyncMessageReceiver()
|
||||
|
||||
@receiver.handler
|
||||
def handle_test_message_2(self, msg: _TMsg2) -> _TResp2:
|
||||
@ -354,7 +387,7 @@ def test_receiver_creation() -> None:
|
||||
class _TestClassR2:
|
||||
"""Test class incorporating receive functionality."""
|
||||
|
||||
receiver = _TestSyncMessageReceiver(TEST_PROTOCOL)
|
||||
receiver = _TestSyncMessageReceiver()
|
||||
|
||||
# Checks that we've added handlers for all message types, etc.
|
||||
receiver.validate()
|
||||
@ -367,7 +400,7 @@ def test_full_pipeline() -> None:
|
||||
class TestClassS:
|
||||
"""Test class incorporating send functionality."""
|
||||
|
||||
msg = _TestMessageSender(TEST_PROTOCOL)
|
||||
msg = _TestMessageSender()
|
||||
|
||||
def __init__(self, target: Union[TestClassRSync,
|
||||
TestClassRAsync]) -> None:
|
||||
@ -393,7 +426,7 @@ def test_full_pipeline() -> None:
|
||||
class TestClassRSync:
|
||||
"""Test class incorporating synchronous receive functionality."""
|
||||
|
||||
receiver = _TestSyncMessageReceiver(TEST_PROTOCOL)
|
||||
receiver = _TestSyncMessageReceiver()
|
||||
|
||||
@receiver.handler
|
||||
def handle_test_message_1(self, msg: _TMsg1) -> _TResp1:
|
||||
@ -421,7 +454,7 @@ def test_full_pipeline() -> None:
|
||||
class TestClassRAsync:
|
||||
"""Test class incorporating asynchronous receive functionality."""
|
||||
|
||||
receiver = _TestAsyncMessageReceiver(TEST_PROTOCOL)
|
||||
receiver = _TestAsyncMessageReceiver()
|
||||
|
||||
@receiver.handler
|
||||
async def handle_test_message_1(self, msg: _TMsg1) -> _TResp1:
|
||||
|
||||
@ -73,6 +73,16 @@ class EmptyResponse(Response):
|
||||
"""The response equivalent of None."""
|
||||
|
||||
|
||||
# TODO: could allow handlers to deal in raw values for these
|
||||
# types similar to how we allow None in place of EmptyResponse.
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BoolResponse(Response):
|
||||
"""A simple bool value response."""
|
||||
|
||||
value: Annotated[bool, IOAttrs('v')]
|
||||
|
||||
|
||||
class MessageProtocol:
|
||||
"""Wrangles a set of message types, formats, and response types.
|
||||
Both endpoints must be using a compatible Protocol for communication
|
||||
@ -144,6 +154,7 @@ class MessageProtocol:
|
||||
|
||||
_reg_if_not(ErrorResponse, -1)
|
||||
_reg_if_not(EmptyResponse, -2)
|
||||
_reg_if_not(BoolResponse, -3)
|
||||
|
||||
# Some extra-thorough validation in debug mode.
|
||||
if __debug__:
|
||||
@ -303,31 +314,24 @@ class MessageProtocol:
|
||||
f'\n')
|
||||
return out
|
||||
|
||||
def create_sender_module(self,
|
||||
basename: str,
|
||||
private: bool = False) -> str:
|
||||
""""Create a Python module defining a MessageSender subclass.
|
||||
|
||||
This class is primarily for type checking and will contain overrides
|
||||
for the varieties of send calls for message/response types defined
|
||||
in the protocol.
|
||||
|
||||
Class names are based on basename; a basename 'FooSender' will
|
||||
result in classes FooSender and BoundFooSender.
|
||||
|
||||
If 'private' is True, class-names will be prefixed with an '_'.
|
||||
|
||||
Note that line lengths are not clipped, so output may need to be
|
||||
run through a formatter to prevent lint warnings about excessive
|
||||
line lengths.
|
||||
"""
|
||||
def do_create_sender_module(self,
|
||||
basename: str,
|
||||
protocol_create_code: str,
|
||||
private: bool = False) -> str:
|
||||
"""Used by create_sender_module(); do not call directly."""
|
||||
# pylint: disable=too-many-locals
|
||||
import textwrap
|
||||
|
||||
ppre = '_' if private else ''
|
||||
out = self._get_module_header('sender')
|
||||
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||
out += (f'class {ppre}{basename}(MessageSender):\n'
|
||||
f' """Protocol-specific sender."""\n'
|
||||
f'\n'
|
||||
f' def __init__(self) -> None:\n'
|
||||
f'{ccind}\n'
|
||||
f' super().__init__(protocol)\n'
|
||||
f'\n'
|
||||
f' def __get__(self,\n'
|
||||
f' obj: Any,\n'
|
||||
f' type_in: Any = None)'
|
||||
@ -384,37 +388,28 @@ class MessageProtocol:
|
||||
|
||||
return out
|
||||
|
||||
def create_receiver_module(self,
|
||||
basename: str,
|
||||
is_async: bool,
|
||||
private: bool = False) -> str:
|
||||
""""Create a Python module defining a MessageReceiver subclass.
|
||||
|
||||
This class is primarily for type checking and will contain overrides
|
||||
for the register method for message/response types defined in
|
||||
the protocol.
|
||||
|
||||
Class names are based on basename; a basename 'FooReceiver' will
|
||||
result in FooReceiver and BoundFooReceiver.
|
||||
|
||||
If 'is_async' is True, handle_raw_message() will be an async method
|
||||
and the @handler decorator will expect async methods.
|
||||
|
||||
If 'private' is True, class-names will be prefixed with an '_'.
|
||||
|
||||
Note that line lengths are not clipped, so output may need to be
|
||||
run through a formatter to prevent lint warnings about excessive
|
||||
line lengths.
|
||||
"""
|
||||
def do_create_receiver_module(self,
|
||||
basename: str,
|
||||
protocol_create_code: str,
|
||||
is_async: bool,
|
||||
private: bool = False) -> str:
|
||||
"""Used by create_receiver_module(); do not call directly."""
|
||||
# pylint: disable=too-many-locals
|
||||
import textwrap
|
||||
|
||||
desc = 'asynchronous' if is_async else 'synchronous'
|
||||
ppre = '_' if private else ''
|
||||
out = self._get_module_header('receiver')
|
||||
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||
out += (f'class {ppre}{basename}(MessageReceiver):\n'
|
||||
f' """Protocol-specific {desc} receiver."""\n'
|
||||
f'\n'
|
||||
f' is_async = {is_async}\n'
|
||||
f'\n'
|
||||
f' def __init__(self) -> None:\n'
|
||||
f'{ccind}\n'
|
||||
f' super().__init__(protocol)\n'
|
||||
f'\n'
|
||||
f' def __get__(\n'
|
||||
f' self,\n'
|
||||
f' obj: Any,\n'
|
||||
@ -773,8 +768,8 @@ class MessageReceiver:
|
||||
handler = self._handlers.get(msgtype)
|
||||
if handler is None:
|
||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||
response = handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(response, msgtype)
|
||||
result = handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(result, msgtype)
|
||||
|
||||
except Exception as exc:
|
||||
return self.raw_response_for_error(exc)
|
||||
@ -790,8 +785,8 @@ class MessageReceiver:
|
||||
handler = self._handlers.get(msgtype)
|
||||
if handler is None:
|
||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||
response = await handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(response, msgtype)
|
||||
result = await handler(bound_obj, msg_decoded)
|
||||
return self._encode_response(result, msgtype)
|
||||
|
||||
except Exception as exc:
|
||||
return self.raw_response_for_error(exc)
|
||||
@ -824,3 +819,84 @@ class BoundMessageReceiver:
|
||||
related error.
|
||||
"""
|
||||
return self._receiver.raw_response_for_error(exc)
|
||||
|
||||
|
||||
def create_sender_module(basename: str,
|
||||
protocol_create_code: str,
|
||||
private: bool = False) -> str:
|
||||
"""Create a Python module defining a MessageSender subclass.
|
||||
|
||||
This class is primarily for type checking and will contain overrides
|
||||
for the varieties of send calls for message/response types defined
|
||||
in the protocol.
|
||||
|
||||
Code passed for 'protocol_create_code' should import necessary
|
||||
modules and assign an instance of the Protocol to a 'protocol'
|
||||
variable.
|
||||
|
||||
Class names are based on basename; a basename 'FooSender' will
|
||||
result in classes FooSender and BoundFooSender.
|
||||
|
||||
If 'private' is True, class-names will be prefixed with an '_'.
|
||||
|
||||
Note that line lengths are not clipped, so output may need to be
|
||||
run through a formatter to prevent lint warnings about excessive
|
||||
line lengths.
|
||||
"""
|
||||
|
||||
# Exec the passed code to get a protocol which we then use to
|
||||
# generate module code. The user could simply call
|
||||
# MessageProtocol.do_create_sender_module() directly, but this allows
|
||||
# us to verify that the create code works and yields the protocol used
|
||||
# to generate the code.
|
||||
protocol = _protocol_from_code(protocol_create_code)
|
||||
return protocol.do_create_sender_module(
|
||||
basename=basename,
|
||||
protocol_create_code=protocol_create_code,
|
||||
private=private)
|
||||
|
||||
|
||||
def create_receiver_module(basename: str,
|
||||
protocol_create_code: str,
|
||||
is_async: bool,
|
||||
private: bool = False) -> str:
|
||||
""""Create a Python module defining a MessageReceiver subclass.
|
||||
|
||||
This class is primarily for type checking and will contain overrides
|
||||
for the register method for message/response types defined in
|
||||
the protocol.
|
||||
|
||||
Class names are based on basename; a basename 'FooReceiver' will
|
||||
result in FooReceiver and BoundFooReceiver.
|
||||
|
||||
If 'is_async' is True, handle_raw_message() will be an async method
|
||||
and the @handler decorator will expect async methods.
|
||||
|
||||
If 'private' is True, class-names will be prefixed with an '_'.
|
||||
|
||||
Note that line lengths are not clipped, so output may need to be
|
||||
run through a formatter to prevent lint warnings about excessive
|
||||
line lengths.
|
||||
"""
|
||||
# Exec the passed code to get a protocol which we then use to
|
||||
# generate module code. The user could simply call
|
||||
# MessageProtocol.do_create_sender_module() directly, but this allows
|
||||
# us to verify that the create code works and yields the protocol used
|
||||
# to generate the code.
|
||||
protocol = _protocol_from_code(protocol_create_code)
|
||||
return protocol.do_create_receiver_module(
|
||||
basename=basename,
|
||||
protocol_create_code=protocol_create_code,
|
||||
is_async=is_async,
|
||||
private=private)
|
||||
|
||||
|
||||
def _protocol_from_code(protocol_create_code: str) -> MessageProtocol:
|
||||
env: Dict = {}
|
||||
exec(protocol_create_code, env) # pylint: disable=exec-used
|
||||
protocol = env.get('protocol')
|
||||
if not isinstance(protocol, MessageProtocol):
|
||||
raise RuntimeError(
|
||||
f'protocol_create_code yielded'
|
||||
f' a {type(protocol)}; expected a MessageProtocol instance.')
|
||||
return protocol
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user