altdesktop / python-dbus-next

🚌 The next great DBus library for Python with asyncio support
https://python-dbus-next.readthedocs.io/en/latest/
MIT License
191 stars 60 forks source link

Confusion with callback arguments #99

Open destockwell opened 2 years ago

destockwell commented 2 years ago

Trying to create a Signal callback: the docs say the callback should have TWO args: The first is for a DBus Exception. If None, all is OK The second is for the signal resulting values, supposedly a list of Any. (second arg should be result list(Any) or something like that.

Perhaps a silly question, but if my callback is contained within an object should it not have self as the first argument?

Alo, when I look at the mpris.py example and I find the following:

listen to signals

def on_properties_changed(interface_name, changed_properties, invalidated_properties):
    for changed, variant in changed_properties.items():
        print(f'property changed: {changed} - {variant.value}')

properties.on_properties_changed(on_properties_changed)

So what is it really?

acrisci commented 2 years ago

I don't think I can answer this question unless you are more specific with the problem you are having. If you think the documentation for the library is lacking, let me know what specific section is confusing you. If you want support, come to the chat room.

destockwell commented 2 years ago

Thanks for your response. Regarding Method callbacks the docs say:

To asynchronously call a method, provide a callback that takes an error as the first argument and a list as the second argument. If the call completed successfully, error will be None. If the service returns an error, it will be a DBusError with information about the error returned from the bus. The result will be a list of values that correspond to the out args of the introspection method definition.

So this would indicate that the (Method) callback would look like: def get_something_cb(error: Exception, result: list(Any):

What I was having a problem with was my Signal callback. The docs say:

DBus signals are exposed with an event-callback interface. The provided callback will be called when the signal is emitted with arguments that correspond to the out args of the interface signal definition.

So, when creating a handler for the Properties interface's PropertiesChanged signal, the signal callback should llook like: def on_properties_changed(interface_name, changed_properties, invalidated_properties): And not (e.g.) def on_properties_changed(error: Exception, result: list(Any)): (which would align with the mpris.py example)

Also if the callback function was contained within a Class, the first parameter would become self, as in: def on_properties_changed(self, interface_name, changed_properties, invalidated_properties): Or must the callback be created outside any Class? Thanks in advance for clarity. I actually enjoy working with dbus-next, especially the way properties are handled.

destockwell commented 2 years ago

Example of one of the callbacks I was working on...unsuccessfully: def player_props_cb(self, error: Exception, result: list(Any)): if Error != None: print(f"{__name__} Error: {error}") return print(f"{__name__} result:\n{result}") if 'Status' in result: self.status = result['Status'] print(f"{__name__} New Status: {self.status}") if 'Position' in result: self.position = result['Position'] print(f"{__name__} New Position:{self.position}") if 'Track' in result: self.track = result['track'] print(f"{__name__} New Track:\n{self.track}")

acrisci commented 2 years ago

I think you are confusing the high-level interface and the low-level interface. The mpris.py example is working code that actually runs and that's the correct way to use it.

https://python-dbus-next.readthedocs.io/en/latest/high-level-client/index.html

I have no idea where that other documentation comes from.

destockwell commented 2 years ago

The other documentation that I quoted was from https://python-dbus-next.readthedocs.io/en/latest/high-level-client/glib-proxy-interface.html

Right after the discussion of the calling a method:

To listen to a signal use this form:

interface.on_SIGNAL To stop listening to a signal use this form:

interface.off_SIGNAL Where SIGNAL is the name of the signal converted to snake case.

DBus signals are exposed with an event-callback interface. The provided callback will be called when the signal is emitted with arguments that correspond to the out args of the interface signal definition.

An example would be helpful,,,

destockwell commented 2 years ago

Closely related issue, this time with method callback, which is for StartDiscovery method on the Adapter object (bluez). This method returns nothing upon completion, but may return an Exception.

ERROR:root:got unexpected error processing a message: discover_player_cb() takes 2 positional arguments but 3 were given. Traceback (most recent call last): File "/usr/local/lib/python3.7/dist-packages/dbus_next/message_bus.py", line 615, in _on_message self._process_message(msg) File "/usr/local/lib/python3.7/dist-packages/dbus_next/message_bus.py", line 713, in _process_message handler(msg, None) File "/usr/local/lib/python3.7/dist-packages/dbus_next/message_bus.py", line 580, in reply_notify callback(reply, err) File "/usr/local/lib/python3.7/dist-packages/dbus_next/glib/proxy_object.py", line 136, in call_notify callback(msg.body, err) TypeError: discover_player_cb() takes 2 positional arguments but 3 were given

In the latest iteration, the call is: adapter_iface.call_start_discovery(self.discover_player_cb) [self. because the callback is defined within the class]

The callback function is: def discover_player_cb(error: Exception, result: Any): if error != None: print(f"{__name__} Error in Device Discovery: {error}") return print(f"{__name__} Complete.")

Note that I MUST have a result parameter, even though the method will return nothing, Also, the result parameter cannot be typed as list(Any) (per docs), because: TypeError: typing.Any is not subscriptable

Any hints would be greatly appreciated...

destockwell commented 2 years ago

Looks like the callback is NOT compatible with an in-class callback.

Moving the callback function to the mainline resolves the number-of-arguments issue.

Typing the "result" parameter as list(Any) still does not work (not subscriptable).