2025, Oct 31 08:32
Python में D‑Bus के साथ NetworkManager: a{sa{sv}} और GLib.Variant का सही उपयोग
Python से D‑Bus पर NetworkManager की AddAndActivateConnection में आने वाली TypeError का समाधान: a{sa{sv}} सिग्नेचर, GLib.Variant और SSID के ay का सही उपयोग
D-Bus के जरिए NetworkManager के साथ काम करना तब तक सरल रहता है, जब तक आप टाइप सिग्नेचर की दीवार से नहीं टकराते। आम अड़चनों में से एक है AddAndActivateConnection, जिसका पहला पैरामीटर a{sa{sv}} तय किया गया है। अगर आप अंदर स्ट्रिंग्स वाला साधारण Python dict भेजते हैं, तो GLib एक TypeError देता है कि उसे GLib.Variant चाहिए था, लेकिन str मिला। यहां समझिए कि असल में क्या हो रहा है और बिना अनुमान लगाए इसे कैसे ठीक करें।
समस्या दोहराना
नीचे दिया गया कोड एक Wi‑Fi डिवाइस ढूंढता है, कनेक्शन प्रोफ़ाइल बनाता है और उसे सक्रिय करने की कोशिश करता है। Python के नजरिए से संरचना ठीक दिखती है, लेकिन D‑Bus के टाइप नियमों के कारण यह विफल हो जाती है।
from pydbus import SystemBus
sys_bus = SystemBus()
nmgr = sys_bus.get("org.freedesktop.NetworkManager")
# Wi‑Fi डिवाइस ढूंढें (type 2)
wlan_objpath = None
for obj_path in nmgr.GetDevices():
    dev_obj = sys_bus.get("org.freedesktop.NetworkManager", obj_path)
    if dev_obj.DeviceType == 2:
        wlan_objpath = obj_path
        break
if not wlan_objpath:
    raise RuntimeError("No Wi-Fi device found.")
net_ssid = "MyWiFi"
net_pass = "mypassword123"
raw_settings = {
    'connection': {
        'id': net_ssid,
        'type': '802-11-wireless',
    },
    '802-11-wireless': {
        'ssid': net_ssid,
        'mode': 'infrastructure',
    },
    '802-11-wireless-security': {
        'key-mgmt': 'wpa-psk',
        'psk': net_pass,
    },
    'ipv4': {
        'method': 'auto',
    },
    'ipv6': {
        'method': 'ignore',
    }
}
nmgr.AddAndActivateConnection(raw_settings, wlan_objpath, "/")
रनटाइम पर यह GLib त्रुटि देता है। मुख्य पंक्ति अक्सर कुछ यूं दिखती है:
TypeError: argument value: Expected GLib.Variant, but got str
यह क्यों असफल होता है
सिग्नेचर a{sa{sv}} मायने रखता है। यह स्ट्रिंग से ऐसे मैप का निरूपण करता है जिसके मान variants होते हैं। बाहरी dict सेटिंग समूहों की डिक्शनरी है। प्रत्येक अंदरूनी dict सेटिंग कीज़ की डिक्शनरी है, और उनमें मौजूद मान variant टाइप के होने चाहिए। Python का dict टाइप जानकारी नहीं रखता, इसलिए D‑Bus सिग्नेचर पूरा करने के लिए हर मान को स्पष्ट रूप से GLib.Variant में लपेटना पड़ता है। भले ही हर मान स्ट्रिंग हो, साधारण a{sa{ss}} a{sa{sv}} के बराबर नहीं है, और जब pydbus GLib.Variant ट्री बनाता है, तो कॉल अस्वीकृत हो जाएगी।
पूरी settings dict को एक ही Variant में लपेट देना आकर्षक लग सकता है, लेकिन इससे mismatch नहीं सुलझता। ऐसा करने पर शीर्ष स्तर पर v बनता है, न कि a{sa{sv}}, और अंदर के मान अभी भी अपने-अपने टाइप वाले प्रति‑कुंजी variants नहीं बनते। इसके अलावा, variant के भीतर भी निहित संरचना D‑Bus टाइप्स में अभिव्यक्त होने योग्य होनी चाहिए, इसलिए यह शॉर्टकट लाभ नहीं देता।
एक और बारीकी अक्सर परेशान करती है: 802-11-wireless.ssid फ़ील्ड का टाइप ay (bytes की array) होना चाहिए, स्ट्रिंग नहीं। ssid के लिए स्ट्रिंग variant भेजने पर अलग त्रुटि मिलेगी। सही रूपांतरण UTF‑8 बाइट्स का bytearray है, जिसे Variant('ay', ...) में लपेटना चाहिए।
समाधान
हर अंदरूनी मान को उसके सही टाइप के साथ GLib.Variant में लपेटें। स्ट्रिंग फ़ील्ड के लिए 's' का उपयोग करें। SSID के लिए 'ay' के साथ UTF‑8 बाइट्स का bytearray दें। बाहरी और अंदरूनी dict सामान्य Python dict ही रहें, लेकिन सभी leaf मान variants होने चाहिए।
from pydbus import SystemBus
from gi.repository.GLib import Variant
sys_bus = SystemBus()
nmgr = sys_bus.get("org.freedesktop.NetworkManager")
# Wi‑Fi डिवाइस ढूंढें (type 2)
wlan_objpath = None
for obj_path in nmgr.GetDevices():
    dev_obj = sys_bus.get("org.freedesktop.NetworkManager", obj_path)
    if dev_obj.DeviceType == 2:
        wlan_objpath = obj_path
        break
if not wlan_objpath:
    raise RuntimeError("No Wi-Fi device found.")
net_ssid = "MyWiFi"
net_pass = "mypassword123"
fixed_settings = {
    "connection": {
        "id": Variant('s', net_ssid),
        "type": Variant('s', "802-11-wireless"),
    },
    "802-11-wireless": {
        "ssid": Variant('ay', bytearray(net_ssid, 'utf-8')),
        "mode": Variant('s', "infrastructure"),
    },
    "802-11-wireless-security": {
        "key-mgmt": Variant('s', "wpa-psk"),
        "psk": Variant('s', net_pass),
    },
    "ipv4": {"method": Variant('s', "auto")},
    "ipv6": {"method": Variant('s', "ignore")},
}
nmgr.AddAndActivateConnection(fixed_settings, wlan_objpath, "/")
पर्दे के पीछे वास्तव में क्या हो रहा है
जो dict D‑Bus पर ले जाए जाते हैं, वे सख्ती से typed होते हैं। इसी कारण APIs अक्सर विकल्प मैप्स को variant मानों के साथ घोषित करती हैं: variants वास्तविक टाइप लिए रहते हैं, जबकि बाहरी संरचना समान रहती है। GLib.Variant D‑Bus variant का सीधा प्रतिबिंब है, इसलिए वहीं वाले फ़ॉर्मैट स्ट्रिंग्स लागू होते हैं। इसी संदर्भ में, pydbus introspection सिग्नेचर के आधार पर GLib.Variant ट्री बनाता है और इसलिए चाहता है कि अंदरूनी मान पहले से variants हों। कुछ कॉल जो a{sv} पढ़ती हैं, वे आपको साधारण Python dict लौटा सकती हैं, लेकिन भेजने के लिए a{sv} बनाना अधिक कड़ा है और स्पष्ट wrapping मांगता है। यदि आप किसी getter से settings लेते हैं, फिर कोई फ़ील्ड बदलकर उन्हें वापस भेजते हैं, तो अपडेट कॉल से पहले बदले हुए leaves को दोबारा variants में लपेटना पड़ेगा; नहीं तो वही type error फिर मिल जाएगी।
यह समझना क्यों जरूरी है
जब आप प्रोग्रामेटिक रूप से NetworkManager settings तैयार करने लगते हैं, तो टाइप mismatch समय गंवाने का सबसे तेज़ तरीका है। यह जानना कि a{sa{sv}} का मतलब है “अंदरूनी मान variants होने चाहिए” आपको शुरू से डेटा सही आकार में ढालने देता है। यह पढ़ने और लिखने को मिलाते समय सूक्ष्म बग्स से भी बचाता है: getter आपको सामान्य Python संरचना दे सकता है, लेकिन setter उसी जगहों के लिए typed variants मांगता है।
मुख्य बातें
हमेशा introspection से मिले D‑Bus सिग्नेचर का पालन करें। AddAndActivateConnection के लिए बाहरी और अंदरूनी मैपिंग्स सामान्य dict हैं, लेकिन अंदरूनी dict में हर वास्तविक मान उचित टाइप का GLib.Variant होना चाहिए। पूरी settings मैप को एक ही Variant में न लपेटें; वह a{sa{sv}} से मेल नहीं खाएगा। सुनिश्चित करें कि 802-11-wireless.ssid Variant('ay', bytearray(...)) हो, स्ट्रिंग नहीं। और जब किसी दूसरी कॉल से ली गई settings में बदलाव करें, तो भेजने से पहले बदले हुए फ़ील्ड्स पर Variant wrappers फिर से लगाएं।