Robot Framework
argumentparser.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 from inspect import isclass, signature, Parameter
17 from typing import get_type_hints
18 
19 from robot.errors import DataError
20 from robot.utils import is_string, split_from_equals
21 from robot.variables import is_assign, is_scalar_assign
22 
23 from .argumentspec import ArgumentSpec
24 
25 
27 
28  def __init__(self, type='Keyword', error_reporter=None):
29  self._type_type = type
30  self._error_reporter_error_reporter = error_reporter
31 
32  def parse(self, source, name=None):
33  raise NotImplementedError
34 
35  def _report_error(self, error):
36  if self._error_reporter_error_reporter:
37  self._error_reporter_error_reporter(error)
38  else:
39  raise DataError('Invalid argument specification: %s' % error)
40 
41 
43 
44  def parse(self, handler, name=None):
45  spec = ArgumentSpec(name, self._type_type)
46  self._set_args_set_args(spec, handler)
47  self._set_types_set_types(spec, handler)
48  return spec
49 
50  def _set_args(self, spec, handler):
51  try:
52  sig = signature(handler)
53  except ValueError: # Can occur w/ C functions (incl. many builtins).
54  spec.var_positional = 'args'
55  return
56  parameters = list(sig.parameters.values())
57  # `inspect.signature` drops `self` with bound methods and that's the case when
58  # inspecting keywords. `__init__` is got directly from class (i.e. isn't bound)
59  # so we need to handle that case ourselves.
60  # Partial objects do not have __name__ at least in Python =< 3.10.
61  if getattr(handler, '__name__', None) == '__init__':
62  parameters = parameters[1:]
63  setters = {
64  Parameter.POSITIONAL_ONLY: spec.positional_only.append,
65  Parameter.POSITIONAL_OR_KEYWORD: spec.positional_or_named.append,
66  Parameter.VAR_POSITIONAL: lambda name: setattr(spec, 'var_positional', name),
67  Parameter.KEYWORD_ONLY: spec.named_only.append,
68  Parameter.VAR_KEYWORD: lambda name: setattr(spec, 'var_named', name),
69  }
70  for param in parameters:
71  setters[param.kind](param.name)
72  if param.default is not param.empty:
73  spec.defaults[param.name] = param.default
74 
75  def _set_types(self, spec, handler):
76  # If types are set using the `@keyword` decorator, use them. Including when
77  # types are explicitly disabled with `@keyword(types=None)`. Otherwise read
78  # type hints.
79  if isclass(handler):
80  handler = handler.__init__
81  robot_types = getattr(handler, 'robot_types', ())
82  if robot_types or robot_types is None:
83  spec.types = robot_types
84  else:
85  spec.types = self._get_type_hints_get_type_hints(handler)
86 
87  def _get_type_hints(self, handler):
88  try:
89  return get_type_hints(handler)
90  except Exception: # Can raise pretty much anything
91  # Not all functions have `__annotations__`.
92  # https://github.com/robotframework/robotframework/issues/4059
93  return getattr(handler, '__annotations__', {})
94 
95 
97 
98  def parse(self, argspec, name=None):
99  spec = ArgumentSpec(name, self._type_type)
100  named_only = False
101  for arg in argspec:
102  arg = self._validate_arg_validate_arg(arg)
103  if spec.var_named:
104  self._report_error_report_error('Only last argument can be kwargs.')
105  elif isinstance(arg, tuple):
106  arg, default = arg
107  arg = self._add_arg_add_arg(spec, arg, named_only)
108  spec.defaults[arg] = default
109  elif self._is_kwargs_is_kwargs(arg):
110  spec.var_named = self._format_kwargs_format_kwargs(arg)
111  elif self._is_varargs_is_varargs(arg):
112  if named_only:
113  self._report_error_report_error('Cannot have multiple varargs.')
114  if not self._is_kw_only_separator_is_kw_only_separator(arg):
115  spec.var_positional = self._format_varargs_format_varargs(arg)
116  named_only = True
117  elif spec.defaults and not named_only:
118  self._report_error_report_error('Non-default argument after default arguments.')
119  else:
120  self._add_arg_add_arg(spec, arg, named_only)
121  return spec
122 
123  def _validate_arg(self, arg):
124  raise NotImplementedError
125 
126  def _is_kwargs(self, arg):
127  raise NotImplementedError
128 
129  def _format_kwargs(self, kwargs):
130  raise NotImplementedError
131 
132  def _is_kw_only_separator(self, arg):
133  raise NotImplementedError
134 
135  def _is_varargs(self, arg):
136  raise NotImplementedError
137 
138  def _format_varargs(self, varargs):
139  raise NotImplementedError
140 
141  def _format_arg(self, arg):
142  return arg
143 
144  def _add_arg(self, spec, arg, named_only=False):
145  arg = self._format_arg_format_arg(arg)
146  target = spec.positional_or_named if not named_only else spec.named_only
147  target.append(arg)
148  return arg
149 
150 
152 
153  def _validate_arg(self, arg):
154  if isinstance(arg, tuple):
155  if self._is_invalid_tuple_is_invalid_tuple(arg):
156  self._report_error_report_error('Invalid argument "%s".' % (arg,))
157  if len(arg) == 1:
158  return arg[0]
159  return arg
160  if '=' in arg:
161  return tuple(arg.split('=', 1))
162  return arg
163 
164  def _is_invalid_tuple(self, arg):
165  return (len(arg) > 2
166  or not is_string(arg[0])
167  or (arg[0].startswith('*') and len(arg) > 1))
168 
169  def _is_kwargs(self, arg):
170  return arg.startswith('**')
171 
172  def _format_kwargs(self, kwargs):
173  return kwargs[2:]
174 
175  def _is_varargs(self, arg):
176  return arg.startswith('*')
177 
178  def _is_kw_only_separator(self, arg):
179  return arg == '*'
180 
181  def _format_varargs(self, varargs):
182  return varargs[1:]
183 
184 
186 
187  def _validate_arg(self, arg):
188  arg, default = split_from_equals(arg)
189  if not (is_assign(arg) or arg == '@{}'):
190  self._report_error_report_error("Invalid argument syntax '%s'." % arg)
191  if default is None:
192  return arg
193  if not is_scalar_assign(arg):
194  typ = 'list' if arg[0] == '@' else 'dictionary'
195  self._report_error_report_error("Only normal arguments accept default values, "
196  "%s arguments like '%s' do not." % (typ, arg))
197  return arg, default
198 
199  def _is_kwargs(self, arg):
200  return arg and arg[0] == '&'
201 
202  def _format_kwargs(self, kwargs):
203  return kwargs[2:-1]
204 
205  def _is_varargs(self, arg):
206  return arg and arg[0] == '@'
207 
208  def _is_kw_only_separator(self, arg):
209  return arg == '@{}'
210 
211  def _format_varargs(self, varargs):
212  return varargs[2:-1]
213 
214  def _format_arg(self, arg):
215  return arg[2:-1]
def __init__(self, type='Keyword', error_reporter=None)
def _add_arg(self, spec, arg, named_only=False)
def split_from_equals(string)
Definition: escaping.py:105
def is_scalar_assign(string, allow_assign_mark=False)
Definition: search.py:55
def is_assign(string, identifiers='$@&', allow_assign_mark=False)
Definition: search.py:50