diff --git a/.gitignore b/.gitignore index 8793551..293aaa9 100644 --- a/.gitignore +++ b/.gitignore @@ -124,3 +124,6 @@ dmypy.json # vim *.swp + +# vscode +.vscode/ diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index 8bb830d..e1d3bce 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -9,13 +9,57 @@ from .proxy_object import BaseProxyObject from . import introspection as intr +import contextvars import inspect import socket import logging import xml.etree.ElementTree as ET import traceback -from typing import Type, Callable, Optional, Union +from typing import Type, Callable, Optional, Union, Any + + +class ReadOnlyContextProxy: + """ + A convenience class for making a context variable accessible as though it + were a local. Any request for an attribute (other than `set_value`) on the + proxy will be passed through to the underlying variable. Attributes are + immutable. + + :param name: The name of the context variable. + """ + def __init__(self, name: str): + self._obj = contextvars.ContextVar(name) + + def __getattr__(self, name: str) -> Any: + proxy = self._obj.get() + return getattr(proxy, name) + + def set_value(self, value: Any): + """ + Set the value of the underlying context variable. + """ + self._obj.set(value) + + +""" +The :class:`Message ` object currently being handled. + +Client code can use this to obtain access to details from the message without +modifying their public API. Typical use is: + +``` +from dbus_next.message_bus import current_message + +@method() +def echo_sender() -> 's': + return current_message.sender +``` + +Attempts to access any attribute of `current_message` outside of a message context +will result in a `LookupError` being raised. +""" +current_message = ReadOnlyContextProxy("current_message") class BaseMessageBus: @@ -661,6 +705,7 @@ def send_error(self, exc): return SendReply() def _process_message(self, msg): + current_message.set_value(msg) handled = False for handler in self._user_message_handlers: diff --git a/dbus_next/service.py b/dbus_next/service.py index b2a96cf..a241dcb 100644 --- a/dbus_next/service.py +++ b/dbus_next/service.py @@ -88,7 +88,7 @@ def echo_two(self, val1: 's', val2: 'u') -> 'su': def decorator(fn): @wraps(fn) def wrapped(*args, **kwargs): - fn(*args, **kwargs) + return fn(*args, **kwargs) fn_name = name if name else fn.__name__ wrapped.__dict__['__DBUS_METHOD'] = _Method(fn, fn_name, disabled=disabled) diff --git a/test/client/test_methods.py b/test/client/test_methods.py index 5f2c6c0..e74e864 100644 --- a/test/client/test_methods.py +++ b/test/client/test_methods.py @@ -2,6 +2,7 @@ from dbus_next.service import ServiceInterface, method import dbus_next.introspection as intr from dbus_next import aio, glib, DBusError +from dbus_next.message_bus import current_message from test.util import check_gi_repository, skip_reason_no_gi import pytest @@ -37,6 +38,10 @@ def EchoThree(self, what1: 's', what2: 's', what3: 's') -> 'sss': def ThrowsError(self): raise DBusError('test.error', 'something went wrong') + @method() + def UsesCurrentMessage(self) -> 's': + return current_message.sender + @pytest.mark.asyncio async def test_aio_proxy_object(): @@ -79,6 +84,9 @@ async def test_aio_proxy_object(): result = await interface.call_echo_string('no reply', flags=MessageFlag.NO_REPLY_EXPECTED) assert result is None + result = await interface.call_uses_current_message() + assert result + with pytest.raises(DBusError): try: await interface.call_throws_error() diff --git a/test/client/test_signals.py b/test/client/test_signals.py index 848265d..94b14cb 100644 --- a/test/client/test_signals.py +++ b/test/client/test_signals.py @@ -3,6 +3,7 @@ from dbus_next import Message from dbus_next.introspection import Node from dbus_next.constants import RequestNameReply +from dbus_next.message_bus import current_message import pytest @@ -53,6 +54,7 @@ def single_handler(value): nonlocal single_counter nonlocal err assert value == 'hello' + assert current_message.sender single_counter += 1 except Exception as e: err = e @@ -65,6 +67,7 @@ def multiple_handler(value1, value2): try: assert value1 == 'hello' assert value2 == 'world' + assert current_message.sender multiple_counter += 1 except Exception as e: err = e diff --git a/test/test_tcp_address.py b/test/test_tcp_address.py index a6b6ebb..3e33ba8 100644 --- a/test/test_tcp_address.py +++ b/test/test_tcp_address.py @@ -15,8 +15,12 @@ async def test_tcp_connection_with_forwarding(event_loop): addr_info = parse_address(os.environ.get('DBUS_SESSION_BUS_ADDRESS')) assert addr_info - assert 'abstract' in addr_info[0][1] - path = f'\0{addr_info[0][1]["abstract"]}' + if 'abstract' in addr_info[0][1]: + path = f'\0{addr_info[0][1]["abstract"]}' + elif 'path' in addr_info[0][1]: + path = addr_info[0][1]['path'] + + assert path async def handle_connection(tcp_reader, tcp_writer): unix_reader, unix_writer = await asyncio.open_unix_connection(path)