Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow varargs callback for signals #22

Merged
merged 2 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/dbus_fast/proxy_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ def _message_handler(self, msg):
def _add_signal(self, intr_signal, interface):
def on_signal_fn(fn):
fn_signature = inspect.signature(fn)
if not callable(fn) or len(fn_signature.parameters) != len(
Copy link
Collaborator Author

@mdegat01 mdegat01 Sep 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wanted to note that remove of not callable(fn) from here was intentional and is backwards compatible. That was never called. If fn was not callable then inspect.signature already raised a TypeError. I added a test confirm this behavior below.

intr_signal.args
if len(fn_signature.parameters) != len(intr_signal.args) and (
inspect.Parameter.VAR_POSITIONAL
not in [par.kind for par in fn_signature.parameters.values()]
or len(fn_signature.parameters) - 1 > len(intr_signal.args)
):
raise TypeError(
f"reply_notify must be a function with {len(intr_signal.args)} parameters"
Expand Down
95 changes: 95 additions & 0 deletions tests/client/test_signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from dbus_fast import Message
from dbus_fast.aio import MessageBus
from dbus_fast.aio.proxy_object import ProxyInterface
from dbus_fast.constants import RequestNameReply
from dbus_fast.introspection import Node
from dbus_fast.service import ServiceInterface, signal
Expand Down Expand Up @@ -158,6 +159,100 @@ def dummy_signal_handler(what):
bus3.disconnect()


@pytest.mark.asyncio
async def test_varargs_callback():
"""Test varargs callback for signal."""
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()

await bus1.request_name("test.signals.name")
service_interface = ExampleInterface()
bus1.export("/test/path", service_interface)

obj = bus2.get_proxy_object(
"test.signals.name", "/test/path", bus1._introspect_export_path("/test/path")
)
interface = obj.get_interface(service_interface.name)

async def ping():
await bus2.call(
Message(
destination=bus1.unique_name,
interface="org.freedesktop.DBus.Peer",
path="/test/path",
member="Ping",
)
)

varargs_handler_counter = 0
varargs_handler_err = None
varargs_plus_handler_counter = 0
varargs_plus_handler_err = None

def varargs_handler(*args):
nonlocal varargs_handler_counter
nonlocal varargs_handler_err
try:
assert args[0] == "hello"
varargs_handler_counter += 1
except AssertionError as ex:
varargs_handler_err = ex

def varargs_plus_handler(value, *_):
nonlocal varargs_plus_handler_counter
nonlocal varargs_plus_handler_err
try:
assert value == "hello"
varargs_plus_handler_counter += 1
except AssertionError as ex:
varargs_plus_handler_err = ex

interface.on_some_signal(varargs_handler)
interface.on_some_signal(varargs_plus_handler)
await ping()

service_interface.SomeSignal()
await ping()
assert varargs_handler_err is None
assert varargs_handler_counter == 1
assert varargs_plus_handler_err is None
assert varargs_plus_handler_counter == 1

bus1.disconnect()
bus2.disconnect()


@pytest.mark.asyncio
async def test_on_signal_type_error():
"""Test on callback raises type errors for invalid callbacks."""
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()

await bus1.request_name("test.signals.name")
service_interface = ExampleInterface()
bus1.export("/test/path", service_interface)

obj = bus2.get_proxy_object(
"test.signals.name", "/test/path", bus1._introspect_export_path("/test/path")
)
interface = obj.get_interface(service_interface.name)

with pytest.raises(TypeError):
interface.on_some_signal("not_a_callable")

with pytest.raises(TypeError):
interface.on_some_signal(lambda a, b: "Too many parameters")

with pytest.raises(TypeError):
interface.on_some_signal(lambda: "Too few parameters")

with pytest.raises(TypeError):
interface.on_some_signal(lambda a, b, *args: "Too many before varargs")

bus1.disconnect()
bus2.disconnect()


@pytest.mark.asyncio
async def test_signals_with_changing_owners():
well_known_name = "test.signals.changing.name"
Expand Down