Skip to content

Commit 96eed36

Browse files
authored
Support for Enum conversion
Remove Union['x', None] from typing hints Instead just return 'x' as hint Testing Enum conversion
1 parent e7e7e74 commit 96eed36

File tree

5 files changed

+71
-9
lines changed

5 files changed

+71
-9
lines changed

atest/DynamicTypesAnnotationsLibrary.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from typing import List, Union, NewType, Dict
1+
from enum import Enum
2+
from typing import List, Union, NewType, Optional
23

34
from robot.api import logger
45

56
from robotlibcore import DynamicCore, keyword
67

78
UserId = NewType('UserId', int)
89

10+
penum = Enum("penum", "ok")
11+
912

1013
class CustomObject(object):
1114

@@ -117,3 +120,9 @@ def keyword_self_and_keyword_only_types(x: 'DynamicTypesAnnotationsLibrary', man
117120
**kwargs: int):
118121
return (f'{mandatory}: {type(mandatory)}, {varargs}: {type(varargs)}, '
119122
f'{other}: {type(other)}, {kwargs}: {type(kwargs)}')
123+
124+
@keyword
125+
def enum_conversion(self, param: Optional[penum] = None):
126+
logger.info(f'OK {param}')
127+
logger.info(param.ok)
128+
return f'OK {param}'

atest/moc_library_py3.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from typing import Optional
2+
3+
14
class MockLibraryPy3:
25

36
def named_only(self, *varargs, key1, key2):
@@ -11,3 +14,6 @@ def args_with_type_hints(self, arg1, arg2, arg3: str, arg4: None) -> bool:
1114

1215
def self_and_keyword_only_types(x: 'MockLibraryPy3', mandatory, *varargs: int, other: bool, **kwargs: int):
1316
pass
17+
18+
def optional_none(self, xxx, arg1: Optional[str] = None, arg2: Optional[str] = None, arg3=False):
19+
pass

atest/tests_types.robot

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ Varargs and KeywordArgs With Typing Hints
6363
Should Match ${return}
6464
... this_is_mandatory: <class 'str'>, (1, 2, 3, 4): <class 'tuple'>, True: <class 'bool'>, {'key1': 1, 'key2': 2}: <class 'dict'>
6565

66+
Enum Conversion Should Work
67+
[Tags] py3
68+
${value} = Enum Conversion ok
69+
Should Match OK penum.ok ${value}
70+
71+
Enum Conversion To Invalid Value Should Fail
72+
[Tags] py3
73+
Run Keyword And Expect Error ValueError: Argument 'param' got value 'not ok' that*
74+
... Enum Conversion not ok
75+
76+
6677
*** Keywords ***
6778
Import DynamicTypesAnnotationsLibrary In Python 3 Only
6879
${py3} = DynamicTypesLibrary.Is Python 3

src/robotlibcore.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import os
2424
import sys
2525

26+
from robot.utils import PY_VERSION
27+
2628
try:
2729
import typing
2830
except ImportError:
@@ -245,9 +247,7 @@ def _get_types(cls, function):
245247
types = getattr(function, 'robot_types', ())
246248
if types is None or types:
247249
return types
248-
if not types:
249-
types = cls._get_typing_hints(function)
250-
return types
250+
return cls._get_typing_hints(function)
251251

252252
@classmethod
253253
def _get_typing_hints(cls, function):
@@ -257,16 +257,17 @@ def _get_typing_hints(cls, function):
257257
hints = typing.get_type_hints(function)
258258
except Exception:
259259
hints = function.__annotations__
260-
all_args = cls._args_as_list(function)
260+
arg_spec = cls._get_arg_spec(function)
261+
all_args = cls._args_as_list(function, arg_spec)
261262
for arg_with_hint in list(hints):
262263
# remove return and self statements
263264
if arg_with_hint not in all_args:
264265
hints.pop(arg_with_hint)
265-
return hints
266+
default = cls._get_defaults(arg_spec)
267+
return cls._remove_optional_none_type_hints(hints, default)
266268

267269
@classmethod
268-
def _args_as_list(cls, function):
269-
arg_spec = cls._get_arg_spec(function)
270+
def _args_as_list(cls, function, arg_spec):
270271
function_args = []
271272
function_args.extend(cls._drop_self_from_args(function, arg_spec))
272273
if arg_spec.varargs:
@@ -276,6 +277,34 @@ def _args_as_list(cls, function):
276277
function_args.append(arg_spec.varkw)
277278
return function_args
278279

280+
# Copied from: robot.running.arguments.argumentparser
281+
@classmethod
282+
def _remove_optional_none_type_hints(cls, type_hints, defaults):
283+
# If argument has None as a default, typing.get_type_hints adds
284+
# optional None to the information it returns. We don't want that.
285+
for arg, default in defaults:
286+
if default is None and arg in type_hints:
287+
type_ = type_hints[arg]
288+
if cls._is_union(type_):
289+
types = type_.__args__
290+
if len(types) == 2 and types[1] is type(None): # noqa
291+
type_hints[arg] = types[0]
292+
return type_hints
293+
294+
# Copied from: robot.running.arguments.argumentparser
295+
@classmethod
296+
def _is_union(cls, typing_type):
297+
if PY_VERSION >= (3, 7) and hasattr(typing_type, '__origin__'):
298+
typing_type = typing_type.__origin__
299+
return isinstance(typing_type, type(typing.Union))
300+
301+
@classmethod
302+
def _get_defaults(cls, arg_spec):
303+
if not arg_spec.defaults:
304+
return {}
305+
names = arg_spec.args[-len(arg_spec.defaults):]
306+
return zip(names, arg_spec.defaults)
307+
279308

280309
class KeywordSpecification(object):
281310

utest/test_keyword_builder.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from robotlibcore import PY2, RF31, KeywordBuilder
44
from moc_library import MockLibrary
55
if not PY2:
6+
from typing import Union
67
from moc_library_py3 import MockLibraryPy3
78

89

@@ -99,6 +100,12 @@ def test_types_(lib_py3):
99100

100101

101102
@pytest.mark.skipif(PY2, reason='Only for Python 3')
102-
def test_types_(lib_py3):
103+
def test_types(lib_py3):
103104
spec = KeywordBuilder.build(lib_py3.self_and_keyword_only_types)
104105
assert spec.argument_types == {'varargs': int, 'other': bool, 'kwargs': int}
106+
107+
108+
@pytest.mark.skipif(PY2, reason='Only for Python 3')
109+
def test_optional_none(lib_py3):
110+
spec = KeywordBuilder.build(lib_py3.optional_none)
111+
assert spec.argument_types == {'arg1': str, 'arg2': str}

0 commit comments

Comments
 (0)