11from __future__ import annotations
22
33import inspect
4+ import re
45import sys
56import textwrap
67import typing
@@ -252,43 +253,48 @@ def _future_annotations_imported(obj: Any) -> bool:
252253
253254
254255def get_all_type_hints (obj : Any , name : str ) -> dict [str , Any ]:
255- rv = {}
256-
256+ result = _get_type_hint (name , obj )
257+ if result :
258+ return result
259+ result = backfill_type_hints (obj , name )
257260 try :
258- rv = get_type_hints ( obj )
259- except (AttributeError , TypeError , RecursionError ) as exc :
260- # Introspecting a slot wrapper will raise TypeError, and and some recursive type
261- # definitions will cause a RecursionError (https://github.com/python/typing/issues/574).
261+ obj . __annotations__ = result
262+ except (AttributeError , TypeError ) :
263+ return result
264+ return _get_type_hint ( name , obj )
262265
263- # If one is using PEP563 annotations, Python will raise a (e.g.,)
264- # TypeError("TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'")
265- # on 'str | None', therefore we accept TypeErrors with that error message
266- # if 'annotations' is imported from '__future__'.
267- if isinstance (exc , TypeError ) and _future_annotations_imported (obj ) and "unsupported operand type" in str (exc ):
268- rv = obj .__annotations__
269- except NameError as exc :
270- _LOGGER .warning ('Cannot resolve forward reference in type annotations of "%s": %s' , name , exc )
271- rv = obj .__annotations__
272266
273- if rv :
274- return rv
267+ _TYPE_GUARD_IMPORT_RE = re . compile ( r" if (typing.)?TYPE_CHECKING:([\s\S]*?)(?=\n\S)" )
268+ _TYPE_GUARD_IMPORTS_RESOLVED = set ()
275269
276- rv = backfill_type_hints (obj , name )
277270
278- try :
279- obj .__annotations__ = rv
280- except (AttributeError , TypeError ):
281- return rv
271+ def _resolve_type_guarded_imports (obj : Any ) -> None :
272+ if hasattr (obj , "__module__" ) and obj .__module__ not in _TYPE_GUARD_IMPORTS_RESOLVED :
273+ _TYPE_GUARD_IMPORTS_RESOLVED .add (obj .__module__ )
274+ if obj .__module__ not in sys .builtin_module_names :
275+ module = inspect .getmodule (obj )
276+ if module :
277+ module_code = inspect .getsource (module )
278+ for (_ , part ) in _TYPE_GUARD_IMPORT_RE .findall (module_code ):
279+ module_code = textwrap .dedent (part )
280+ exec (module_code , obj .__globals__ )
282281
282+
283+ def _get_type_hint (name : str , obj : Any ) -> dict [str , Any ]:
284+ _resolve_type_guarded_imports (obj )
283285 try :
284- rv = get_type_hints (obj )
285- except (AttributeError , TypeError ):
286- pass
286+ result = get_type_hints (obj )
287+ except (AttributeError , TypeError , RecursionError ) as exc :
288+ # TypeError - slot wrapper, PEP-563 when part of new syntax not supported
289+ # RecursionError - some recursive type definitions https://github.com/python/typing/issues/574
290+ if isinstance (exc , TypeError ) and _future_annotations_imported (obj ) and "unsupported operand type" in str (exc ):
291+ result = obj .__annotations__
292+ else :
293+ result = {}
287294 except NameError as exc :
288295 _LOGGER .warning ('Cannot resolve forward reference in type annotations of "%s": %s' , name , exc )
289- rv = obj .__annotations__
290-
291- return rv
296+ result = obj .__annotations__
297+ return result
292298
293299
294300def backfill_type_hints (obj : Any , name : str ) -> dict [str , Any ]:
@@ -305,11 +311,9 @@ def backfill_type_hints(obj: Any, name: str) -> dict[str, Any]:
305311
306312 def _one_child (module : Module ) -> stmt | None :
307313 children = module .body # use the body to ignore type comments
308-
309314 if len (children ) != 1 :
310315 _LOGGER .warning ('Did not get exactly one node from AST for "%s", got %s' , name , len (children ))
311316 return None
312-
313317 return children [0 ]
314318
315319 try :
@@ -526,4 +530,5 @@ def setup(app: Sphinx) -> dict[str, bool]:
526530 "normalize_source_lines" ,
527531 "process_docstring" ,
528532 "process_signature" ,
533+ "backfill_type_hints" ,
529534]
0 commit comments