Skip to content

Commit 716d27b

Browse files
committed
refactor: Improve objects conversion to annotation during dynamic analysis
Issue-369: #369
1 parent df39eab commit 716d27b

2 files changed

Lines changed: 35 additions & 6 deletions

File tree

src/_griffe/agents/inspector.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -562,15 +562,19 @@ def _convert_object_to_annotation(obj: Any, parent: Module | Class) -> str | Exp
562562
# can come from modules which did *not* import them,
563563
# so `inspect.signature` returns actual Python objects
564564
# that we must deal with.
565-
if not isinstance(obj, str):
565+
if isinstance(obj, str):
566+
annotation = obj
567+
else:
568+
# Always give precedence to the object's representation...
569+
obj_repr = repr(obj)
566570
if hasattr(obj, "__name__"): # noqa: SIM108
567-
# Simple types like `int`, `str`, custom classes, etc..
568-
obj = obj.__name__
571+
# ...unless it contains chevrons (which likely means it's a class),
572+
# in which case we use the object's name.
573+
annotation = obj.__name__ if "<" in obj_repr else obj_repr
569574
else:
570-
# Other, more complex types: hope for the best.
571-
obj = repr(obj)
575+
annotation = obj_repr
572576
try:
573-
annotation_node = compile(obj, mode="eval", filename="<>", flags=ast.PyCF_ONLY_AST, optimize=2)
577+
annotation_node = compile(annotation, mode="eval", filename="<>", flags=ast.PyCF_ONLY_AST, optimize=2)
574578
except SyntaxError:
575579
return obj
576580
return safe_get_annotation(annotation_node.body, parent=parent) # type: ignore[attr-defined]

tests/test_inspector.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
import sys
6+
57
import pytest
68

79
from griffe import inspect, temporary_inspected_module, temporary_inspected_package, temporary_pypackage
@@ -30,6 +32,29 @@ def test_annotations_from_classes() -> None:
3032
assert returns.canonical_path == f"{module.name}.A"
3133

3234

35+
# YORE: EOL 3.9: Remove line.
36+
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Type unions not supported on 3.9")
37+
@pytest.mark.parametrize(
38+
("annotation", "expected"),
39+
[
40+
("tuple[int, str]", "tuple[int, str]"),
41+
("Union[int, str]", "typing.Union[int, str]"),
42+
("int | str", "int | str"),
43+
("int | Literal[1]", "typing.Union[int, typing.Literal[1]]"),
44+
],
45+
)
46+
def test_annotations_from_types(annotation: str, expected: str) -> None:
47+
"""Assert annotations are correctly converted to string."""
48+
with temporary_inspected_module(
49+
f"""
50+
from typing import Literal, Union
51+
def func(param: {annotation}): ...
52+
""",
53+
) as module:
54+
param = module["func"].parameters["param"]
55+
assert str(param.annotation) == expected
56+
57+
3358
def test_class_level_imports() -> None:
3459
"""Assert annotations using class-level imports are resolved."""
3560
with temporary_inspected_module(

0 commit comments

Comments
 (0)