efro.error.TransportError renamed to CommunicationError

This commit is contained in:
Eric Froemling 2021-09-05 19:37:54 -05:00
parent e9ca5092ef
commit 9733751716
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
7 changed files with 215 additions and 51 deletions

View File

@ -69,6 +69,7 @@
<w>animcurve</w>
<w>annarg</w>
<w>annargs</w>
<w>anns</w>
<w>anntype</w>
<w>anota</w>
<w>anroid</w>
@ -1402,6 +1403,7 @@
<w>mrmaxmeier</w>
<w>msbuild</w>
<w>msgdict</w>
<w>msgtype</w>
<w>mshell</w>
<w>msvccompiler</w>
<w>msvcp</w>
@ -1881,6 +1883,7 @@
<w>respawned</w>
<w>respawnicon</w>
<w>responsetype</w>
<w>responsetypes</w>
<w>resultstr</w>
<w>retrysecs</w>
<w>returncode</w>
@ -2118,6 +2121,7 @@
<w>startercache</w>
<w>startscan</w>
<w>starttime</w>
<w>statictest</w>
<w>statictestfiles</w>
<w>statictype</w>
<w>stayin</w>
@ -2217,6 +2221,7 @@
<w>targetname</w>
<w>targetpath</w>
<w>targetpractice</w>
<w>targs</w>
<w>tbtcolor</w>
<w>tbtn</w>
<w>tbttxt</w>

View File

@ -44,6 +44,7 @@
<w>aniso</w>
<w>annarg</w>
<w>annargs</w>
<w>anns</w>
<w>anntype</w>
<w>ansiwrap</w>
<w>anyofallof</w>
@ -639,6 +640,7 @@
<w>mqrspec</w>
<w>msaa</w>
<w>msgdict</w>
<w>msgtype</w>
<w>mult</w>
<w>multing</w>
<w>multipass</w>
@ -867,6 +869,7 @@
<w>resends</w>
<w>resetbtn</w>
<w>resetinput</w>
<w>responsetypes</w>
<w>resync</w>
<w>retrysecs</w>
<w>retval</w>
@ -980,6 +983,7 @@
<w>startx</w>
<w>starty</w>
<w>staticdata</w>
<w>statictest</w>
<w>stdint</w>
<w>stepfast</w>
<w>stephane</w>
@ -1014,6 +1018,7 @@
<w>tabtype</w>
<w>tabtypes</w>
<w>talloc</w>
<w>targs</w>
<w>tegra</w>
<w>telefonaktiebolaget</w>
<w>teleported</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2021-09-02 for Ballistica version 1.6.5 build 20391</em></h4>
<h4><em>last updated on 2021-09-05 for Ballistica version 1.6.5 build 20391</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>

View File

@ -4,7 +4,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, overload
from dataclasses import dataclass
import pytest
@ -12,9 +12,10 @@ import pytest
from efro.dataclassio import ioprepped
from efro.message import (Message, MessageProtocol, MessageSender,
MessageReceiver)
# from efrotools.statictest import static_type_equals
if TYPE_CHECKING:
from typing import List, Type
from typing import List, Type, Any, Callable, Union
@ioprepped
@ -25,42 +26,147 @@ class _TestMessage1(Message):
@classmethod
def get_response_types(cls) -> List[Type[Message]]:
return [_TestMessage2]
return [_TestMessageR1]
@ioprepped
@dataclass
class _TestMessage2(Message):
"""Just testing."""
sval: str
@classmethod
def get_response_types(cls) -> List[Type[Message]]:
return [_TestMessageR1, _TestMessageR2]
@ioprepped
@dataclass
class _TestMessageR1(Message):
"""Just testing."""
bval: bool
@ioprepped
@dataclass
class _TestMessageR2(Message):
"""Just testing."""
fval: float
@ioprepped
@dataclass
class _TestMessageR3(Message):
"""Just testing."""
fval: float
class _TestMessageSender(MessageSender):
"""Testing type overrides for message sending.
Normally this would be auto-generated based on the protocol.
"""
def __get__(self,
obj: Any,
type_in: Any = None) -> _BoundTestMessageSender:
return _BoundTestMessageSender(obj, self)
class _BoundTestMessageSender:
"""Testing type overrides for message sending.
Normally this would be auto-generated based on the protocol.
"""
def __init__(self, obj: Any, sender: _TestMessageSender) -> None:
assert obj is not None
self._obj = obj
self._sender = sender
@overload
def send(self, message: _TestMessage1) -> _TestMessageR1:
...
@overload
def send(self,
message: _TestMessage2) -> Union[_TestMessageR1, _TestMessageR2]:
...
def send(self, message: Any) -> Any:
"""Send a particular message type."""
return self._sender.send(self._obj, message)
class _TestMessageReceiver(MessageReceiver):
"""Testing type overrides for message receiving.
Normally this would be auto-generated based on the protocol.
"""
def __get__(self,
obj: Any,
type_in: Any = None) -> _BoundTestMessageReceiver:
return _BoundTestMessageReceiver(obj, self)
@overload
def handler(
self, call: Callable[[Any, _TestMessage1], _TestMessageR1]
) -> Callable[[Any, _TestMessage1], _TestMessageR1]:
...
@overload
def handler(
self, call: Callable[[Any, _TestMessage2], Union[_TestMessageR1,
_TestMessageR2]]
) -> Callable[[Any, _TestMessage2], Union[_TestMessageR1, _TestMessageR2]]:
...
def handler(self, call: Callable) -> Callable:
"""Decorator to register a handler for a particular message type."""
self.register_handler(call)
return call
class _BoundTestMessageReceiver:
"""Testing type overrides for message receiving.
Normally this would be auto-generated based on the protocol.
"""
def __init__(self, obj: Any, receiver: _TestMessageReceiver) -> None:
assert obj is not None
self._obj = obj
self._receiver = receiver
TEST_PROTOCOL = MessageProtocol(message_types={
1: _TestMessage1,
2: _TestMessage2,
3: _TestMessageR1,
4: _TestMessageR2,
})
def test_protocol_creation() -> None:
"""Test protocol creation."""
# This should fail because _TestMessage1 can return _TestMessage2 which
# This should fail because _TestMessage1 can return _TestMessageR1 which
# is not given an id here.
with pytest.raises(ValueError):
_protocol = MessageProtocol(message_types={1: _TestMessage1})
# Now it should work.
_protocol = MessageProtocol(message_types={
1: _TestMessage1,
2: _TestMessage2
2: _TestMessageR1
})
def test_message_sending() -> None:
"""Test simple message sending."""
protocol = MessageProtocol(message_types={
1: _TestMessage1,
2: _TestMessage2
})
# Define a class that can send messages and one that can receive them.
class TestClassS:
"""For testing send functionality."""
"""Test class incorporating send functionality."""
msg = MessageSender(protocol)
msg = _TestMessageSender(TEST_PROTOCOL)
def __init__(self, receiver: TestClassR) -> None:
self._receiver = receiver
@ -72,18 +178,31 @@ def test_message_sending() -> None:
return b''
class TestClassR:
"""For testing receive functionality."""
"""Test class incorporating receive functionality."""
receiver = MessageReceiver(protocol)
receiver = _TestMessageReceiver(TEST_PROTOCOL)
@receiver.handler
def handle_test_message(self, msg: Message) -> Message:
def handle_test_message_1(self, msg: _TestMessage1) -> _TestMessageR1:
"""Test."""
del msg # Unused
print('Hello from test message 1 handler!')
return _TestMessage2(bval=True)
return _TestMessageR1(bval=True)
@receiver.handler
def handle_test_message_2(
self,
msg: _TestMessage2) -> Union[_TestMessageR1, _TestMessageR2]:
"""Test."""
del msg # Unused
print('Hello from test message 1 handler!')
return _TestMessageR2(fval=1.2)
obj_r = TestClassR()
obj_s = TestClassS(receiver=obj_r)
print(f'MADE TEST OBJS {obj_s} and {obj_r}')
obj_s.msg.send(_TestMessage1(ival=0))
_result = obj_s.msg.send(_TestMessage1(ival=0))
_result2 = obj_s.msg.send(_TestMessage2(sval='rah'))
print('SKIPPING STATIC CHECK')
# assert static_type_equals(result, _TestMessageR1)
# assert isinstance(result, _TestMessageR1)

View File

@ -35,8 +35,8 @@ class CleanError(Exception):
print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush)
class TransportError(Exception):
"""A transport-related communication error has occurred.
class CommunicationError(Exception):
"""A communication related error has occurred.
This covers anything network-related going wrong in the sending
of data or receiving of a response. This error does not imply

View File

@ -17,8 +17,9 @@ from efro.dataclassio import (ioprepped, is_ioprepped_dataclass, IOAttrs,
dataclass_to_dict)
if TYPE_CHECKING:
from typing import Dict, Type, Tuple, List, Any, Callable, Optional, Set
from efro.error import TransportError
from typing import (Dict, Type, Tuple, List, Any, Callable, Optional, Set,
Sequence)
from efro.error import CommunicationError
TM = TypeVar('TM', bound='MessageSender')
@ -101,6 +102,7 @@ class MessageProtocol:
for m_id, m_type in message_types.items():
m_rtypes = m_type.get_response_types()
assert isinstance(m_rtypes, list)
assert len(set(m_rtypes)) == len(m_rtypes) # check for dups
all_response_types.update(m_rtypes)
for cls in all_response_types:
assert is_ioprepped_dataclass(cls) and issubclass(cls, Message)
@ -117,7 +119,7 @@ class MessageProtocol:
m_id = self._message_ids_by_type.get(type(message))
if m_id is None:
raise TypeError(f'Message type is not registered in protocol:'
raise TypeError(f'Message type is not registered in Protocol:'
f' {type(message)}')
msgdict = dataclass_to_dict(message)
@ -154,6 +156,23 @@ class MessageProtocol:
the protocol.
"""
def validate_message_type(self, msgtype: Type,
responsetypes: Sequence[Type]) -> None:
"""Ensure message type associated response types are valid.
Raises an exception if not.
"""
if msgtype not in self._message_ids_by_type:
raise TypeError(f'Message type {msgtype} is not registered'
f' in this Protocol.')
# Make sure the responses exactly matches what the message expects.
assert len(set(responsetypes)) == len(responsetypes)
for responsetype in responsetypes:
if responsetype not in self._message_ids_by_type:
raise TypeError(f'Response message type {responsetype} is'
f' not registered in this Protocol.')
class MessageSender:
"""Facilitates sending messages to a target and receiving responses.
@ -179,18 +198,6 @@ class MessageSender:
self._protocol = protocol
self._send_raw_message_call: Optional[Callable[[Any, bytes],
bytes]] = None
self._bound_obj: Any = None
def __get__(self: TM, obj: Any, type_in: Any = None) -> TM:
if obj is None:
raise RuntimeError('Must be called on an instance, not a type.')
# Return a copy of ourself bound to the instance we were called from.
bound_sender = type(self)(self._protocol)
bound_sender._send_raw_message_call = self._send_raw_message_call
bound_sender._bound_obj = obj
return bound_sender
def send_raw_handler(
self, call: Callable[[Any, bytes],
@ -200,25 +207,23 @@ class MessageSender:
self._send_raw_message_call = call
return call
def send(self, message: Message) -> Any:
def send(self, bound_obj: Any, message: Message) -> Any:
"""Send a message and receive a response.
Will encode the message for transport and call dispatch_raw_message()
"""
if self._send_raw_message_call is None:
raise RuntimeError('send() is unimplemented for this type.')
if self._bound_obj is None:
raise RuntimeError('Cannot call on an unbound instance.')
encoded = self._protocol.message_encode(message)
return self._send_raw_message_call(None, encoded)
return self._send_raw_message_call(bound_obj, encoded)
def send_bg(self, message: Any) -> Any:
def send_bg(self, bound_obj: Any, message: Message) -> Message:
"""Send a message asynchronously and receive a future.
The message will be encoded for transport and passed to
dispatch_raw_message from a background thread.
"""
raise RuntimeError('Unimplemented!')
def send_async(self, message: Any) -> Any:
def send_async(self, bound_obj: Any, message: Message) -> Message:
"""Send a message asynchronously using asyncio.
The message will be encoded for transport and passed to
dispatch_raw_message_async.
@ -253,12 +258,42 @@ class MessageReceiver:
def __init__(self, protocol: MessageProtocol) -> None:
self._protocol = protocol
def handler(
self, call: Callable[[Any, Message], Message]
) -> Callable[[Any, Message], Message]:
"""Decorator for registering calls to handle message types."""
print('WOULD REGISTER HANDLER TYPE')
return call
# noinspection PyProtectedMember
def register_handler(self, call: Callable) -> None:
"""Register a handler call.
The message type handled by the call is determined by its
type annotation.
"""
# TODO: can use types.GenericAlias in 3.9.
from typing import _GenericAlias # type: ignore
from typing import Union, get_type_hints, get_args
# Return-type annotation can be a Union, but we probably don't
# have it available at runtime. Explicitly pull it in.
anns = get_type_hints(call, localns={'Union': Union})
msg = anns.get('msg')
if not isinstance(msg, type):
raise TypeError(
f'expected a type for "msg" annotation; got {type(msg)}.')
ret = anns.get('return')
rets: Tuple[Type, ...]
# Return types can be a single type or a union of types.
if isinstance(ret, _GenericAlias):
targs = get_args(ret)
if not all(isinstance(a, type) for a in targs):
raise TypeError(f'expected only types for "return" annotation;'
f' got {targs}.')
rets = targs
print(f'LOOKED AT GENERIC ALIAS {targs}')
else:
if not isinstance(ret, type):
raise TypeError(f'expected one or more types for'
f' "return" annotation; got a {type(ret)}.')
rets = (ret, )
print(f'WOULD REGISTER HANDLER! (got {msg} and {rets})')
def handle_raw_message(self, msg: bytes) -> bytes:
"""Should be called when the receiver gets a message.

View File

@ -10,11 +10,11 @@ if TYPE_CHECKING:
def is_urllib_network_error(exc: BaseException) -> bool:
"""Is the provided exception a network-related error?
"""Is the provided exception from urllib a network-related error?
This should be passed an exception which resulted from opening or
reading a urllib Request. It should return True for any errors that
could conceivably arise due to unavailable/poor network connections,
reading a urllib Request. It returns True for any errors that could
conceivably arise due to unavailable/poor network connections,
firewall/connectivity issues, etc. These issues can often be safely
ignored or presented to the user as general 'network-unavailable'
states.