def _iterate_in_parameters(cls, member, signature): """Iterate over input parameters.""" # Get type hints for parameters. direction = DBusSpecification.DIRECTION_IN type_hints = get_type_hints(member) # Iterate over method parameters, skip cls. for name in list(signature.parameters)[1:]: # Check the kind of the parameter kind = signature.parameters[name].kind # Ignore **kwargs and all arguments after * and *args # if the method supports additional arguments. if kind in (Parameter.VAR_KEYWORD, Parameter.KEYWORD_ONLY) \ and are_additional_arguments_supported(member): continue if kind != Parameter.POSITIONAL_OR_KEYWORD: raise DBusSpecificationError( "Only positional or keyword arguments are allowed." ) # Check if the type is defined. if name not in type_hints: raise DBusSpecificationError( "Undefined type of parameter '{}'.".format(name) ) yield name, type_hints[name], direction
def _generate_signal(cls, member, member_name): """Generate signal defined by a class member. :param member: a dbus_signal object. :param member_name: a name of the signal :return: a signal element raises DBusSpecificationError: if signal has defined return type """ element = cls.xml_generator.create_signal(member_name) method = member.definition if not method: return element for name, type_hint, direction in cls._iterate_parameters(method): # Only input parameters can be defined. if direction == DBusSpecification.DIRECTION_OUT: raise DBusSpecificationError( "Signal {} has defined return type.".format(member_name)) # All parameters are exported as output parameters # (see specification). direction = DBusSpecification.DIRECTION_OUT parameter = cls.xml_generator.create_parameter( name, get_dbus_type(type_hint), direction) cls.xml_generator.add_child(element, parameter) return element
def register_object(cls, connection, object_path, object_xml, callback, callback_args=()): """Register an object on DBus.""" node_info = Gio.DBusNodeInfo.new_for_xml( object_xml ) method_call_closure = partial( cls._object_callback, user_data=(callback, callback_args) ) registrations = [] if not node_info.interfaces: raise DBusSpecificationError( "No interfaces for registration." ) for interface_info in node_info.interfaces: registration_id = connection.register_object( object_path, interface_info, method_call_closure, None, None ) registrations.append(registration_id) return partial( cls._unregister_object, connection, registrations )
def _generate_property(cls, member, member_name): """Generate DBus property defined by class member. :param member: a property object :param member_name: a property name :return: a property element raises DBusSpecificationError: if the property is invalid """ access = None type_hint = None try: # Process the setter. if member.fset: [(_, type_hint, _)] = cls._iterate_parameters(member.fset) access = DBusSpecification.ACCESS_WRITE # Process the getter. if member.fget: [(_, type_hint, _)] = cls._iterate_parameters(member.fget) access = DBusSpecification.ACCESS_READ except ValueError: raise DBusSpecificationError( "Undefined type of DBus property '{}'.".format(member_name) ) from None # Property has both. if member.fget and member.fset: access = DBusSpecification.ACCESS_READWRITE if access is None: raise DBusSpecificationError( "DBus property '{}' is not accessible.".format(member_name) ) return cls.xml_generator.create_property( member_name, get_dbus_type(type_hint), access )
def _iterate_parameters(cls, member): """Iterate over method parameters. For every parameter returns its name, a type hint and a direction. :param member: a method object :return: an iterator raises DBusSpecificationError: if parameters are invalid """ # Get type hints for parameters. type_hints = get_type_hints(member) # Get method signature. signature = inspect.signature(member) # Iterate over method parameters, skip cls. for name in list(signature.parameters)[1:]: # Check the kind of the parameter kind = signature.parameters[name].kind if kind != Parameter.POSITIONAL_OR_KEYWORD: raise DBusSpecificationError( "Only positional or keyword arguments are allowed.") # Check if the type is defined. if name not in type_hints: raise DBusSpecificationError( "Parameter {} doesn't have defined type.".format(name)) yield name, type_hints[name], DBusSpecification.DIRECTION_IN # Is the return type defined? if signature.return_annotation is signature.empty: return # Is the return type other than None? if signature.return_annotation is None: return yield (DBusSpecification.RETURN_PARAMETER, signature.return_annotation, DBusSpecification.DIRECTION_OUT)
def _iterate_out_parameters(cls, member, signature): """Iterate over output parameters.""" name = DBusSpecification.RETURN_PARAMETER direction = DBusSpecification.DIRECTION_OUT type_hint = signature.return_annotation # Is the return type defined? if type_hint is signature.empty: return # Is the return type other than None? if type_hint is None: return # Generate multiple output arguments if requested. if getattr(member, RETURNS_MULTIPLE_ARGUMENTS_ATTRIBUTE, False): # The return type has to be a tuple. if not is_base_type(type_hint, Tuple): raise DBusSpecificationError( "Expected a tuple of multiple arguments." ) # The return type has to contain multiple arguments. type_args = get_type_arguments(type_hint) if len(type_args) < 2: raise DBusSpecificationError( "Expected a tuple of more than one argument." ) # Iterate over types in the tuple for i, type_arg in enumerate(type_args): yield "{}_{}".format(name, i), type_arg, direction return # Otherwise, return only one output argument. yield name, type_hint, direction
def get_xml(obj): """Return XML specification of an object. :param obj: an object decorated with @dbus_interface or @dbus_class :return: a string with XML specification """ xml_specification = getattr(obj, DBUS_XML_ATTRIBUTE, None) if xml_specification is None: raise DBusSpecificationError( "XML specification is not defined at '{}'.".format( DBUS_XML_ATTRIBUTE)) return xml_specification
def _find_properties_specs(self, obj): """Find specifications of DBus properties. :param obj: an object with DBus properties :return: a map of property names and their specifications """ specification = DBusSpecification.from_xml(get_xml(obj)) properties_specs = {} for member in specification.members: if not isinstance(member, DBusSpecification.Property): continue if member.name in properties_specs: raise DBusSpecificationError( "DBus property '{}' is defined in more than " "one interface.".format(member.name)) properties_specs[member.name] = member return properties_specs
def _generate_interface(cls, interface_cls, interfaces, interface_name): """Generate interface defined by given class. :param interface_cls: a class object that defines the interface :param interfaces: a dictionary of implemented interfaces :param interface_name: a name of the new interface :return: a new interface element :raises DBusSpecificationError: if a class member cannot be exported """ interface = cls.xml_generator.create_interface(interface_name) # Search class members. for member_name, member in inspect.getmembers(interface_cls): # Check it the name is exportable. if not cls._is_exportable(member_name): continue # Skip names already defined in implemented interfaces. if cls._is_defined(interfaces, member_name): continue # Generate XML element for exportable member. if cls._is_signal(member): element = cls._generate_signal(member, member_name) elif cls._is_property(member): element = cls._generate_property(member, member_name) elif cls._is_method(member): element = cls._generate_method(member, member_name) else: raise DBusSpecificationError( "Unsupported definition of DBus member '{}'.".format( member_name ) ) # Add generated element to the interface. cls.xml_generator.add_child(interface, element) return interface