diff --git a/atest/lib_future_annotation.py b/atest/lib_future_annotation.py new file mode 100644 index 0000000..1fd6576 --- /dev/null +++ b/atest/lib_future_annotation.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing_extensions import TypedDict + +from robotlibcore import DynamicCore, keyword + + +class Location(TypedDict): + longitude: float + latitude: float + + +class lib_future_annotation(DynamicCore): + + def __init__(self) -> None: + DynamicCore.__init__(self, []) + + @keyword + def future_annotations(self, arg: Location): + longitude = arg["longitude"] + latitude = arg["latitude"] + return f'{longitude} type({type(longitude)}), {latitude} {type(latitude)}' diff --git a/requirements-dev.txt b/requirements-dev.txt index 2f4358f..aff0549 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,3 +11,4 @@ wheel rellu >= 0.7 twine wheel +typing-extensions >= 4.5.0 diff --git a/src/robotlibcore.py b/src/robotlibcore.py index c87c5c8..0d95b29 100644 --- a/src/robotlibcore.py +++ b/src/robotlibcore.py @@ -21,7 +21,7 @@ import inspect import os from dataclasses import dataclass -from typing import Any, Callable, List, Optional, Union, get_type_hints +from typing import Any, Callable, List, Optional, Union, get_type_hints, ForwardRef from robot.api.deco import keyword # noqa: F401 from robot.errors import DataError @@ -223,6 +223,17 @@ def _get_arguments(cls, function): def _get_arg_spec(cls, function: Callable) -> inspect.FullArgSpec: return inspect.getfullargspec(function) + @classmethod + def _get_type_hint(cls, function: Callable): + try: + hints = get_type_hints(function) + except Exception: # noqa: BLE001 + hints = function.__annotations__ + for arg, hint in hints.items(): + if isinstance(hint, ForwardRef): + hint = hint.__forward_arg__ + return hints + @classmethod def _get_args(cls, arg_spec: inspect.FullArgSpec, function: Callable) -> list: args = cls._drop_self_from_args(function, arg_spec) @@ -279,10 +290,7 @@ def _get_types(cls, function): @classmethod def _get_typing_hints(cls, function): function = cls.unwrap(function) - try: - hints = get_type_hints(function) - except Exception: # noqa: BLE001 - hints = function.__annotations__ + hints = cls._get_type_hint(function) arg_spec = cls._get_arg_spec(function) all_args = cls._args_as_list(function, arg_spec) for arg_with_hint in list(hints): diff --git a/utest/test_get_keyword_types.py b/utest/test_get_keyword_types.py index 7a0dba2..7e407f3 100644 --- a/utest/test_get_keyword_types.py +++ b/utest/test_get_keyword_types.py @@ -4,6 +4,7 @@ import pytest from DynamicTypesAnnotationsLibrary import CustomObject, DynamicTypesAnnotationsLibrary from DynamicTypesLibrary import DynamicTypesLibrary +from lib_future_annotation import lib_future_annotation, Location @pytest.fixture(scope="module") @@ -16,6 +17,11 @@ def lib_types(): return DynamicTypesAnnotationsLibrary("aaa") +@pytest.fixture(scope="module") +def lib_annotation(): + return lib_future_annotation() + + def test_using_keyword_types(lib): types = lib.get_keyword_types("keyword_with_types") assert types == {"arg1": str} @@ -204,3 +210,9 @@ def test_kw_with_many_named_arguments_with_default(lib_types: DynamicTypesAnnota assert types == {"arg1": int, "arg2": str} types = lib_types.get_keyword_types("kw_with_positional_and_named_arguments") assert types == {"arg2": int} + + +def test_lib_annotations(lib_annotation: lib_future_annotation): + types = lib_annotation.get_keyword_types("future_annotations") + expected = {"arg1": Location} + assert types == expected