gh-128184: Fix display of signatures with ForwardRefs (#130815)

Co-authored-by: sobolevn <mail@sobolevn.me>
This commit is contained in:
Jelle Zijlstra 2025-03-04 06:58:37 -08:00 committed by GitHub
parent 80e6d3ec49
commit 1d251b8339
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 46 additions and 2 deletions

View file

@ -1163,7 +1163,10 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen,
try: try:
# In some cases fetching a signature is not possible. # In some cases fetching a signature is not possible.
# But, we surely should not fail in this case. # But, we surely should not fail in this case.
text_sig = str(inspect.signature(cls)).replace(' -> None', '') text_sig = str(inspect.signature(
cls,
annotation_format=annotationlib.Format.FORWARDREF,
)).replace(' -> None', '')
except (TypeError, ValueError): except (TypeError, ValueError):
text_sig = '' text_sig = ''
cls.__doc__ = (cls.__name__ + text_sig) cls.__doc__ = (cls.__name__ + text_sig)

View file

@ -143,7 +143,7 @@ __all__ = [
import abc import abc
from annotationlib import Format from annotationlib import Format, ForwardRef
from annotationlib import get_annotations # re-exported from annotationlib import get_annotations # re-exported
import ast import ast
import dis import dis
@ -1342,6 +1342,8 @@ def formatannotation(annotation, base_module=None, *, quote_annotation_strings=T
if annotation.__module__ in ('builtins', base_module): if annotation.__module__ in ('builtins', base_module):
return annotation.__qualname__ return annotation.__qualname__
return annotation.__module__+'.'+annotation.__qualname__ return annotation.__module__+'.'+annotation.__qualname__
if isinstance(annotation, ForwardRef):
return annotation.__forward_arg__
return repr(annotation) return repr(annotation)
def formatannotationrelativeto(object): def formatannotationrelativeto(object):

View file

@ -12,6 +12,7 @@ import builtins
import types import types
import weakref import weakref
import traceback import traceback
import textwrap
import unittest import unittest
from unittest.mock import Mock from unittest.mock import Mock
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol, DefaultDict from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol, DefaultDict
@ -2343,6 +2344,31 @@ class TestDocString(unittest.TestCase):
self.assertDocStrEqual(C.__doc__, "C(x:collections.deque=<factory>)") self.assertDocStrEqual(C.__doc__, "C(x:collections.deque=<factory>)")
def test_docstring_undefined_name(self):
@dataclass
class C:
x: undef
self.assertDocStrEqual(C.__doc__, "C(x:undef)")
def test_docstring_with_unsolvable_forward_ref_in_init(self):
# See: https://github.com/python/cpython/issues/128184
ns = {}
exec(
textwrap.dedent(
"""
from dataclasses import dataclass
@dataclass
class C:
def __init__(self, x: X, num: int) -> None: ...
""",
),
ns,
)
self.assertDocStrEqual(ns['C'].__doc__, "C(x:X,num:int)")
def test_docstring_with_no_signature(self): def test_docstring_with_no_signature(self):
# See https://github.com/python/cpython/issues/103449 # See https://github.com/python/cpython/issues/103449
class Meta(type): class Meta(type):

View file

@ -1753,6 +1753,10 @@ class TestFormatAnnotation(unittest.TestCase):
self.assertEqual(inspect.formatannotation(ann), 'Union[List[str], int]') self.assertEqual(inspect.formatannotation(ann), 'Union[List[str], int]')
self.assertEqual(inspect.formatannotation(ann1), 'Union[List[testModule.typing.A], int]') self.assertEqual(inspect.formatannotation(ann1), 'Union[List[testModule.typing.A], int]')
def test_forwardref(self):
fwdref = ForwardRef('fwdref')
self.assertEqual(inspect.formatannotation(fwdref), 'fwdref')
class TestIsMethodDescriptor(unittest.TestCase): class TestIsMethodDescriptor(unittest.TestCase):
@ -4587,6 +4591,11 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(str(inspect.signature(foo)), self.assertEqual(str(inspect.signature(foo)),
inspect.signature(foo).format()) inspect.signature(foo).format())
def foo(x: undef):
pass
sig = inspect.signature(foo, annotation_format=Format.FORWARDREF)
self.assertEqual(str(sig), '(x: undef)')
def test_signature_str_positional_only(self): def test_signature_str_positional_only(self):
P = inspect.Parameter P = inspect.Parameter
S = inspect.Signature S = inspect.Signature

View file

@ -0,0 +1,4 @@
Improve display of :class:`annotationlib.ForwardRef` object
within :class:`inspect.Signature` representations.
This also fixes a :exc:`NameError` that was raised when using
:func:`dataclasses.dataclass` on classes with unresolvable forward references.