@@ -41,17 +41,20 @@ class HybridCore(object):
4141
4242 def __init__ (self , library_components ):
4343 self .keywords = {}
44+ self .keywords_spec = {}
4445 self .attributes = {}
4546 self .add_library_components (library_components )
4647 self .add_library_components ([self ])
4748
4849 def add_library_components (self , library_components ):
50+ self .keywords_spec ['__init__' ] = KeywordBuilder .build (self .__init__ )
4951 for component in library_components :
5052 for name , func in self .__get_members (component ):
5153 if callable (func ) and hasattr (func , 'robot_name' ):
5254 kw = getattr (component , name )
5355 kw_name = func .robot_name or name
5456 self .keywords [kw_name ] = kw
57+ self .keywords_spec [kw_name ] = KeywordBuilder .build (kw )
5558 # Expose keywords as attributes both using original
5659 # method names as well as possible custom names.
5760 self .attributes [name ] = self .attributes [kw_name ] = kw
@@ -98,37 +101,23 @@ def run_keyword(self, name, args, kwargs=None):
98101 return self .keywords [name ](* args , ** (kwargs or {}))
99102
100103 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
106106
107107 def get_keyword_tags (self , name ):
108108 return self .keywords [name ].robot_tags
109109
110110 def get_keyword_documentation (self , name ):
111111 if name == '__intro__' :
112112 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
117115
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
132121
133122 def __get_keyword (self , keyword_name ):
134123 if keyword_name == '__init__' :
@@ -140,22 +129,6 @@ def __get_keyword(self, keyword_name):
140129 raise ValueError ('Keyword "%s" not found.' % keyword_name )
141130 return method
142131
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-
159132 def get_keyword_source (self , keyword_name ):
160133 method = self .__get_keyword (keyword_name )
161134 path = self .__get_keyword_path (method )
@@ -185,91 +158,128 @@ def __get_keyword_path(self, method):
185158 return None
186159
187160
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 ):
233162
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+ )
238170
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
243183
244184 @classmethod
245- def from_function (cls , function ):
246- cls ._function = function
185+ def _get_arg_spec (cls , function ):
247186 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
260209
261210 @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 []
267215
268216 @classmethod
269- def _get_kw_args (cls , spec ):
217+ def _get_kwargs (cls , arg_spec ):
270218 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