Skip to content

Draft: `Gio.DBusTemplate`: A convenient abstraction to turn Python objects into D-Bus objects

Marco Köpcke requested to merge theCapypara/pygobject:gdbustemplate into master

This adds a new override Gio.DBusTemplate. Classes can be decorated with this to turn Python classes into D-Bus objects with a syntax that is very familiar to users already using Gtk.Template and GObject.Object.Property (those were the inspirations for this). The usage is also very similiar to what I've seen in Vala and users coming from dbus-python will also find this familiar but much more powerful.

The interface supports defining method, signal and property handlers for D-Bus objects. The Properties and Introspectable interfaces are also automatically implemented (thanks to Gio.DBusConnection.register_object) and PropertiesChanged is automatically emitted but can also be manually emitted if needed.

Example Usage

from gi.repository import Gio, GLib

from gdbus_ext import DBusTemplate

EXAMPLE_XML = """
<node>
    <interface name='org.example.Example'>
        <method name='MethodNoArgs'/>
        <method name='MethodWithArg'>
            <arg direction='in' name='Argument' type='s'/>
            <arg direction='out' name='ReturnValue' type='b'/>
        </method>
        <property name='ExamplePropertyReadOnly' type='b' access='read' />
        <property name='ExamplePropertyReadWrite' type='b' access='readwrite' />
    </interface>
    <interface name='org.example.AnotherExample'>
        <property name='ExamplePropertyReadWrite' type='b' access='readwrite' />
        <signal name='SomethingHappened'>
            <arg name='Events' type='as'/>
        </signal>
    </interface>
</node>
"""


@DBusTemplate(string=EXAMPLE_XML)
class Example:
    def connect_with_dbus(self, connection: Gio.DBusConnection):
        DBusTemplate.register_object(
            connection,
            "org.example.Example",
            "/org/example/Example",
            self,
        )

    @DBusTemplate.Method()
    def method_no_args(self):
        ...

    @DBusTemplate.Method()
    def method_with_arg(self, argument: str):
        return argument == "example"

    @DBusTemplate.Property()
    def example_property_read_only(self):
        return True

    # In this example, this property is defined in multiple interfaces, so its interface name must be provided.
    @DBusTemplate.Property(interface="org.example.Example")
    def example_property_read_write(self):
        return self._prop

    @example_property_read_write.setter
    def example_property_read_write(self, value):
        self._prop = value

    @DBusTemplate.Property(
        interface="org.example.AnotherExample", name="ExamplePropertyReadWrite"
    )
    def example_property_read_write2(self):
        return not self._prop

    @example_property_read_write2.setter
    def example_property_read_write2(self, value):
        self._prop = not value

    # Calling this method will emit the signal.
    @DBusTemplate.Signal()
    def something_happened(self, events: list[str]):
        ...

    def some_external_event(self):
        # Let's say some external event changed the internal read-only property
        # org.example.Example.ExamplePropertyReadOnly.
        # A property change signal can then be manually emitted. Note that this does not need to be done, if using
        # property setters.
        DBusTemplate.properties_changed(
            self, "org.example.Example", ("ExamplePropertyReadOnly",)
        )


if __name__ == "__main__":
    loop = GLib.MainLoop()
    connection = Gio.bus_get_sync(Gio.BusType.SESSION, None)
    obj = Example()
    obj.connect_with_dbus(connection)
    loop.run()

Proof of Concept

I created an example implementation here: https://github.com/theCapypara/gio-dbustemplate-demo

This is a demo Gtk "music player" that implements MPRIS2 and uses Gio.DBusTemplate.

Disclaimer

I have personally not used D-Bus much. I have read into this topic to write these abstractions, since I'm currently contributing to Workbench and saw that writing D-Bus objects in Python is currently pretty tedious, so I wrote this. I used Gtk.Template and GNOME Music's abstraction as a base and built upon that and used the "Proof of Concept" to make sure the implementation and API are actually sound.

Type Hints

Since PyGObject supports Python 3.8 and all versions support the typing module, I have kept the type hints in that I used during development. I recognize these are quite unusual for this project, so if you want I can remove or tone them down a bit.

Draft Status

This is still a draft because tests and docs are pending. I would like some feedback on the implementation and API first before diving into those. But I definitely plan to add both tests & docs.

Edited by Marco Köpcke

Merge request reports