diff --git a/cmd2/utils.py b/cmd2/utils.py index 88b688c62..a8814e5e1 100644 --- a/cmd2/utils.py +++ b/cmd2/utils.py @@ -1617,11 +1617,15 @@ def get_types(func_or_method: Callable[..., Any]) -> tuple[dict[str, Any], Any]: :return: tuple with first element being dictionary mapping param names to type hints and second element being the return type hint or None if there is no return value type hint :raises ValueError: if the `func_or_method` argument is not a valid object to pass to `inspect.get_annotations` + or if its annotations cannot be resolved """ try: type_hints = inspect.get_annotations(func_or_method, eval_str=True) # Get dictionary of type hints except TypeError as exc: raise ValueError("Argument passed to get_types should be a function or method") from exc + except (NameError, SyntaxError) as exc: + name = getattr(func_or_method, "__name__", func_or_method) + raise ValueError(f"Unable to resolve type hints for {name}: {exc}") from exc ret_ann = type_hints.pop("return", None) # Pop off the return annotation if it exists if inspect.ismethod(func_or_method): type_hints.pop("self", None) # Pop off `self` hint for methods diff --git a/tests/test_utils.py b/tests/test_utils.py index aaedc708c..cfd411095 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1864,6 +1864,14 @@ def foo(x: int) -> str: assert param_value is int +def test_get_types_unresolvable_forward_reference() -> None: + def foo(x: "NotDefinedAnywhere") -> None: # noqa: F821 + print(x) + + with pytest.raises(ValueError, match="Unable to resolve type hints"): + cu.get_types(foo) + + def test_get_types_method() -> None: class Foo: def bar(self, x: bool) -> None: