Source code for dasbus.specification

#
# Support for DBus XML specifications
#
# Copyright (C) 2019  Red Hat, Inc.  All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
# USA
#
# For more info about DBus specification see:
# https://dbus.freedesktop.org/doc/dbus-specification.html#introspection-format
#
from collections import namedtuple

from dasbus.xml import XMLParser

__all__ = [
    "DBusSpecificationError",
    "DBusSpecification",
    "DBusSpecificationParser"
]


[docs]class DBusSpecificationError(Exception): """Exception for the DBus specification errors.""" pass
[docs]class DBusSpecification(object): """DBus XML specification.""" DIRECTION_IN = "in" DIRECTION_OUT = "out" ACCESS_READ = "read" ACCESS_WRITE = "write" ACCESS_READWRITE = "readwrite" RETURN_PARAMETER = "return" STANDARD_INTERFACES = """ <node> <interface name="org.freedesktop.DBus.Introspectable"> <method name="Introspect"> <arg type="s" name="xml_data" direction="out"/> </method> </interface> <interface name="org.freedesktop.DBus.Peer"> <method name="Ping"/> <method name="GetMachineId"> <arg type="s" name="machine_uuid" direction="out"/> </method> </interface> <interface name="org.freedesktop.DBus.Properties"> <method name="Get"> <arg type="s" name="interface_name" direction="in"/> <arg type="s" name="property_name" direction="in"/> <arg type="v" name="value" direction="out"/> </method> <method name="GetAll"> <arg type="s" name="interface_name" direction="in"/> <arg type="a{sv}" name="properties" direction="out"/> </method> <method name="Set"> <arg type="s" name="interface_name" direction="in"/> <arg type="s" name="property_name" direction="in"/> <arg type="v" name="value" direction="in"/> </method> <signal name="PropertiesChanged"> <arg type="s" name="interface_name"/> <arg type="a{sv}" name="changed_properties"/> <arg type="as" name="invalidated_properties"/> </signal> </interface> </node> """ # Representation of specification members. Signal = namedtuple("Signal", [ "name", "interface_name", "type" ]) Method = namedtuple("Method", [ "name", "interface_name", "in_type", "out_type" ]) Property = namedtuple("Property", [ "name", "interface_name", "readable", "writable", "type" ]) # Specification data holders. __slots__ = ["_members"]
[docs] @classmethod def from_xml(cls, xml): """Return a DBus specification for the given XML.""" return DBusSpecificationParser.parse_specification(xml, cls)
def __init__(self): """Create a new DBus specification.""" self._members = {} @property def interfaces(self): """Interfaces of the DBus specification.""" return list(dict(self._members.keys()).keys()) @property def members(self): """Members of the DBus specification.""" return list(self._members.values())
[docs] def add_member(self, member): """Add a member of a DBus interface.""" self._members[(member.interface_name, member.name)] = member
[docs] def get_member(self, interface_name, member_name): """Get a member of a DBus interface.""" try: return self._members[(interface_name, member_name)] except KeyError: pass raise DBusSpecificationError( "DBus specification has no member '{}.{}'.".format( interface_name, member_name ) )
[docs]class DBusSpecificationParser(object): """Class for parsing DBus XML specification.""" # The XML parser. xml_parser = XMLParser
[docs] @classmethod def parse_specification(cls, xml, factory=DBusSpecification): """Generate a representation of a DBus XML specification. :param xml: the XML specification to parse :param factory: the DBus specification factory :return: a representation od the DBus specification """ specification = factory() cls._parse_xml(specification, DBusSpecification.STANDARD_INTERFACES) cls._parse_xml(specification, xml) return specification
@classmethod def _parse_xml(cls, specification, xml): """Parse the given XML.""" node = cls.xml_parser.xml_to_element(xml) # Iterate over interfaces. for interface_element in node: if not cls.xml_parser.is_interface(interface_element): continue # Parse the interface. cls._parse_interface(specification, interface_element) @classmethod def _parse_interface(cls, specification, interface_element): """Parse the interface element from the DBus specification.""" interface_name = cls.xml_parser.get_name(interface_element) # Iterate over members. for member_element in interface_element: if cls.xml_parser.is_property(member_element): member = cls._parse_property(interface_name, member_element) elif cls.xml_parser.is_signal(member_element): member = cls._parse_signal(interface_name, member_element) elif cls.xml_parser.is_method(member_element): member = cls._parse_method(interface_name, member_element) else: continue # Add the member specification to the mapping. specification.add_member(member) return interface_name @classmethod def _parse_property(cls, interface_name, property_element): """Parse the property element from the DBus specification.""" property_name = cls.xml_parser.get_name(property_element) property_type = cls.xml_parser.get_type(property_element) property_access = cls.xml_parser.get_access(property_element) readable = property_access in ( DBusSpecification.ACCESS_READ, DBusSpecification.ACCESS_READWRITE ) writable = property_access in ( DBusSpecification.ACCESS_WRITE, DBusSpecification.ACCESS_READWRITE ) return DBusSpecification.Property( name=property_name, interface_name=interface_name, readable=readable, writable=writable, type=property_type ) @classmethod def _parse_signal(cls, interface_name, signal_element): """Parse the signal element from the DBus specification.""" signal_name = cls.xml_parser.get_name(signal_element) signal_type = [] for element in signal_element: if not cls.xml_parser.is_parameter(element): continue element_type = cls.xml_parser.get_type(element) signal_type.append(element_type) return DBusSpecification.Signal( name=signal_name, interface_name=interface_name, type=cls._get_type(signal_type) ) @classmethod def _parse_method(cls, interface_name, method_element): """Parse the method element from the DBus specification.""" method_name = cls.xml_parser.get_name(method_element) in_types = [] out_types = [] for element in method_element: if not cls.xml_parser.is_parameter(element): continue direction = cls.xml_parser.get_direction(element) element_type = cls.xml_parser.get_type(element) if direction == DBusSpecification.DIRECTION_IN: in_types.append(element_type) elif direction == DBusSpecification.DIRECTION_OUT: out_types.append(element_type) return DBusSpecification.Method( name=method_name, interface_name=interface_name, in_type=cls._get_type(in_types), out_type=cls._get_type(out_types) ) @classmethod def _get_type(cls, types): """Join types into one value.""" if not types: return None return "({})".format("".join(types))