mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-27 17:33:13 +08:00
efro.error.TransportError renamed to CommunicationError
This commit is contained in:
parent
e9ca5092ef
commit
9733751716
5
.idea/dictionaries/ericf.xml
generated
5
.idea/dictionaries/ericf.xml
generated
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user