Skip to content

Commit

Permalink
Add a method to register binary methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
BenediktBurger committed May 29, 2024
1 parent cb62581 commit d2d5ad8
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 3 deletions.
11 changes: 11 additions & 0 deletions pyleco/utils/listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ def register_rpc_method(self, method: Callable, **kwargs) -> None:
"""
self.message_handler.register_rpc_method(method=method, **kwargs)

def register_binary_rpc_method(
self, method: Callable, accept_binary_input: bool = False, **kwargs
) -> None:
"""Register a binary method for calling with the current message handler.
If you restart the listening, you have to register the method anew.
"""
self.message_handler.register_binary_rpc_method(

Check warning on line 149 in pyleco/utils/listener.py

View check run for this annotation

Codecov / codecov/patch

pyleco/utils/listener.py#L149

Added line #L149 was not covered by tests
method=method, accept_binary_input=accept_binary_input, **kwargs
)

def stop_listen(self) -> None:
"""Stop the listener Thread."""
try:
Expand Down
56 changes: 56 additions & 0 deletions pyleco/utils/message_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#

from __future__ import annotations
from functools import wraps
from json import JSONDecodeError
import logging
import time
Expand Down Expand Up @@ -138,6 +139,61 @@ def register_rpc_method(self, method: Callable, **kwargs) -> None:
"""Register a method to be available via rpc calls."""
self.rpc.method(**kwargs)(method)

def _handle_possible_binary_return_value(
self, return_value: Union[Any, bytes, list[bytes]]
) -> Optional[Any]:
if isinstance(return_value, (bytearray, bytes, memoryview)):
self.additional_response_payload = [return_value]
return None
elif isinstance(return_value, list) and isinstance(
return_value[0], (bytearray, bytes, memoryview)
):
self.additional_response_payload = return_value
return None
else:
return return_value

def _generate_binary_capable_method(
self,
method: Callable[..., Union[Any, bytes, list[bytes]]],
accept_binary_input: bool = False,
) -> Callable[..., Any]:
if accept_binary_input is True:

@wraps(method)
def modified_method(*args, **kwargs): # type: ignore
return_value = method(
*args, additional_payload=self.current_message.payload[1:], **kwargs
)
return self._handle_possible_binary_return_value(return_value=return_value)
else:

@wraps(method)
def modified_method(*args, **kwargs):
return_value = method(*args, **kwargs)
return self._handle_possible_binary_return_value(return_value=return_value)

return modified_method

def register_binary_rpc_method(
self,
method: Callable[..., Union[Any, bytes, list[bytes]]],
accept_binary_input: bool = False,
**kwargs,
) -> None:
"""Register a method which accepts binary input and/or returns binary values.
If a method should accept binary input, set the `accept_binary_input=True` and the method
must accept the additional payload as an `additional_payload` parameter.
If a method returns a binary object or a list of binary objects, they are sent as
additional_payload with the json response value `None`.
"""
modified_method = self._generate_binary_capable_method(
method=method, accept_binary_input=accept_binary_input
)
self.register_rpc_method(modified_method, **kwargs)

def register_rpc_methods(self) -> None:
"""Register methods for RPC."""
self.register_rpc_method(self.shut_down)
Expand Down
20 changes: 17 additions & 3 deletions tests/acceptance_tests/test_director_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
# THE SOFTWARE.
#

from __future__ import annotations
import logging
import threading
from time import sleep
Expand Down Expand Up @@ -70,15 +71,20 @@ def triple(self, factor: float = 1, factor2: float = 1) -> float:
def start_actor(event: threading.Event):
actor = Actor("actor", FakeInstrument, port=PORT)

def binary_method() -> None:
def binary_method_manually() -> None:
"""Receive binary data and return it."""
payload = actor.current_message.payload[1:]
try:
actor.additional_response_payload = [payload[0] * 2]
except IndexError:
pass

actor.register_rpc_method(binary_method)
def binary_method_created(additional_payload: list[bytes]) -> bytes:
"""Receive binary data and return it."""
return additional_payload[0] * 2

actor.register_rpc_method(binary_method_manually)
actor.register_binary_rpc_method(binary_method_created, accept_binary_input=True)
actor.connect()
actor.rpc.method()(actor.device.triple)
actor.register_device_method(actor.device.triple)
Expand Down Expand Up @@ -147,5 +153,13 @@ def test_device_method_via_rpc(director: Director):

def test_binary_data_transfer(director: Director):
assert director.ask_rpc(
method="binary_method", additional_payload=[b"123"], extract_additional_payload=True
method="binary_method_manually",
additional_payload=[b"123"],
extract_additional_payload=True,
) == (None, [b"123123"])


def test_binary_data_transfer_created(director: Director):
assert director.ask_rpc(
method="binary_method_created", additional_payload=[b"123"], extract_additional_payload=True
) == (None, [b"123123"])
68 changes: 68 additions & 0 deletions tests/utils/test_message_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,74 @@ def test_binary_payload_sent(self, handler_b: MessageHandler):
assert response.data == {"jsonrpc": "2.0", "id": 8, "result": None}


@pytest.mark.parametrize(
"return_value",
("asfd", 123.456, ["abc", 3, 9], 90),
)
def test_handle_possible_binary_return_value_unmodified_json(handler: MessageHandler, return_value):
result = handler._handle_possible_binary_return_value(return_value)
assert result == return_value
assert handler.additional_response_payload is None

@pytest.mark.parametrize(
"return_value, payload",
(
(b"abcd", [b"abcd"]),
([b"ab"], [b"ab"]),
([b"ab", b"cd"], [b"ab", b"cd"]),
),
)
def test_handle_possible_binary_return_value_with_binary(
handler: MessageHandler, return_value, payload
):
result = handler._handle_possible_binary_return_value(return_value)
assert result is None
assert handler.additional_response_payload == payload

class Test_generate_binary_method:
@pytest.fixture
def binary_method(self):
def binary_method(index: int, additional_payload: list[bytes]) -> bytes:
"""Docstring of binary method."""
return additional_payload[index]
return binary_method

@pytest.fixture(params=(True, False))
def modified_binary_method(self, handler: MessageHandler, binary_method, request):
handler.current_message = Message(
"rec", "send", data=b"", additional_payload=[b"0", b"1", b"2", b"3"]
)
self._accept_binary_input = abi = request.param
mod = handler._generate_binary_capable_method(binary_method, accept_binary_input=abi)
self.handler = handler
return mod

def test_name(self, binary_method, modified_binary_method):
assert modified_binary_method.__name__ == binary_method.__name__

def test_docstring(self, modified_binary_method, binary_method):
assert modified_binary_method.__doc__ == binary_method.__doc__

def test_annotation(self, modified_binary_method, binary_method):
assert modified_binary_method.__annotations__ == binary_method.__annotations__

def test_functionality_kwargs(self, modified_binary_method):
if self._accept_binary_input:
assert modified_binary_method(index=1) is None
else:
assert (
modified_binary_method(index=1, additional_payload=[b"0", b"1", b"2", b"3"]) is None
)
assert self.handler.additional_response_payload == [b"1"]

def test_functionality_args(self, modified_binary_method):
if self._accept_binary_input:
assert modified_binary_method(1) is None
else:
assert modified_binary_method(1, [b"0", b"1", b"2", b"3"]) is None
assert self.handler.additional_response_payload == [b"1"]


class Test_listen:
@pytest.fixture
def handler_l(self, handler: MessageHandler, fake_cid_generation):
Expand Down

0 comments on commit d2d5ad8

Please sign in to comment.