@@ -41,17 +41,20 @@ class HybridCore(object):
41
41
42
42
def __init__ (self , library_components ):
43
43
self .keywords = {}
44
+ self .keywords_spec = {}
44
45
self .attributes = {}
45
46
self .add_library_components (library_components )
46
47
self .add_library_components ([self ])
47
48
48
49
def add_library_components (self , library_components ):
50
+ self .keywords_spec ['__init__' ] = KeywordBuilder .build (self .__init__ )
49
51
for component in library_components :
50
52
for name , func in self .__get_members (component ):
51
53
if callable (func ) and hasattr (func , 'robot_name' ):
52
54
kw = getattr (component , name )
53
55
kw_name = func .robot_name or name
54
56
self .keywords [kw_name ] = kw
57
+ self .keywords_spec [kw_name ] = KeywordBuilder .build (kw )
55
58
# Expose keywords as attributes both using original
56
59
# method names as well as possible custom names.
57
60
self .attributes [name ] = self .attributes [kw_name ] = kw
@@ -98,37 +101,23 @@ def run_keyword(self, name, args, kwargs=None):
98
101
return self .keywords [name ](* args , ** (kwargs or {}))
99
102
100
103
def get_keyword_arguments (self , name ):
101
- kw_method = self .__get_keyword (name )
102
- if kw_method is None :
103
- return None
104
- spec = ArgumentSpec .from_function (kw_method )
105
- return spec .get_arguments ()
104
+ spec = self .keywords_spec .get (name )
105
+ return spec .argument_specification
106
106
107
107
def get_keyword_tags (self , name ):
108
108
return self .keywords [name ].robot_tags
109
109
110
110
def get_keyword_documentation (self , name ):
111
111
if name == '__intro__' :
112
112
return inspect .getdoc (self ) or ''
113
- if name == '__init__' :
114
- return inspect .getdoc (self .__init__ ) or ''
115
- kw = self .keywords [name ]
116
- return inspect .getdoc (kw ) or ''
113
+ spec = self .keywords_spec .get (name )
114
+ return spec .documentation
117
115
118
- def get_keyword_types (self , keyword_name ):
119
- method = self .__get_keyword (keyword_name )
120
- if method is None :
121
- return method
122
- types = getattr (method , 'robot_types' , ())
123
- if types is None :
124
- return types
125
- if not types :
126
- types = self .__get_typing_hints (method )
127
- if RF31 :
128
- types = self .__join_defaults_with_types (method , types )
129
- else :
130
- types .pop ('return' , None )
131
- return types
116
+ def get_keyword_types (self , name ):
117
+ spec = self .keywords_spec .get (name )
118
+ if spec is None :
119
+ raise ValueError ('Keyword "%s" not found.' % name )
120
+ return spec .argument_types
132
121
133
122
def __get_keyword (self , keyword_name ):
134
123
if keyword_name == '__init__' :
@@ -140,22 +129,6 @@ def __get_keyword(self, keyword_name):
140
129
raise ValueError ('Keyword "%s" not found.' % keyword_name )
141
130
return method
142
131
143
- def __get_typing_hints (self , method ):
144
- if PY2 :
145
- return {}
146
- spec = ArgumentSpec .from_function (method )
147
- return spec .get_typing_hints ()
148
-
149
- def __join_defaults_with_types (self , method , types ):
150
- spec = ArgumentSpec .from_function (method )
151
- for name , value in spec .defaults :
152
- if name not in types and isinstance (value , (bool , type (None ))):
153
- types [name ] = type (value )
154
- for name , value in spec .kwonlydefaults :
155
- if name not in types and isinstance (value , (bool , type (None ))):
156
- types [name ] = type (value )
157
- return types
158
-
159
132
def get_keyword_source (self , keyword_name ):
160
133
method = self .__get_keyword (keyword_name )
161
134
path = self .__get_keyword_path (method )
@@ -185,91 +158,128 @@ def __get_keyword_path(self, method):
185
158
return None
186
159
187
160
188
- class ArgumentSpec (object ):
189
-
190
- _function = None
191
-
192
- def __init__ (self , positional = None , defaults = None , varargs = None , kwonlyargs = None ,
193
- kwonlydefaults = None , kwargs = None ):
194
- self .positional = positional or []
195
- self .defaults = defaults or []
196
- self .varargs = varargs
197
- self .kwonlyargs = kwonlyargs or []
198
- self .kwonlydefaults = kwonlydefaults or []
199
- self .kwargs = kwargs
200
-
201
- def __contains__ (self , item ):
202
- if item in self .positional :
203
- return True
204
- if self .varargs and item in self .varargs :
205
- return True
206
- if item in self .kwonlyargs :
207
- return True
208
- if self .kwargs and item in self .kwargs :
209
- return True
210
- return False
211
-
212
- def get_arguments (self ):
213
- args = self ._format_positional (self .positional , self .defaults )
214
- args += self ._format_default (self .defaults )
215
- if self .varargs :
216
- args .append ('*%s' % self .varargs )
217
- args += self ._format_positional (self .kwonlyargs , self .kwonlydefaults )
218
- args += self ._format_default (self .kwonlydefaults )
219
- if self .kwargs :
220
- args .append ('**%s' % self .kwargs )
221
- return args
222
-
223
- def get_typing_hints (self ):
224
- try :
225
- hints = typing .get_type_hints (self ._function )
226
- except Exception :
227
- hints = self ._function .__annotations__
228
- for arg in list (hints ):
229
- # remove return and self statements
230
- if arg not in self :
231
- hints .pop (arg )
232
- return hints
161
+ class KeywordBuilder (object ):
233
162
234
- def _format_positional (self , positional , defaults ):
235
- for argument , _ in defaults :
236
- positional .remove (argument )
237
- return positional
163
+ @classmethod
164
+ def build (cls , function ):
165
+ return KeywordSpecification (
166
+ argument_specification = cls ._get_arguments (function ),
167
+ documentation = inspect .getdoc (function ) or '' ,
168
+ argument_types = cls ._get_types (function )
169
+ )
238
170
239
- def _format_default (self , defaults ):
240
- if not RF31 :
241
- return [default for default in defaults ]
242
- return ['%s=%s' % (argument , default ) for argument , default in defaults ]
171
+ @classmethod
172
+ def _get_arguments (cls , function ):
173
+ arg_spec = cls ._get_arg_spec (function )
174
+ argument_specification = cls ._get_default_and_named_args (
175
+ arg_spec , function
176
+ )
177
+ argument_specification .extend (cls ._get_var_args (arg_spec ))
178
+ kw_only_args = cls ._get_kw_only (arg_spec )
179
+ if kw_only_args :
180
+ argument_specification .extend (kw_only_args )
181
+ argument_specification .extend (cls ._get_kwargs (arg_spec ))
182
+ return argument_specification
243
183
244
184
@classmethod
245
- def from_function (cls , function ):
246
- cls ._function = function
185
+ def _get_arg_spec (cls , function ):
247
186
if PY2 :
248
- spec = inspect .getargspec (function )
249
- else :
250
- spec = inspect .getfullargspec (function )
251
- args = spec .args [1 :] if inspect .ismethod (function ) else spec .args # drop self
252
- defaults = cls ._get_defaults (spec )
253
- kwonlyargs , kwonlydefaults , kwargs = cls ._get_kw_args (spec )
254
- return cls (positional = args ,
255
- defaults = defaults ,
256
- varargs = spec .varargs ,
257
- kwonlyargs = kwonlyargs ,
258
- kwonlydefaults = kwonlydefaults ,
259
- kwargs = kwargs )
187
+ return inspect .getargspec (function )
188
+ return inspect .getfullargspec (function )
189
+
190
+ @classmethod
191
+ def _get_default_and_named_args (cls , arg_spec , function ):
192
+ args = cls ._drop_self_from_args (function , arg_spec )
193
+ args .reverse ()
194
+ defaults = list (arg_spec .defaults ) if arg_spec .defaults else []
195
+ formated_args = []
196
+ for arg in args :
197
+ if defaults :
198
+ formated_args .append (
199
+ cls ._format_defaults (arg , defaults .pop ())
200
+ )
201
+ else :
202
+ formated_args .append (arg )
203
+ formated_args .reverse ()
204
+ return formated_args
205
+
206
+ @classmethod
207
+ def _drop_self_from_args (cls , function , arg_spec ):
208
+ return arg_spec .args [1 :] if inspect .ismethod (function ) else arg_spec .args
260
209
261
210
@classmethod
262
- def _get_defaults (cls , spec ):
263
- if not spec .defaults :
264
- return []
265
- names = spec .args [- len (spec .defaults ):]
266
- return list (zip (names , spec .defaults ))
211
+ def _get_var_args (cls , arg_spec ):
212
+ if arg_spec .varargs :
213
+ return ['*%s' % arg_spec .varargs ]
214
+ return []
267
215
268
216
@classmethod
269
- def _get_kw_args (cls , spec ):
217
+ def _get_kwargs (cls , arg_spec ):
270
218
if PY2 :
271
- return [], [], spec .keywords
272
- kwonlyargs = spec .kwonlyargs or []
273
- defaults = spec .kwonlydefaults or {}
274
- kwonlydefaults = [(arg , name ) for arg , name in defaults .items ()]
275
- return kwonlyargs , kwonlydefaults , spec .varkw
219
+ return ['**%s' % arg_spec .keywords ] if arg_spec .keywords else []
220
+ return ['**%s' % arg_spec .varkw ] if arg_spec .varkw else []
221
+
222
+ @classmethod
223
+ def _get_kw_only (cls , arg_spec ):
224
+ kw_only_args = []
225
+ if PY2 :
226
+ return kw_only_args
227
+ for arg in arg_spec .kwonlyargs :
228
+ if not arg_spec .kwonlydefaults or arg not in arg_spec .kwonlydefaults :
229
+ kw_only_args .append (arg )
230
+ else :
231
+ value = arg_spec .kwonlydefaults .get (arg , '' )
232
+ kw_only_args .append (cls ._format_defaults (arg , value ))
233
+ return kw_only_args
234
+
235
+ @classmethod
236
+ def _format_defaults (cls , arg , value ):
237
+ if RF31 :
238
+ return '%s=%s' % (arg , value )
239
+ return arg , value
240
+
241
+ @classmethod
242
+ def _get_types (cls , function ):
243
+ if function is None :
244
+ return function
245
+ types = getattr (function , 'robot_types' , ())
246
+ if types is None or types :
247
+ return types
248
+ if not types :
249
+ types = cls ._get_typing_hints (function )
250
+ return types
251
+
252
+ @classmethod
253
+ def _get_typing_hints (cls , function ):
254
+ if PY2 :
255
+ return {}
256
+ try :
257
+ hints = typing .get_type_hints (function )
258
+ except Exception :
259
+ hints = function .__annotations__
260
+ all_args = cls ._args_as_list (function )
261
+ for arg_with_hint in list (hints ):
262
+ # remove return and self statements
263
+ if arg_with_hint not in all_args :
264
+ hints .pop (arg_with_hint )
265
+ return hints
266
+
267
+ @classmethod
268
+ def _args_as_list (cls , function ):
269
+ arg_spec = cls ._get_arg_spec (function )
270
+ function_args = []
271
+ function_args .extend (cls ._drop_self_from_args (function , arg_spec ))
272
+ if arg_spec .varargs :
273
+ function_args .append (arg_spec .varargs )
274
+ function_args .extend (arg_spec .kwonlyargs or [])
275
+ if arg_spec .varkw :
276
+ function_args .append (arg_spec .varkw )
277
+ return function_args
278
+
279
+
280
+ class KeywordSpecification (object ):
281
+
282
+ def __init__ (self , argument_specification = None , documentation = None , argument_types = None ):
283
+ self .argument_specification = argument_specification
284
+ self .documentation = documentation
285
+ self .argument_types = argument_types
0 commit comments