Skip to content

Commit ced27fc

Browse files
committed
Split argument parsing logic to separate Py2 and Py3 classes.
This eases migrating to inspect.signature with Py3 code to be able to support positional-only arguments (robotframework#3695). Less conditional code simplifies the logic many other ways as well.
1 parent 336df0d commit ced27fc

File tree

3 files changed

+160
-97
lines changed

3 files changed

+160
-97
lines changed

src/robot/running/arguments/argumentparser.py

Lines changed: 4 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,16 @@
1414
# limitations under the License.
1515

1616
from robot.errors import DataError
17-
from robot.utils import (JYTHON, PY_VERSION, PY2, is_string, split_from_equals,
18-
unwrap)
17+
from robot.utils import JYTHON, PY2, is_string, split_from_equals
1918
from robot.variables import is_assign
2019

2120
from .argumentspec import ArgumentSpec
2221

22+
# Move PythonArgumentParser to this module when Python 2 support is dropped.
2323
if PY2:
24-
from inspect import getargspec, ismethod
25-
26-
def getfullargspec(func):
27-
return getargspec(func) + ([], None, {})
28-
else:
29-
from inspect import getfullargspec, ismethod
30-
31-
if PY_VERSION >= (3, 5):
32-
import typing
24+
from .py2argumentparser import PythonArgumentParser
3325
else:
34-
typing = None
26+
from .py3argumentparser import PythonArgumentParser
3527

3628
if JYTHON:
3729
from java.lang import Class
@@ -47,91 +39,6 @@ def parse(self, source, name=None):
4739
raise NotImplementedError
4840

4941

50-
class PythonArgumentParser(_ArgumentParser):
51-
52-
def parse(self, handler, name=None):
53-
args, varargs, kws, defaults, kwo, kwo_defaults, annotations \
54-
= self._get_arg_spec(handler)
55-
if ismethod(handler) or handler.__name__ == '__init__':
56-
args = args[1:] # Drop 'self'.
57-
spec = ArgumentSpec(
58-
name,
59-
self._type,
60-
positional=args,
61-
varargs=varargs,
62-
kwargs=kws,
63-
kwonlyargs=kwo,
64-
defaults=self._get_defaults(args, defaults, kwo_defaults)
65-
)
66-
spec.types = self._get_types(handler, annotations, spec)
67-
return spec
68-
69-
def _get_arg_spec(self, handler):
70-
handler = unwrap(handler)
71-
try:
72-
return getfullargspec(handler)
73-
except TypeError: # Can occur w/ C functions (incl. many builtins).
74-
return [], 'args', None, None, [], None, {}
75-
76-
def _get_defaults(self, args, default_values, kwo_defaults):
77-
if default_values:
78-
defaults = dict(zip(args[-len(default_values):], default_values))
79-
else:
80-
defaults = {}
81-
if kwo_defaults:
82-
defaults.update(kwo_defaults)
83-
return defaults
84-
85-
def _get_types(self, handler, annotations, spec):
86-
types = getattr(handler, 'robot_types', ())
87-
if types is None:
88-
return None
89-
if types:
90-
return types
91-
return self._get_type_hints(handler, annotations, spec)
92-
93-
def _get_type_hints(self, handler, annotations, spec):
94-
if not typing:
95-
return annotations
96-
try:
97-
type_hints = typing.get_type_hints(handler)
98-
except Exception: # Can raise pretty much anything
99-
return annotations
100-
self._remove_mismatching_type_hints(type_hints, spec.argument_names)
101-
self._remove_optional_none_type_hints(type_hints, spec.defaults)
102-
return type_hints
103-
104-
def _remove_mismatching_type_hints(self, type_hints, argument_names):
105-
# typing.get_type_hints returns info from the original function even
106-
# if it is decorated. Argument names are got from the wrapping
107-
# decorator and thus there is a mismatch that needs to be resolved.
108-
mismatch = set(type_hints) - set(argument_names)
109-
for name in mismatch:
110-
type_hints.pop(name)
111-
112-
def _remove_optional_none_type_hints(self, type_hints, defaults):
113-
# If argument has None as a default, typing.get_type_hints adds
114-
# optional None to the information it returns. We don't want that.
115-
for arg in defaults:
116-
if defaults[arg] is None and arg in type_hints:
117-
type_ = type_hints[arg]
118-
if self._is_union(type_):
119-
try:
120-
types = type_.__args__
121-
except AttributeError:
122-
# Python 3.5.2's typing uses __union_params__ instead
123-
# of __args__. This block can likely be safely removed
124-
# when Python 3.5 support is dropped
125-
types = type_.__union_params__
126-
if len(types) == 2 and types[1] is type(None):
127-
type_hints[arg] = types[0]
128-
129-
def _is_union(self, type_):
130-
if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
131-
type_ = type_.__origin__
132-
return isinstance(type_, type(typing.Union))
133-
134-
13542
class JavaArgumentParser(_ArgumentParser):
13643

13744
def parse(self, signatures, name=None):
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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 getargspec, ismethod
17+
18+
from .argumentspec import ArgumentSpec
19+
20+
21+
class PythonArgumentParser(object):
22+
23+
def __init__(self, type='Keyword'):
24+
self._type = type
25+
26+
def parse(self, handler, name=None):
27+
try:
28+
args, varargs, kws, defaults = getargspec(handler)
29+
except TypeError: # Can occur w/ C functions (incl. many builtins).
30+
args, varargs, kws, defaults = [], 'args', None, None
31+
if ismethod(handler) or handler.__name__ == '__init__':
32+
args = args[1:] # Drop 'self'.
33+
spec = ArgumentSpec(
34+
name,
35+
self._type,
36+
positional=args,
37+
varargs=varargs,
38+
kwargs=kws,
39+
defaults=self._get_defaults(args, defaults),
40+
types=getattr(handler, 'robot_types', ())
41+
)
42+
return spec
43+
44+
def _get_defaults(self, args, default_values):
45+
if not default_values:
46+
return {}
47+
return dict(zip(args[-len(default_values):], default_values))
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
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 getfullargspec, ismethod, unwrap
17+
import typing
18+
19+
from robot.utils import PY_VERSION
20+
21+
from .argumentspec import ArgumentSpec
22+
23+
24+
class PythonArgumentParser:
25+
26+
def __init__(self, type='Keyword'):
27+
self._type = type
28+
29+
def parse(self, handler, name=None):
30+
args, varargs, kws, defaults, kwo, kwo_defaults, annotations \
31+
= self._get_arg_spec(handler)
32+
if ismethod(handler) or handler.__name__ == '__init__':
33+
args = args[1:] # Drop 'self'.
34+
spec = ArgumentSpec(
35+
name,
36+
self._type,
37+
positional=args,
38+
varargs=varargs,
39+
kwargs=kws,
40+
kwonlyargs=kwo,
41+
defaults=self._get_defaults(args, defaults, kwo_defaults)
42+
)
43+
spec.types = self._get_types(handler, annotations, spec)
44+
return spec
45+
46+
def _get_arg_spec(self, handler):
47+
handler = unwrap(handler)
48+
try:
49+
if handler.__name__ == 'po':
50+
print(getfullargspec(handler))
51+
return getfullargspec(handler)
52+
except TypeError: # Can occur w/ C functions (incl. many builtins).
53+
return [], 'args', None, None, [], None, {}
54+
55+
def _get_defaults(self, args, default_values, kwo_defaults):
56+
if default_values:
57+
defaults = dict(zip(args[-len(default_values):], default_values))
58+
else:
59+
defaults = {}
60+
if kwo_defaults:
61+
defaults.update(kwo_defaults)
62+
return defaults
63+
64+
def _get_types(self, handler, annotations, spec):
65+
types = getattr(handler, 'robot_types', ())
66+
if types is None:
67+
return None
68+
if types:
69+
return types
70+
return self._get_type_hints(handler, annotations, spec)
71+
72+
def _get_type_hints(self, handler, annotations, spec):
73+
try:
74+
type_hints = typing.get_type_hints(handler)
75+
except Exception: # Can raise pretty much anything
76+
return annotations
77+
self._remove_mismatching_type_hints(type_hints, spec.argument_names)
78+
self._remove_optional_none_type_hints(type_hints, spec.defaults)
79+
return type_hints
80+
81+
def _remove_mismatching_type_hints(self, type_hints, argument_names):
82+
# typing.get_type_hints returns info from the original function even
83+
# if it is decorated. Argument names are got from the wrapping
84+
# decorator and thus there is a mismatch that needs to be resolved.
85+
mismatch = set(type_hints) - set(argument_names)
86+
for name in mismatch:
87+
type_hints.pop(name)
88+
89+
def _remove_optional_none_type_hints(self, type_hints, defaults):
90+
# If argument has None as a default, typing.get_type_hints adds
91+
# optional None to the information it returns. We don't want that.
92+
for arg in defaults:
93+
if defaults[arg] is None and arg in type_hints:
94+
type_ = type_hints[arg]
95+
if self._is_union(type_):
96+
try:
97+
types = type_.__args__
98+
except AttributeError:
99+
# Python 3.5.2's typing uses __union_params__ instead
100+
# of __args__. This block can likely be safely removed
101+
# when Python 3.5 support is dropped
102+
types = type_.__union_params__
103+
if len(types) == 2 and types[1] is type(None):
104+
type_hints[arg] = types[0]
105+
106+
def _is_union(self, type_):
107+
if PY_VERSION >= (3, 7) and hasattr(type_, '__origin__'):
108+
type_ = type_.__origin__
109+
return isinstance(type_, type(typing.Union))

0 commit comments

Comments
 (0)