Blame dbus/decorators.py

Packit 130fc8
"""Service-side D-Bus decorators."""
Packit 130fc8
Packit 130fc8
# Copyright (C) 2003, 2004, 2005, 2006 Red Hat Inc. <http://www.redhat.com/>
Packit 130fc8
# Copyright (C) 2003 David Zeuthen
Packit 130fc8
# Copyright (C) 2004 Rob Taylor
Packit 130fc8
# Copyright (C) 2005, 2006 Collabora Ltd. <http://www.collabora.co.uk/>
Packit 130fc8
#
Packit 130fc8
# Permission is hereby granted, free of charge, to any person
Packit 130fc8
# obtaining a copy of this software and associated documentation
Packit 130fc8
# files (the "Software"), to deal in the Software without
Packit 130fc8
# restriction, including without limitation the rights to use, copy,
Packit 130fc8
# modify, merge, publish, distribute, sublicense, and/or sell copies
Packit 130fc8
# of the Software, and to permit persons to whom the Software is
Packit 130fc8
# furnished to do so, subject to the following conditions:
Packit 130fc8
#
Packit 130fc8
# The above copyright notice and this permission notice shall be
Packit 130fc8
# included in all copies or substantial portions of the Software.
Packit 130fc8
#
Packit 130fc8
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
Packit 130fc8
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
Packit 130fc8
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
Packit 130fc8
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
Packit 130fc8
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
Packit 130fc8
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
Packit 130fc8
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
Packit 130fc8
# DEALINGS IN THE SOFTWARE.
Packit 130fc8
Packit 130fc8
__all__ = ('method', 'signal')
Packit 130fc8
__docformat__ = 'restructuredtext'
Packit 130fc8
Packit 130fc8
import inspect
Packit 130fc8
Packit 130fc8
from dbus import validate_interface_name, Signature, validate_member_name
Packit 130fc8
from dbus.lowlevel import SignalMessage
Packit 130fc8
from dbus.exceptions import DBusException
Packit 130fc8
from dbus._compat import is_py2
Packit 130fc8
Packit 130fc8
Packit 130fc8
def method(dbus_interface, in_signature=None, out_signature=None,
Packit 130fc8
           async_callbacks=None,
Packit 130fc8
           sender_keyword=None, path_keyword=None, destination_keyword=None,
Packit 130fc8
           message_keyword=None, connection_keyword=None,
Packit 130fc8
           byte_arrays=False,
Packit 130fc8
           rel_path_keyword=None, **kwargs):
Packit 130fc8
    """Factory for decorators used to mark methods of a `dbus.service.Object`
Packit 130fc8
    to be exported on the D-Bus.
Packit 130fc8
Packit 130fc8
    The decorated method will be exported over D-Bus as the method of the
Packit 130fc8
    same name on the given D-Bus interface.
Packit 130fc8
Packit 130fc8
    :Parameters:
Packit 130fc8
        `dbus_interface` : str
Packit 130fc8
            Name of a D-Bus interface
Packit 130fc8
        `in_signature` : str or None
Packit 130fc8
            If not None, the signature of the method parameters in the usual
Packit 130fc8
            D-Bus notation
Packit 130fc8
        `out_signature` : str or None
Packit 130fc8
            If not None, the signature of the return value in the usual
Packit 130fc8
            D-Bus notation
Packit 130fc8
        `async_callbacks` : tuple containing (str,str), or None
Packit 130fc8
            If None (default) the decorated method is expected to return
Packit 130fc8
            values matching the `out_signature` as usual, or raise
Packit 130fc8
            an exception on error. If not None, the following applies:
Packit 130fc8
Packit 130fc8
            `async_callbacks` contains the names of two keyword arguments to
Packit 130fc8
            the decorated function, which will be used to provide a success
Packit 130fc8
            callback and an error callback (in that order).
Packit 130fc8
Packit 130fc8
            When the decorated method is called via the D-Bus, its normal
Packit 130fc8
            return value will be ignored; instead, a pair of callbacks are
Packit 130fc8
            passed as keyword arguments, and the decorated method is
Packit 130fc8
            expected to arrange for one of them to be called.
Packit 130fc8
Packit 130fc8
            On success the success callback must be called, passing the
Packit 130fc8
            results of this method as positional parameters in the format
Packit 130fc8
            given by the `out_signature`.
Packit 130fc8
Packit 130fc8
            On error the decorated method may either raise an exception
Packit 130fc8
            before it returns, or arrange for the error callback to be
Packit 130fc8
            called with an Exception instance as parameter.
Packit 130fc8
Packit 130fc8
        `sender_keyword` : str or None
Packit 130fc8
            If not None, contains the name of a keyword argument to the
Packit 130fc8
            decorated function, conventionally ``'sender'``. When the
Packit 130fc8
            method is called, the sender's unique name will be passed as
Packit 130fc8
            this keyword argument.
Packit 130fc8
Packit 130fc8
        `path_keyword` : str or None
Packit 130fc8
            If not None (the default), the decorated method will receive
Packit 130fc8
            the destination object path as a keyword argument with this
Packit 130fc8
            name. Normally you already know the object path, but in the
Packit 130fc8
            case of "fallback paths" you'll usually want to use the object
Packit 130fc8
            path in the method's implementation.
Packit 130fc8
Packit 130fc8
            For fallback objects, `rel_path_keyword` (new in 0.82.2) is
Packit 130fc8
            likely to be more useful.
Packit 130fc8
Packit 130fc8
            :Since: 0.80.0?
Packit 130fc8
Packit 130fc8
        `rel_path_keyword` : str or None
Packit 130fc8
            If not None (the default), the decorated method will receive
Packit 130fc8
            the destination object path, relative to the path at which the
Packit 130fc8
            object was exported, as a keyword argument with this
Packit 130fc8
            name. For non-fallback objects the relative path will always be
Packit 130fc8
            '/'.
Packit 130fc8
Packit 130fc8
            :Since: 0.82.2
Packit 130fc8
Packit 130fc8
        `destination_keyword` : str or None
Packit 130fc8
            If not None (the default), the decorated method will receive
Packit 130fc8
            the destination bus name as a keyword argument with this name.
Packit 130fc8
            Included for completeness - you shouldn't need this.
Packit 130fc8
Packit 130fc8
            :Since: 0.80.0?
Packit 130fc8
Packit 130fc8
        `message_keyword` : str or None
Packit 130fc8
            If not None (the default), the decorated method will receive
Packit 130fc8
            the `dbus.lowlevel.MethodCallMessage` as a keyword argument
Packit 130fc8
            with this name.
Packit 130fc8
Packit 130fc8
            :Since: 0.80.0?
Packit 130fc8
Packit 130fc8
        `connection_keyword` : str or None
Packit 130fc8
            If not None (the default), the decorated method will receive
Packit 130fc8
            the `dbus.connection.Connection` as a keyword argument
Packit 130fc8
            with this name. This is generally only useful for objects
Packit 130fc8
            that are available on more than one connection.
Packit 130fc8
Packit 130fc8
            :Since: 0.82.0
Packit 130fc8
Packit 130fc8
        `utf8_strings` : bool
Packit 130fc8
            If False (default), D-Bus strings are passed to the decorated
Packit 130fc8
            method as objects of class dbus.String, a unicode subclass.
Packit 130fc8
Packit 130fc8
            If True, D-Bus strings are passed to the decorated method
Packit 130fc8
            as objects of class dbus.UTF8String, a str subclass guaranteed
Packit 130fc8
            to be encoded in UTF-8.
Packit 130fc8
Packit 130fc8
            This option does not affect object-paths and signatures, which
Packit 130fc8
            are always 8-bit strings (str subclass) encoded in ASCII.
Packit 130fc8
Packit 130fc8
            :Since: 0.80.0
Packit 130fc8
Packit 130fc8
        `byte_arrays` : bool
Packit 130fc8
            If False (default), a byte array will be passed to the decorated
Packit 130fc8
            method as an `Array` (a list subclass) of `Byte` objects.
Packit 130fc8
Packit 130fc8
            If True, a byte array will be passed to the decorated method as
Packit 130fc8
            a `ByteArray`, a str subclass. This is usually what you want,
Packit 130fc8
            but is switched off by default to keep dbus-python's API
Packit 130fc8
            consistent.
Packit 130fc8
Packit 130fc8
            :Since: 0.80.0
Packit 130fc8
    """
Packit 130fc8
    validate_interface_name(dbus_interface)
Packit 130fc8
Packit 130fc8
    def decorator(func):
Packit 130fc8
        if hasattr(inspect, 'Signature'):
Packit 130fc8
            args = []
Packit 130fc8
Packit 130fc8
            for arg in inspect.signature(func).parameters.values():
Packit 130fc8
                if arg.kind in (inspect.Parameter.POSITIONAL_ONLY,
Packit 130fc8
                        inspect.Parameter.POSITIONAL_OR_KEYWORD):
Packit 130fc8
                    args.append(arg.name)
Packit 130fc8
        else:
Packit 130fc8
            args = inspect.getargspec(func)[0]
Packit 130fc8
Packit 130fc8
        args.pop(0)
Packit 130fc8
Packit 130fc8
        if async_callbacks:
Packit 130fc8
            if type(async_callbacks) != tuple:
Packit 130fc8
                raise TypeError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
Packit 130fc8
            if len(async_callbacks) != 2:
Packit 130fc8
                raise ValueError('async_callbacks must be a tuple of (keyword for return callback, keyword for error callback)')
Packit 130fc8
            args.remove(async_callbacks[0])
Packit 130fc8
            args.remove(async_callbacks[1])
Packit 130fc8
Packit 130fc8
        if sender_keyword:
Packit 130fc8
            args.remove(sender_keyword)
Packit 130fc8
        if rel_path_keyword:
Packit 130fc8
            args.remove(rel_path_keyword)
Packit 130fc8
        if path_keyword:
Packit 130fc8
            args.remove(path_keyword)
Packit 130fc8
        if destination_keyword:
Packit 130fc8
            args.remove(destination_keyword)
Packit 130fc8
        if message_keyword:
Packit 130fc8
            args.remove(message_keyword)
Packit 130fc8
        if connection_keyword:
Packit 130fc8
            args.remove(connection_keyword)
Packit 130fc8
Packit 130fc8
        if in_signature:
Packit 130fc8
            in_sig = tuple(Signature(in_signature))
Packit 130fc8
Packit 130fc8
            if len(in_sig) > len(args):
Packit 130fc8
                raise ValueError('input signature is longer than the number of arguments taken')
Packit 130fc8
            elif len(in_sig) < len(args):
Packit 130fc8
                raise ValueError('input signature is shorter than the number of arguments taken')
Packit 130fc8
Packit 130fc8
        func._dbus_is_method = True
Packit 130fc8
        func._dbus_async_callbacks = async_callbacks
Packit 130fc8
        func._dbus_interface = dbus_interface
Packit 130fc8
        func._dbus_in_signature = in_signature
Packit 130fc8
        func._dbus_out_signature = out_signature
Packit 130fc8
        func._dbus_sender_keyword = sender_keyword
Packit 130fc8
        func._dbus_path_keyword = path_keyword
Packit 130fc8
        func._dbus_rel_path_keyword = rel_path_keyword
Packit 130fc8
        func._dbus_destination_keyword = destination_keyword
Packit 130fc8
        func._dbus_message_keyword = message_keyword
Packit 130fc8
        func._dbus_connection_keyword = connection_keyword
Packit 130fc8
        func._dbus_args = args
Packit 130fc8
        func._dbus_get_args_options = dict(byte_arrays=byte_arrays)
Packit 130fc8
        if is_py2:
Packit 130fc8
            func._dbus_get_args_options['utf8_strings'] = kwargs.get(
Packit 130fc8
                'utf8_strings', False)
Packit 130fc8
        elif 'utf8_strings' in kwargs:
Packit 130fc8
            raise TypeError("unexpected keyword argument 'utf8_strings'")
Packit 130fc8
        return func
Packit 130fc8
Packit 130fc8
    return decorator
Packit 130fc8
Packit 130fc8
Packit 130fc8
def signal(dbus_interface, signature=None, path_keyword=None,
Packit 130fc8
           rel_path_keyword=None):
Packit 130fc8
    """Factory for decorators used to mark methods of a `dbus.service.Object`
Packit 130fc8
    to emit signals on the D-Bus.
Packit 130fc8
Packit 130fc8
    Whenever the decorated method is called in Python, after the method
Packit 130fc8
    body is executed, a signal with the same name as the decorated method,
Packit 130fc8
    with the given D-Bus interface, will be emitted from this object.
Packit 130fc8
Packit 130fc8
    :Parameters:
Packit 130fc8
        `dbus_interface` : str
Packit 130fc8
            The D-Bus interface whose signal is emitted
Packit 130fc8
        `signature` : str
Packit 130fc8
            The signature of the signal in the usual D-Bus notation
Packit 130fc8
Packit 130fc8
        `path_keyword` : str or None
Packit 130fc8
            A keyword argument to the decorated method. If not None,
Packit 130fc8
            that argument will not be emitted as an argument of
Packit 130fc8
            the signal, and when the signal is emitted, it will appear
Packit 130fc8
            to come from the object path given by the keyword argument.
Packit 130fc8
Packit 130fc8
            Note that when calling the decorated method, you must always
Packit 130fc8
            pass in the object path as a keyword argument, not as a
Packit 130fc8
            positional argument.
Packit 130fc8
Packit 130fc8
            This keyword argument cannot be used on objects where
Packit 130fc8
            the class attribute ``SUPPORTS_MULTIPLE_OBJECT_PATHS`` is true.
Packit 130fc8
Packit 130fc8
            :Deprecated: since 0.82.0. Use `rel_path_keyword` instead.
Packit 130fc8
Packit 130fc8
        `rel_path_keyword` : str or None
Packit 130fc8
            A keyword argument to the decorated method. If not None,
Packit 130fc8
            that argument will not be emitted as an argument of
Packit 130fc8
            the signal.
Packit 130fc8
Packit 130fc8
            When the signal is emitted, if the named keyword argument is given,
Packit 130fc8
            the signal will appear to come from the object path obtained by
Packit 130fc8
            appending the keyword argument to the object's object path.
Packit 130fc8
            This is useful to implement "fallback objects" (objects which
Packit 130fc8
            own an entire subtree of the object-path tree).
Packit 130fc8
Packit 130fc8
            If the object is available at more than one object-path on the
Packit 130fc8
            same or different connections, the signal will be emitted at
Packit 130fc8
            an appropriate object-path on each connection - for instance,
Packit 130fc8
            if the object is exported at /abc on connection 1 and at
Packit 130fc8
            /def and /x/y/z on connection 2, and the keyword argument is
Packit 130fc8
            /foo, then signals will be emitted from /abc/foo and /def/foo
Packit 130fc8
            on connection 1, and /x/y/z/foo on connection 2.
Packit 130fc8
Packit 130fc8
            :Since: 0.82.0
Packit 130fc8
    """
Packit 130fc8
    validate_interface_name(dbus_interface)
Packit 130fc8
Packit 130fc8
    if path_keyword is not None:
Packit 130fc8
        from warnings import warn
Packit 130fc8
        warn(DeprecationWarning('dbus.service.signal::path_keyword has been '
Packit 130fc8
                                'deprecated since dbus-python 0.82.0, and '
Packit 130fc8
                                'will not work on objects that support '
Packit 130fc8
                                'multiple object paths'),
Packit 130fc8
             DeprecationWarning, stacklevel=2)
Packit 130fc8
        if rel_path_keyword is not None:
Packit 130fc8
            raise TypeError('dbus.service.signal::path_keyword and '
Packit 130fc8
                            'rel_path_keyword cannot both be used')
Packit 130fc8
Packit 130fc8
    def decorator(func):
Packit 130fc8
        member_name = func.__name__
Packit 130fc8
        validate_member_name(member_name)
Packit 130fc8
Packit 130fc8
        def emit_signal(self, *args, **keywords):
Packit 130fc8
            abs_path = None
Packit 130fc8
            if path_keyword is not None:
Packit 130fc8
                if self.SUPPORTS_MULTIPLE_OBJECT_PATHS:
Packit 130fc8
                    raise TypeError('path_keyword cannot be used on the '
Packit 130fc8
                                    'signals of an object that supports '
Packit 130fc8
                                    'multiple object paths')
Packit 130fc8
                abs_path = keywords.pop(path_keyword, None)
Packit 130fc8
                if (abs_path != self.__dbus_object_path__ and
Packit 130fc8
                    not self.__dbus_object_path__.startswith(abs_path + '/')):
Packit 130fc8
                    raise ValueError('Path %r is not below %r', abs_path,
Packit 130fc8
                                     self.__dbus_object_path__)
Packit 130fc8
Packit 130fc8
            rel_path = None
Packit 130fc8
            if rel_path_keyword is not None:
Packit 130fc8
                rel_path = keywords.pop(rel_path_keyword, None)
Packit 130fc8
Packit 130fc8
            func(self, *args, **keywords)
Packit 130fc8
Packit 130fc8
            for location in self.locations:
Packit 130fc8
                if abs_path is None:
Packit 130fc8
                    # non-deprecated case
Packit 130fc8
                    if rel_path is None or rel_path in ('/', ''):
Packit 130fc8
                        object_path = location[1]
Packit 130fc8
                    else:
Packit 130fc8
                        # will be validated by SignalMessage ctor in a moment
Packit 130fc8
                        object_path = location[1] + rel_path
Packit 130fc8
                else:
Packit 130fc8
                    object_path = abs_path
Packit 130fc8
Packit 130fc8
                message = SignalMessage(object_path,
Packit 130fc8
                                                       dbus_interface,
Packit 130fc8
                                                       member_name)
Packit 130fc8
                message.append(signature=signature, *args)
Packit 130fc8
Packit 130fc8
                location[0].send_message(message)
Packit 130fc8
        # end emit_signal
Packit 130fc8
Packit 130fc8
        args = inspect.getargspec(func)[0]
Packit 130fc8
        args.pop(0)
Packit 130fc8
Packit 130fc8
        for keyword in rel_path_keyword, path_keyword:
Packit 130fc8
            if keyword is not None:
Packit 130fc8
                try:
Packit 130fc8
                    args.remove(keyword)
Packit 130fc8
                except ValueError:
Packit 130fc8
                    raise ValueError('function has no argument "%s"' % keyword)
Packit 130fc8
Packit 130fc8
        if signature:
Packit 130fc8
            sig = tuple(Signature(signature))
Packit 130fc8
Packit 130fc8
            if len(sig) > len(args):
Packit 130fc8
                raise ValueError('signal signature is longer than the number of arguments provided')
Packit 130fc8
            elif len(sig) < len(args):
Packit 130fc8
                raise ValueError('signal signature is shorter than the number of arguments provided')
Packit 130fc8
Packit 130fc8
        emit_signal.__name__ = func.__name__
Packit 130fc8
        emit_signal.__doc__ = func.__doc__
Packit 130fc8
        emit_signal._dbus_is_signal = True
Packit 130fc8
        emit_signal._dbus_interface = dbus_interface
Packit 130fc8
        emit_signal._dbus_signature = signature
Packit 130fc8
        emit_signal._dbus_args = args
Packit 130fc8
        return emit_signal
Packit 130fc8
Packit 130fc8
    return decorator