pythondbusnetworkmanager

Issues with converthing a python dictionary to a GLib Variant


I'm trying to use the NetworkManager DBus method AddAndActivateConnection with the pydbus library to connect to a Wi-Fi network. But it's giving me a lot of trouble in generating the proper GLib Variant based on the format specifier: a{sa{sv}}

from pydbus import SystemBus

bus = SystemBus()
nm = bus.get("org.freedesktop.NetworkManager")

# Find Wi-Fi device
wifi_path = None
for dev_path in nm.GetDevices():
    dev = bus.get("org.freedesktop.NetworkManager", dev_path)
    if dev.DeviceType == 2:
        wifi_path = dev_path
        break

if not wifi_path:
    raise RuntimeError("No Wi-Fi device found.")

ssid = "MyWiFi"
password = "mypassword123"

settings = {
    'connection': {
        'id': ssid,
        'type': '802-11-wireless',
    },
    '802-11-wireless': {
        'ssid': ssid,
        'mode': 'infrastructure',
    },
    '802-11-wireless-security': {
        'key-mgmt': 'wpa-psk',
        'psk': password,
    },
    'ipv4': {
        'method': 'auto',
    },
    'ipv6': {
        'method': 'ignore',
    }
}

nm.AddAndActivateConnection(settings, wifi_path, "/")

This results in this error in GLib:

Traceback (most recent call last):
  File "/root/connect3.py", line 43, in <module>
    nm.AddAndActivateConnection(settings, wifi_path, "/")
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/pydbus/proxy_method.py", line 74, in __call__
    self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs),
                                     ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 189, in __new__
    v = creator._create(format_string, value)
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 150, in _create
    builder.add_value(self._create(dup, i))
                      ~~~~~~~~~~~~^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 145, in _create
    builder.add_value(self._create(element_type, i))
                      ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 150, in _create
    builder.add_value(self._create(dup, i))
                      ~~~~~~~~~~~~^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 145, in _create
    builder.add_value(self._create(element_type, i))
                      ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 150, in _create
    builder.add_value(self._create(dup, i))
                      ~~~~~~~~~~~~^^^^^^^^
  File "/usr/lib/python3/dist-packages/gi/overrides/GLib.py", line 118, in _create
    return self._LEAF_CONSTRUCTORS[format](value)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^
TypeError: argument value: Expected GLib.Variant, but got str

I've tried using GLib.Variant() to pre package the settings dictionary but I just cannot find a working combination. I've tried debugging it with pdb/pudb but I can't quite grasp what is happing in the library.

I'm just lost at this point, I've been trying everything I can think of but just can't get it to work.

Can someone explain what I am doing wrong?

System info: arch: aarch64 OS: Debian 13 (trixie) Python version: 3.13.5

Solved! final code:

settings = {
        "connection": {
            "id": Variant('s', ssid),
            "type": Variant('s', "802-11-wireless"),
        },
        "802-11-wireless": {
            "ssid": Variant('ay', bytearray(ssid, 'utf-8')),
            "mode": Variant('s', "infrastructure"),
        },
        "802-11-wireless-security": {
            "key-mgmt": Variant('s', "wpa-psk"),
            "psk": Variant('s', password),
        },
        "ipv4": {"method": Variant('s', "auto")},
        "ipv6": {"method": Variant('s', "ignore")},
    }
    nm.AddAndActivateConnection(settings, wifi_path, "/")

if ssid is a string variant it will give another weird error


Solution

  • Introspection data defines this parameter as a{sa{sv}}, which means the parameter itself is not a variant (quite the opposite). Instead, values of the inner dict are of 'variant' type.

    Unlike Python or JSON, dicts in D-Bus are strongly typed – you can't have one value be a 'string' and another 'int64' – so it is common for APIs which take an "options dict" parameter to declare that dict as holding 'variant' values. Each variant value still carries the type of its contents, but that type remains invisible until the variant is unwrapped.

    (Though your dict with all string values happens to meet the restriction, but nevertheless would still be the wrong type a{sa{ss}} and the method call would still fail, if the library were to convert it blindly to D-Bus types. But here pydbus is working "backwards" from a known-expected type so it's able to raise the exception at an earlier stage.)

    So the correct input would be:

    settings = {                                            # a{
        'connection': {                                     #   s: a{
            'id': Variant('s', ssid),                       #     s: v
            'type': Variant('s', '802-11-wireless'),        #     s: v
        },                                                  #   }
    }                                                       # }
    

    (GLib has a separate type system from that of D-Bus, but due to their shared history, GLib.Variant is still a near-equivalent of the D-Bus variant type with only minor extensions. So it generally accepts the same syntax for the 'type' parameter.)

    Packaging the entire dict in a variant wouldn't avoid this issue. Generally the contents of a variant have the exact same type restrictions – if some structure couldn't be represented in D-Bus types directly, then it cannot be represented inside a variant either. Second, even though your dict with all string values happens to fit the requirements (i.e. is representable as a{sa{ss}}), wrapping it in a Variant turns the whole thing into v – which is again not the same thing as a{sa{sv}}.