LEW21 / pydbus

Pythonic DBus library
GNU Lesser General Public License v2.1
331 stars 75 forks source link

Support async methods #58

Open pespin opened 7 years ago

pespin commented 7 years ago

I could not find any API which exports or uses async methods from Gio. I was able to workaround the issue by implementing the following snippet in my code:

def __async_result_handler(obj, result, user_data):
    '''Generic callback dispatcher called from glib loop when an async method
    call has returned. This callback is set up by method dbus_async_call.'''
    (result_callback, error_callback, real_user_data) = user_data
    try:
        ret = obj.call_finish(result)
    except Exception:
        etype, e = sys.exc_info()[:2]
        # return exception as value
        if error_callback:
            error_callback(obj, e, real_user_data)
        else:
            result_callback(obj, e, real_user_data)
        return

    ret = ret.unpack()
    # to be compatible with standard Python behaviour, unbox
    # single-element tuples and return None for empty result tuples
    if len(ret) == 1:
        ret = ret[0]
    elif len(ret) == 0:
        ret = None
    result_callback(obj, ret, real_user_data)

def dbus_async_call(proxymethod, instance, *args, **kwargs):
    '''pydbus doesn't support asynchronous methods. This method adds support for
    it until pydbus implements it'''
    argdiff = len(args) - len(proxymethod._inargs)
    if argdiff < 0:
        raise TypeError(proxymethod.__qualname__ + " missing {} required positional argument(s)".format(-argdiff))
    elif argdiff > 0:
        raise TypeError(proxymethod.__qualname__ + " takes {} positional argument(s) but {} was/were given".format(len(proxymethod._inargs), len(args)))

    timeout = kwargs.get("timeout", 30) * 1000
    user_data = (kwargs['result_handler'], kwargs.get('error_handler'), kwargs.get('user_data'))

    ret = instance._bus.con.call(
        instance._bus_name, instance._path,
        proxymethod._iface_name, proxymethod.__name__,
        GLib.Variant(proxymethod._sinargs, args), GLib.VariantType.new(proxymethod._soutargs),
        0, timeout, None,
        __async_result_handler, user_data)

Then call it with something like this:

        dbus_async_call(nr.Scan, nr, timeout=30, result_handler=self.scan_result_cb,
                        error_handler=self.raise_exn_cb, user_data=None)

    def raise_exn_cb(self, obj, e, user_data):
        pass

    def scan_result_cb(self, obj, result, user_data):
        self.register(result)

It would be nice that pydbus could support this out of the box, by using something like dbus_object.foo_method(param0, ..., param N, timeout=XYZ, reply_handler=myfunction, error_handler=myotherfunction)

hcoin commented 7 years ago

I've got the system so all the parameters can be named, with defaults, along with the arglist. Since I created a patch so that pydbus works on even older versions, I worked through that routine. I'll see about breaking out the callback / error callback. The timeout is set already at registration time.

On 05/31/2017 06:00 AM, Pau Espin Pedrol wrote:

I could not find any API which exports or uses async methods from Gio. I was able to workaround the issue by implementing the following snippet in my code:

|def async_result_handler(obj, result, user_data): '''Generic callback dispatcher called from glib loop when an async method call has returned. This callback is set up by method dbus_async_call.''' (result_callback, error_callback, real_user_data) = user_data try: ret = obj.call_finish(result) except Exception: etype, e = sys.exc_info()[:2] # return exception as value if error_callback: error_callback(obj, e, real_user_data) else: result_callback(obj, e, real_user_data) return ret = ret.unpack() # to be compatible with standard Python behaviour, unbox # single-element tuples and return None for empty result tuples if len(ret) == 1: ret = ret[0] elif len(ret) == 0: ret = None result_callback(obj, ret, real_user_data) def dbus_async_call(proxymethod, instance, *args, *kwargs): '''pydbus doesn't support asynchronous methods. This method adds support for it until pydbus implements it''' argdiff = len(args) - len(proxymethod._inargs) if argdiff < 0: raise TypeError(proxymethod.qualname + " missing {} required positional argument(s)".format(-argdiff)) elif argdiff > 0: raise TypeError(proxymethod.qualname + " takes {} positional argument(s) but {} was/were given".format(len(proxymethod._inargs), len(args))) timeout = kwargs.get("timeout", 30) 1000 user_data = (kwargs['result_handler'], kwargs.get('error_handler'), kwargs.get('user_data')) ret = instance._bus.con.call( instance._bus_name, instance._path, proxymethod._iface_name, proxymethod.name, GLib.Variant(proxymethod._sinargs, args), GLib.VariantType.new(proxymethod._soutargs), 0, timeout, None, async_result_handler, user_data) |

Then call it with something like this:

|dbus_async_call(nr.Scan, nr, timeout=30, result_handler=self.scan_result_cb, error_handler=self.raise_exn_cb, user_data=None) def raise_exn_cb(self, obj, e, user_data): pass def scan_result_cb(self, obj, result, user_data): self.register(result) |

It would be nice that pydbus could support this out of the box, by using something like dbus_object.foo_method(param0, ..., param N, timeout=XYZ, reply_handler=myfunction, error_handler=myotherfunction)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/LEW21/pydbus/issues/58, or mute the thread https://github.com/notifications/unsubscribe-auth/AFtFs9WOYjtkcDaWlV4EnuB2N2WIc7P-ks5r_Ug_gaJpZM4NrfWI.

-- Regards,

Harry G. Coin, Manager Quiet Fountain LLC

pespin commented 7 years ago

Hi, what's your current status and your target? Do you plan to merge those changes back in this repo?

Timeout being set at registration time means it's set once for the entire proxy object? imho it would be nice being able to set the timeout at least for each method, and possibly also each time we call the method.

hcoin commented 7 years ago

Thanks for this interest. Your answers (I hope) are here: https://github.com/hcoin/pydbus/blob/master/status.txt

hcoin commented 7 years ago

Much work done, only one major commit left. Plus install instructions known good for all major distros including publishing on pre 2.46 glibs. https://github.com/hcoin/pydbus I'm planning to have it be 'alpha release ready' 8/1/17. I will be looking for a few people with known pydbus experience to be committers along with me.

hcoin commented 7 years ago

Hopefully Linus too!