Type annotations, PEP 649 and PEP 563

That is exactly my use-case of stringified annotations. PEP 563 indeed makes it much more reliable.

To explain a bit: I’m writing a tool, Griffe, that visits the AST of modules to extract useful information. It is able to rebuild an expression from nodes, in which each name is a struct containing both the name as written in the code, and the full, resolved path in the scope of its parent.
For compiled/builtin modules, it cannot load an AST so it falls back on introspection. When the introspected module imports future annotations, great, I can simply compile stringified annotations to transform them into the previously mentioned expressions (these annotations come, for example, from inspect.Signature.return_annotation). If the introspected module does not import future annotation, I have to handle an actual Python object, which is much more unpredictable.

The gist of it:

def _convert_object_to_annotation(obj, parent):
    # even when *we* import future annotations,
    # the object from which we get a signature
    # can come from modules which did *not* import them,
    # so inspect.signature returns actual Python objects
    # that we must deal with
    if not isinstance(obj, str):
        if hasattr(obj, "__name__"):
            # simple types like int, str, custom classes, etc.
            obj = obj.__name__
        else:
            # other, more complex types: hope for the best
            obj = repr(obj)
    try:
        annotation_node = compile(obj, mode="eval", filename="<>", flags=ast.PyCF_ONLY_AST, optimize=2)
    except SyntaxError:
        return obj
    return get_annotation(annotation_node.body, parent=parent)

Emphasis on repr(obj). Types imported from the typing module have good representation methods. But this is not enough, see the example below:

>>> from typing import List, Tuple
>>> T = List[Tuple[str, int]]
>>> repr(T)
'typing.List[typing.Tuple[str, int]]'

The issue here is that typing is unknown in the given scope, so I won’t be able to resolve it properly.

Even worse:

>>> TT = Tuple[T, T]
>>> repr(TT)
'typing.Tuple[typing.List[typing.Tuple[str, int]], typing.List[typing.Tuple[str, int]]]'

That makes a really long string, and T was lost in the process.

If the introspected module had stringified annotations instead, I would get 'TT' from, for example, inspect.signature, and I would be able to resolve it.

1 Like