diff --git a/atest/DynamicTypesAnnotationsLibrary.py b/atest/DynamicTypesAnnotationsLibrary.py index a46056b..659e262 100644 --- a/atest/DynamicTypesAnnotationsLibrary.py +++ b/atest/DynamicTypesAnnotationsLibrary.py @@ -148,3 +148,7 @@ def enum_conversion(self, param: Optional[penum] = None): def keyword_with_deco_and_signature(self, arg1: bool = False, arg2: bool = False): """Test me doc here""" return f"{arg1}: {type(arg1)}, {arg2}: {type(arg2)}" + + @keyword + def keyword_optional_with_none(self, arg: Optional[str] = None): + return f"arg: {arg}, type: {type(arg)}" diff --git a/atest/tests_types.robot b/atest/tests_types.robot index 139d983..436c8c0 100644 --- a/atest/tests_types.robot +++ b/atest/tests_types.robot @@ -1,5 +1,6 @@ *** Settings *** Library DynamicTypesLibrary.py +Library DynamicTypesAnnotationsLibrary.py xxx Suite Setup Import DynamicTypesAnnotationsLibrary In Python 3 Only *** Test Cases *** @@ -73,6 +74,16 @@ Enum Conversion To Invalid Value Should Fail Run Keyword And Expect Error ValueError: Argument 'param' got value 'not ok' that* ... Enum Conversion not ok +Type Conversion With Optional And None + ${types} = Keyword Optional With None + Should Contain ${types} arg: None, + Should Contain ${types} + ${types} = Keyword Optional With None None + Should Contain ${types} arg: None, + Should Contain ${types} + ${types} = Keyword Optional With None ${None} + Should Contain ${types} arg: None, + Should Contain ${types} *** Keywords *** Import DynamicTypesAnnotationsLibrary In Python 3 Only diff --git a/src/robotlibcore.py b/src/robotlibcore.py index 47d2b06..bbfeb4d 100644 --- a/src/robotlibcore.py +++ b/src/robotlibcore.py @@ -18,19 +18,17 @@ examples see the project pages at https://github.com/robotframework/PythonLibCore """ - import inspect import os +import typing +from robot import __version__ as robot_version from robot.utils import PY_VERSION -try: - import typing -except ImportError: - typing = None from robot.api.deco import keyword # noqa F401 +RF32 = robot_version < '4.' __version__ = '2.2.2.dev1' @@ -246,8 +244,10 @@ def _get_typing_hints(cls, function): # remove return and self statements if arg_with_hint not in all_args: hints.pop(arg_with_hint) - default = cls._get_defaults(arg_spec) - return cls._remove_optional_none_type_hints(hints, default) + if RF32: + default = cls._get_defaults(arg_spec) + return cls._remove_optional_none_type_hints(hints, default) + return hints @classmethod def _args_as_list(cls, function, arg_spec): @@ -260,6 +260,7 @@ def _args_as_list(cls, function, arg_spec): function_args.append(arg_spec.varkw) return function_args + # TODO: Remove when support RF 3.2 is dropped # Copied from: robot.running.arguments.argumentparser @classmethod def _remove_optional_none_type_hints(cls, type_hints, defaults): @@ -274,6 +275,7 @@ def _remove_optional_none_type_hints(cls, type_hints, defaults): type_hints[arg] = types[0] return type_hints + # TODO: Remove when support RF 3.2 is dropped # Copied from: robot.running.arguments.argumentparser @classmethod def _is_union(cls, typing_type): diff --git a/utest/test_get_keyword_types.py b/utest/test_get_keyword_types.py index f377d04..d5cd917 100644 --- a/utest/test_get_keyword_types.py +++ b/utest/test_get_keyword_types.py @@ -1,6 +1,9 @@ -from typing import List, Union - import pytest +import typing + +from robotlibcore import RF32 + +from typing import List, Union from DynamicTypesAnnotationsLibrary import DynamicTypesAnnotationsLibrary from DynamicTypesAnnotationsLibrary import CustomObject @@ -180,3 +183,15 @@ def test_keyword_self_and_keyword_only_types(lib_types): def test_keyword_with_decorator_arguments(lib_types): types = lib_types.get_keyword_types('keyword_with_deco_and_signature') assert types == {'arg1': bool, 'arg2': bool} + + +@pytest.mark.skipif(RF32, reason='Only for RF4+') +def test_keyword_optional_with_none_rf4(lib_types): + types = lib_types.get_keyword_types('keyword_optional_with_none') + assert types == {'arg': typing.Union[str, type(None)]} + + +@pytest.mark.skipif(not RF32, reason='Only for RF3.2+') +def test_keyword_optional_with_none_rf32(lib_types): + types = lib_types.get_keyword_types('keyword_optional_with_none') + assert types == {'arg': str} diff --git a/utest/test_keyword_builder.py b/utest/test_keyword_builder.py index f66e653..eb956ee 100644 --- a/utest/test_keyword_builder.py +++ b/utest/test_keyword_builder.py @@ -1,6 +1,7 @@ import pytest +import typing -from robotlibcore import KeywordBuilder +from robotlibcore import KeywordBuilder, RF32 from moc_library import MockLibrary from DynamicTypesAnnotationsLibrary import DynamicTypesAnnotationsLibrary @@ -77,11 +78,18 @@ def test_types(lib): assert spec.argument_types == {'varargs': int, 'other': bool, 'kwargs': int} -def test_optional_none(lib): +@pytest.mark.skipif(not RF32, reason='Only for RF3.2+') +def test_optional_none_rf32(lib): spec = KeywordBuilder.build(lib.optional_none) assert spec.argument_types == {'arg1': str, 'arg2': str} +@pytest.mark.skipif(RF32, reason='Only for RF4') +def test_optional_none_rf4(lib): + spec = KeywordBuilder.build(lib.optional_none) + assert spec.argument_types == {'arg1': typing.Union[str, None], 'arg2': typing.Union[str, None]} + + def test_complex_deco(dyn_types): spec = KeywordBuilder.build(dyn_types.keyword_with_deco_and_signature) assert spec.argument_types == {'arg1': bool, 'arg2': bool}