# Copyright 2008-2015 Nokia Networks
# Copyright 2016- Robot Framework Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from robot.errors import DataError
from robot.utils import (JYTHON, PY_VERSION, PY2, is_string, split_from_equals,
unwrap)
from robot.variables import is_assign
from .argumentspec import ArgumentSpec
if PY2:
from inspect import getargspec, ismethod
[docs] def getfullargspec(func):
return getargspec(func) + ([], None, {})
else:
from inspect import getfullargspec, ismethod
if PY_VERSION >= (3, 5):
import typing
else:
typing = None
if JYTHON:
from java.lang import Class
from java.util import List, Map
class _ArgumentParser(object):
def __init__(self, type='Keyword'):
self._type = type
def parse(self, source, name=None):
raise NotImplementedError
[docs]class PythonArgumentParser(_ArgumentParser):
[docs] def parse(self, handler, name=None):
args, varargs, kws, defaults, kwo, kwo_defaults, annotations \
= self._get_arg_spec(handler)
if ismethod(handler) or handler.__name__ == '__init__':
args = args[1:] # Drop 'self'.
spec = ArgumentSpec(
name,
self._type,
positional=args,
varargs=varargs,
kwargs=kws,
kwonlyargs=kwo,
defaults=self._get_defaults(args, defaults, kwo_defaults)
)
spec.types = self._get_types(handler, annotations, spec)
return spec
def _get_arg_spec(self, handler):
handler = unwrap(handler)
try:
return getfullargspec(handler)
except TypeError: # Can occur w/ C functions (incl. many builtins).
return [], 'args', None, None, [], None, {}
def _get_defaults(self, args, default_values, kwo_defaults):
if default_values:
defaults = dict(zip(args[-len(default_values):], default_values))
else:
defaults = {}
if kwo_defaults:
defaults.update(kwo_defaults)
return defaults
def _get_types(self, handler, annotations, spec):
types = getattr(handler, 'robot_types', ())
if types is None:
return None
if types:
return types
return self._get_type_hints(handler, annotations, spec)
def _get_type_hints(self, handler, annotations, spec):
if not typing:
return annotations
try:
type_hints = typing.get_type_hints(handler)
except Exception: # Can raise pretty much anything
return annotations
self._remove_mismatching_type_hints(type_hints, spec.argument_names)
self._remove_optional_none_type_hints(type_hints, spec.defaults)
return type_hints
def _remove_mismatching_type_hints(self, type_hints, argument_names):
# typing.get_type_hints returns info from the original function even
# if it is decorated. Argument names are got from the wrapping
# decorator and thus there is a mismatch that needs to be resolved.
mismatch = set(type_hints) - set(argument_names)
for name in mismatch:
type_hints.pop(name)
def _remove_optional_none_type_hints(self, type_hints, defaults):
# If argument has None as a default, typing.get_type_hints adds
# optional None to the information it returns. We don't want that.
for arg in defaults:
if defaults[arg] is None and arg in type_hints:
type_ = type_hints[arg]
if self._is_union(type_):
try:
types = type_.__args__
except AttributeError:
# Python 3.5.2's typing uses __union_params__ instead
# of __args__. This block can likely be safely removed
# when Python 3.5 support is dropped
types = type_.__union_params__
if len(types) == 2 and types[1] is type(None):
type_hints[arg] = types[0]
def _is_union(self, type_):
if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
type_ = type_.__origin__
return isinstance(type_, type(typing.Union))
[docs]class JavaArgumentParser(_ArgumentParser):
[docs] def parse(self, signatures, name=None):
if not signatures:
return self._no_signatures_arg_spec(name)
elif len(signatures) == 1:
return self._single_signature_arg_spec(signatures[0], name)
else:
return self._multi_signature_arg_spec(signatures, name)
def _no_signatures_arg_spec(self, name):
# Happens when a class has no public constructors
return self._format_arg_spec(name)
def _single_signature_arg_spec(self, signature, name):
varargs, kwargs = self._get_varargs_and_kwargs_support(signature.args)
positional = len(signature.args) - int(varargs) - int(kwargs)
return self._format_arg_spec(name, positional, varargs=varargs,
kwargs=kwargs)
def _get_varargs_and_kwargs_support(self, args):
if not args:
return False, False
if self._is_varargs_type(args[-1]):
return True, False
if not self._is_kwargs_type(args[-1]):
return False, False
if len(args) > 1 and self._is_varargs_type(args[-2]):
return True, True
return False, True
def _is_varargs_type(self, arg):
return arg is List or isinstance(arg, Class) and arg.isArray()
def _is_kwargs_type(self, arg):
return arg is Map
def _multi_signature_arg_spec(self, signatures, name):
mina = maxa = len(signatures[0].args)
for sig in signatures[1:]:
argc = len(sig.args)
mina = min(argc, mina)
maxa = max(argc, maxa)
return self._format_arg_spec(name, maxa, maxa-mina)
def _format_arg_spec(self, name, positional=0, defaults=0, varargs=False,
kwargs=False):
positional = ['arg%d' % (i+1) for i in range(positional)]
if defaults:
defaults = {name: '' for name in positional[-defaults:]}
else:
defaults = {}
return ArgumentSpec(name, self._type,
positional=positional,
varargs='varargs' if varargs else None,
kwargs='kwargs' if kwargs else None,
defaults=defaults,
supports_named=False)
class _ArgumentSpecParser(_ArgumentParser):
def parse(self, argspec, name=None):
spec = ArgumentSpec(name, self._type)
kw_only_args = False
for arg in argspec:
arg = self._validate_arg(arg)
if spec.kwargs:
self._raise_invalid_spec('Only last argument can be kwargs.')
elif isinstance(arg, tuple):
arg, default = arg
arg = self._add_arg(spec, arg, kw_only_args)
spec.defaults[arg] = default
elif self._is_kwargs(arg):
spec.kwargs = self._format_kwargs(arg)
elif self._is_varargs(arg):
if kw_only_args:
self._raise_invalid_spec('Cannot have multiple varargs.')
if not self._is_kw_only_separator(arg):
spec.varargs = self._format_varargs(arg)
kw_only_args = True
elif spec.defaults and not kw_only_args:
self._raise_invalid_spec('Non-default argument after default '
'arguments.')
else:
self._add_arg(spec, arg, kw_only_args)
return spec
def _validate_arg(self, arg):
raise NotImplementedError
def _raise_invalid_spec(self, error):
raise DataError('Invalid argument specification: %s' % error)
def _is_kwargs(self, arg):
raise NotImplementedError
def _format_kwargs(self, kwargs):
raise NotImplementedError
def _is_kw_only_separator(self, arg):
raise NotImplementedError
def _is_varargs(self, arg):
raise NotImplementedError
def _format_varargs(self, varargs):
raise NotImplementedError
def _format_arg(self, arg):
return arg
def _add_arg(self, spec, arg, kw_only_arg=False):
arg = self._format_arg(arg)
target = spec.positional if not kw_only_arg else spec.kwonlyargs
target.append(arg)
return arg
[docs]class DynamicArgumentParser(_ArgumentSpecParser):
def _validate_arg(self, arg):
if isinstance(arg, tuple):
if self._is_invalid_tuple(arg):
self._raise_invalid_spec('Invalid argument "%s".' % (arg,))
if len(arg) == 1:
return arg[0]
return arg
if '=' in arg:
return tuple(arg.split('=', 1))
return arg
def _is_invalid_tuple(self, arg):
return (len(arg) > 2
or not is_string(arg[0])
or (arg[0].startswith('*') and len(arg) > 1))
def _is_kwargs(self, arg):
return arg.startswith('**')
def _format_kwargs(self, kwargs):
return kwargs[2:]
def _is_varargs(self, arg):
return arg.startswith('*')
def _is_kw_only_separator(self, arg):
return arg == '*'
def _format_varargs(self, varargs):
return varargs[1:]
[docs]class UserKeywordArgumentParser(_ArgumentSpecParser):
def _validate_arg(self, arg):
arg, default = split_from_equals(arg)
if not (is_assign(arg) or arg == '@{}'):
self._raise_invalid_spec("Invalid argument syntax '%s'." % arg)
if default is not None:
return arg, default
return arg
def _is_kwargs(self, arg):
return arg[0] == '&'
def _format_kwargs(self, kwargs):
return kwargs[2:-1]
def _is_varargs(self, arg):
return arg[0] == '@'
def _is_kw_only_separator(self, arg):
return arg == '@{}'
def _format_varargs(self, varargs):
return varargs[2:-1]
def _format_arg(self, arg):
return arg[2:-1]