1212ismethod = inspect .ismethod if sys .version_info [0 ] == 2 else inspect .isfunction
1313
1414
15- def verify_arguments (function ):
16- """Verify that the function is correctly defined."""
15+ def extract_membered_types (function , require_strong_typing , arg_count ):
16+ has_arg_types = hasattr (function , "arg_types" )
17+ if has_arg_types :
18+ arg_types = function .arg_types
19+ elif require_strong_typing :
20+ raise ValueError (
21+ "No argument types defined for method "
22+ "'{}'" .format (function .__name__ ))
23+ else :
24+ arg_types = (None , ) * arg_count
25+
26+ if hasattr (function , "ret_type" ):
27+ if not has_arg_types :
28+ raise ValueError (
29+ "Only explicit return type defined but no explicit "
30+ "argument types for method '{}'" .format (function .__name__ ))
31+ ret_type = function .ret_type
32+ elif has_arg_types :
33+ raise ValueError (
34+ "Only explicit argument types defined but no explicit return "
35+ "for method '{}'" .format (function .__name__ ))
36+ else :
37+ ret_type = None
38+
39+ return arg_types , ret_type
40+
41+
42+ def verify_arguments_getargspec (function , require_strong_typing ):
43+ """Verify arguments using the getargspec function."""
1744 args , vargs , kwargs , defaults = inspect .getargspec (function )
45+ args = args [1 :]
46+ arg_types , ret_type = extract_membered_types (
47+ function , require_strong_typing , len (args ))
48+
1849 if defaults is not None :
1950 raise ValueError ("dbus methods do not allow default values" )
2051 # TODO: Check if vargs or kwargs are actually allowed in dbus.
2152 if vargs is not None :
2253 raise ValueError ("dbus methods do not allow variable argument functions" )
2354 if kwargs is not None :
2455 raise ValueError ("dbus methods do not allow variable keyword arguments" )
25- return args
56+ return args , arg_types , ret_type
57+
58+
59+ def verify_arguments_signature (function , require_strong_typing ):
60+ """Verify arguments using the Signature class in Python 3."""
61+ signature = inspect .signature (function )
62+ parameters = signature .parameters [1 :]
63+ if not all (param .default is param .empty for param in parameters ):
64+ raise ValueError (
65+ "Default values are not allowed for method "
66+ "'{}'" .format (function .__name__ ))
67+ if not all (param .kind == param .POSITIONAL_OR_KEYWORD
68+ for param in parameters ):
69+ raise ValueError (
70+ "Variable arguments or keyword only arguments are not allowed for "
71+ "method '{}'" .format (function .__name__ ))
72+
73+ names = [p .name for p in parameters ]
74+ arg_types = [param .annotation for param in parameters ]
75+ empty_arg = arg_types .count (inspect .Parameter .empty )
76+ if 0 < empty_arg < len (arg_types ):
77+ raise ValueError (
78+ "Only partially defined types for method "
79+ "'{}'" .format (function .__name__ ))
80+ elif arg_types and empty_arg > 0 and hasattr (function , "arg_types" ):
81+ raise ValueError (
82+ "Annotations and explicit argument types are used together in "
83+ "method '{}'" .format (function .__name__ ))
84+
85+ ret_type = signature .return_annotation
86+ if (ret_type is not signature .empty and
87+ hasattr (function , "ret_type" )):
88+ raise ValueError (
89+ "Annotations and explicit return type are used together in "
90+ "method '{}'" .format (function .__name__ ))
91+
92+ # Fall back to the explicit types only if there were no annotations, but
93+ # that might be actually valid if the function returns nothing and has
94+ # no parameters.
95+ # So it also checks that the function has any parameter or it has either of
96+ # the two attributes defined.
97+ # So it won't actually raise an error if a function has no parameter and
98+ # no annotations and no explicit types defined, because it is not possible
99+ # to determine if a function returns something.
100+ if (ret_type is signature .empty and empty_arg == len (arg_types ) and
101+ (len (arg_types ) > 0 or hasattr (function , "arg_types" ) or
102+ hasattr (function , "ret_type" ))):
103+ arg_types , ret_type = extract_membered_types (
104+ function , require_strong_typing , len (arg_types ))
105+
106+ return names , arg_types , ret_type
107+
108+
109+ def verify_arguments (function , require_strong_typing = False ):
110+ """Verify that the function is correctly defined."""
111+ if sys .version_info [0 ] == 2 :
112+ verify_func = verify_arguments_getargspec
113+ else :
114+ verify_func = verify_arguments_signature
115+
116+ names , arg_types , ret_type = verify_func (function , require_strong_typing )
117+ if len (arg_types ) != len (names ):
118+ raise ValueError (
119+ "Number of argument types ({}) differs from the number of "
120+ "parameters ({}) in function {}" .format (
121+ len (arg_types ), len (names ), function .__name__ ))
26122
123+ arg_types = dict (zip (names , arg_types ))
27124
28- def generate_introspection_xml (cls ):
125+ return arg_types , ret_type
126+
127+
128+ def generate_introspection_xml (cls , require_strong_typing = False ):
29129 """Generate introspection XML for the given class."""
30130 def get_interface (entry ):
31131 """Get the interface XML element for the given member."""
@@ -58,6 +158,26 @@ def valid_member(member):
58158 if isinstance (value , property ):
59159 entry = ElementTree .SubElement (
60160 get_interface (value .fget ), "property" )
161+ if sys .version_info [0 ] == 3 :
162+ signature = inspect .signature (value .fget )
163+ prop_type = signature .return_annotation
164+ if prop_type is signature .empty :
165+ prop_type = None
166+ elif hasattr (function , "prop_type" ):
167+ raise ValueError (
168+ "Annotations and explicit return type are used "
169+ "together in method '{}'" .format (function .__name__ ))
170+ else :
171+ prop_type = None
172+ if prop_type is None and hasattr (value .fget , "prop_type" ):
173+ prop_type = value .fget .prop_type
174+
175+ if prop_type is not None :
176+ attributes ["type" ] = prop_type
177+ elif require_strong_typing :
178+ raise ValueError (
179+ "No type defined for property '{}'" .format (name ))
180+
61181 if value .fset is None :
62182 attributes ["access" ] = "read"
63183 else :
@@ -67,21 +187,39 @@ def valid_member(member):
67187 {"name" : PROPERTY_EMITS_SIGNAL , "value" : "true" })
68188 attributes ["access" ] = "readwrite"
69189 elif ismethod (value ):
70- args = verify_arguments (value )
190+ arg_types , ret_type = verify_arguments (value )
71191 entry = ElementTree .SubElement (get_interface (value ), "method" )
72192 # Ignore the first parameter (which is self and not public)
73- for arg in args [ 1 :] :
193+ for arg , arg_type in arg_types . items () :
74194 attrib = {"name" : arg , "direction" : "in" }
195+ if arg_type is not None :
196+ attrib ["type" ] = arg_type
75197 ElementTree .SubElement (entry , "arg" , attrib )
198+ if ret_type is not None :
199+ ElementTree .SubElement (
200+ entry , "arg" ,
201+ {"name" : "return" , "direction" : "out" , "type" : ret_type })
76202
77203 entry .attrib = attributes
78204 return ElementTree .tostring (root )
79205
80206
81207def attach_introspection_xml (cls ):
82- """Generate and add introspection data to the class and return it."""
83- cls .dbus = generate_introspection_xml (cls )
84- return cls
208+ """
209+ Generate and add introspection data to the class and return it.
210+
211+ If used as a decorator without a parameter it won't require strong typing.
212+ If the parameter is True or False, it'll require it depending ot it.
213+ """
214+ def decorate (cls ):
215+ cls .dbus = generate_introspection_xml (cls , require_strong_typing )
216+ return cls
217+ if cls is True or cls is False :
218+ require_strong_typing = cls
219+ return decorate
220+ else :
221+ require_strong_typing = False
222+ return decorate (cls )
85223
86224
87225def signalled (prop ):
0 commit comments