1"""Helpers for inspecting Python modules."""
2
3from __future__ import annotations
4
5import ast
6import builtins
7import contextlib
8import enum
9import inspect
10import re
11import sys
12import types
13import typing
14from collections.abc import Mapping
15from functools import cached_property, partial, partialmethod, singledispatchmethod
16from importlib import import_module
17from inspect import Parameter, Signature
18from io import StringIO
19from types import ClassMethodDescriptorType, MethodDescriptorType, WrapperDescriptorType
20from typing import TYPE_CHECKING, Any
21
22from sphinx.pycode.ast import unparse as ast_unparse
23from sphinx.util import logging
24from sphinx.util.typing import ForwardRef, stringify_annotation
25
26if TYPE_CHECKING:
27 from collections.abc import Callable, Sequence
28 from inspect import _ParameterKind
29 from types import MethodType, ModuleType
30 from typing import Final, Protocol, Union
31
32 from typing_extensions import TypeAlias, TypeIs
33
34 class _SupportsGet(Protocol):
35 def __get__(self, __instance: Any, __owner: type | None = ...) -> Any: ... # NoQA: E704
36
37 class _SupportsSet(Protocol):
38 # instance and value are contravariants but we do not need that precision
39 def __set__(self, __instance: Any, __value: Any) -> None: ... # NoQA: E704
40
41 class _SupportsDelete(Protocol):
42 # instance is contravariant but we do not need that precision
43 def __delete__(self, __instance: Any) -> None: ... # NoQA: E704
44
45 _RoutineType: TypeAlias = Union[
46 types.FunctionType,
47 types.LambdaType,
48 types.MethodType,
49 types.BuiltinFunctionType,
50 types.BuiltinMethodType,
51 types.WrapperDescriptorType,
52 types.MethodDescriptorType,
53 types.ClassMethodDescriptorType,
54 ]
55 _SignatureType: TypeAlias = Union[
56 Callable[..., Any],
57 staticmethod,
58 classmethod,
59 ]
60
61logger = logging.getLogger(__name__)
62
63memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
64
65# re-export as is
66isasyncgenfunction = inspect.isasyncgenfunction
67ismethod = inspect.ismethod
68ismethoddescriptor = inspect.ismethoddescriptor
69isclass = inspect.isclass
70ismodule = inspect.ismodule
71
72
73def unwrap(obj: Any) -> Any:
74 """Get an original object from wrapped object (wrapped functions).
75
76 Mocked objects are returned as is.
77 """
78 if hasattr(obj, '__sphinx_mock__'):
79 # Skip unwrapping mock object to avoid RecursionError
80 return obj
81
82 try:
83 return inspect.unwrap(obj)
84 except ValueError:
85 # might be a mock object
86 return obj
87
88
89def unwrap_all(obj: Any, *, stop: Callable[[Any], bool] | None = None) -> Any:
90 """Get an original object from wrapped object.
91
92 Unlike :func:`unwrap`, this unwraps partial functions, wrapped functions,
93 class methods and static methods.
94
95 When specified, *stop* is a predicate indicating whether an object should
96 be unwrapped or not.
97 """
98 if callable(stop):
99 while not stop(obj):
100 if ispartial(obj):
101 obj = obj.func
102 elif inspect.isroutine(obj) and hasattr(obj, '__wrapped__'):
103 obj = obj.__wrapped__
104 elif isclassmethod(obj) or isstaticmethod(obj):
105 obj = obj.__func__
106 else:
107 return obj
108 return obj # in case the while loop never starts
109
110 while True:
111 if ispartial(obj):
112 obj = obj.func
113 elif inspect.isroutine(obj) and hasattr(obj, '__wrapped__'):
114 obj = obj.__wrapped__
115 elif isclassmethod(obj) or isstaticmethod(obj):
116 obj = obj.__func__
117 else:
118 return obj
119
120
121def getall(obj: Any) -> Sequence[str] | None:
122 """Get the ``__all__`` attribute of an object as a sequence.
123
124 This returns ``None`` if the given ``obj.__all__`` does not exist and
125 raises :exc:`ValueError` if ``obj.__all__`` is not a list or tuple of
126 strings.
127 """
128 __all__ = safe_getattr(obj, '__all__', None)
129 if __all__ is None:
130 return None
131 if isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__):
132 return __all__
133 raise ValueError(__all__)
134
135
136def getannotations(obj: Any) -> Mapping[str, Any]:
137 """Safely get the ``__annotations__`` attribute of an object."""
138 if sys.version_info >= (3, 10, 0) or not isinstance(obj, type):
139 __annotations__ = safe_getattr(obj, '__annotations__', None)
140 else:
141 # Workaround for bugfix not available until python 3.10 as recommended by docs
142 # https://docs.python.org/3.10/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
143 __dict__ = safe_getattr(obj, '__dict__', {})
144 __annotations__ = __dict__.get('__annotations__', None)
145 if isinstance(__annotations__, Mapping):
146 return __annotations__
147 return {}
148
149
150def getglobals(obj: Any) -> Mapping[str, Any]:
151 """Safely get :attr:`obj.__globals__ <function.__globals__>`."""
152 __globals__ = safe_getattr(obj, '__globals__', None)
153 if isinstance(__globals__, Mapping):
154 return __globals__
155 return {}
156
157
158def getmro(obj: Any) -> tuple[type, ...]:
159 """Safely get :attr:`obj.__mro__ <class.__mro__>`."""
160 __mro__ = safe_getattr(obj, '__mro__', None)
161 if isinstance(__mro__, tuple):
162 return __mro__
163 return ()
164
165
166def getorigbases(obj: Any) -> tuple[Any, ...] | None:
167 """Safely get ``obj.__orig_bases__``.
168
169 This returns ``None`` if the object is not a class or if ``__orig_bases__``
170 is not well-defined (e.g., a non-tuple object or an empty sequence).
171 """
172 if not isclass(obj):
173 return None
174
175 # Get __orig_bases__ from obj.__dict__ to avoid accessing the parent's __orig_bases__.
176 # refs: https://github.com/sphinx-doc/sphinx/issues/9607
177 __dict__ = safe_getattr(obj, '__dict__', {})
178 __orig_bases__ = __dict__.get('__orig_bases__')
179 if isinstance(__orig_bases__, tuple) and len(__orig_bases__) > 0:
180 return __orig_bases__
181 return None
182
183
184def getslots(obj: Any) -> dict[str, Any] | dict[str, None] | None:
185 """Safely get :term:`obj.__slots__ <__slots__>` as a dictionary if any.
186
187 - This returns ``None`` if ``obj.__slots__`` does not exist.
188 - This raises a :exc:`TypeError` if *obj* is not a class.
189 - This raises a :exc:`ValueError` if ``obj.__slots__`` is invalid.
190 """
191 if not isclass(obj):
192 raise TypeError
193
194 __slots__ = safe_getattr(obj, '__slots__', None)
195 if __slots__ is None:
196 return None
197 elif isinstance(__slots__, dict):
198 return __slots__
199 elif isinstance(__slots__, str):
200 return {__slots__: None}
201 elif isinstance(__slots__, (list, tuple)):
202 return dict.fromkeys(__slots__)
203 else:
204 raise ValueError
205
206
207def isNewType(obj: Any) -> bool:
208 """Check the if object is a kind of :class:`~typing.NewType`."""
209 if sys.version_info[:2] >= (3, 10):
210 return isinstance(obj, typing.NewType)
211 __module__ = safe_getattr(obj, '__module__', None)
212 __qualname__ = safe_getattr(obj, '__qualname__', None)
213 return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
214
215
216def isenumclass(x: Any) -> TypeIs[type[enum.Enum]]:
217 """Check if the object is an :class:`enumeration class <enum.Enum>`."""
218 return isclass(x) and issubclass(x, enum.Enum)
219
220
221def isenumattribute(x: Any) -> TypeIs[enum.Enum]:
222 """Check if the object is an enumeration attribute."""
223 return isinstance(x, enum.Enum)
224
225
226def unpartial(obj: Any) -> Any:
227 """Get an original object from a partial-like object.
228
229 If *obj* is not a partial object, it is returned as is.
230
231 .. seealso:: :func:`ispartial`
232 """
233 while ispartial(obj):
234 obj = obj.func
235 return obj
236
237
238def ispartial(obj: Any) -> TypeIs[partial | partialmethod]:
239 """Check if the object is a partial function or method."""
240 return isinstance(obj, (partial, partialmethod))
241
242
243def isclassmethod(
244 obj: Any,
245 cls: Any = None,
246 name: str | None = None,
247) -> TypeIs[classmethod]:
248 """Check if the object is a :class:`classmethod`."""
249 if isinstance(obj, classmethod):
250 return True
251 if ismethod(obj) and obj.__self__ is not None and isclass(obj.__self__):
252 return True
253 if cls and name:
254 # trace __mro__ if the method is defined in parent class
255 sentinel = object()
256 for basecls in getmro(cls):
257 meth = basecls.__dict__.get(name, sentinel)
258 if meth is not sentinel:
259 return isclassmethod(meth)
260 return False
261
262
263def isstaticmethod(
264 obj: Any,
265 cls: Any = None,
266 name: str | None = None,
267) -> TypeIs[staticmethod]:
268 """Check if the object is a :class:`staticmethod`."""
269 if isinstance(obj, staticmethod):
270 return True
271 if cls and name:
272 # trace __mro__ if the method is defined in parent class
273 sentinel = object()
274 for basecls in getattr(cls, '__mro__', [cls]):
275 meth = basecls.__dict__.get(name, sentinel)
276 if meth is not sentinel:
277 return isinstance(meth, staticmethod)
278 return False
279
280
281def isdescriptor(x: Any) -> TypeIs[_SupportsGet | _SupportsSet | _SupportsDelete]:
282 """Check if the object is a :external+python:term:`descriptor`."""
283 return any(
284 callable(safe_getattr(x, item, None)) for item in ('__get__', '__set__', '__delete__')
285 )
286
287
288def isabstractmethod(obj: Any) -> bool:
289 """Check if the object is an :func:`abstractmethod`."""
290 return safe_getattr(obj, '__isabstractmethod__', False) is True
291
292
293def isboundmethod(method: MethodType) -> bool:
294 """Check if the method is a bound method."""
295 return safe_getattr(method, '__self__', None) is not None
296
297
298def is_cython_function_or_method(obj: Any) -> bool:
299 """Check if the object is a function or method in cython."""
300 try:
301 return obj.__class__.__name__ == 'cython_function_or_method'
302 except AttributeError:
303 return False
304
305
306_DESCRIPTOR_LIKE: Final[tuple[type, ...]] = (
307 ClassMethodDescriptorType,
308 MethodDescriptorType,
309 WrapperDescriptorType,
310)
311
312
313def isattributedescriptor(obj: Any) -> bool:
314 """Check if the object is an attribute-like descriptor."""
315 if inspect.isdatadescriptor(obj):
316 # data descriptor is kind of attribute
317 return True
318 if isdescriptor(obj):
319 # non data descriptor
320 unwrapped = unwrap(obj)
321 if isfunction(unwrapped) or isbuiltin(unwrapped) or ismethod(unwrapped):
322 # attribute must not be either function, builtin and method
323 return False
324 if is_cython_function_or_method(unwrapped):
325 # attribute must not be either function and method (for cython)
326 return False
327 if isclass(unwrapped):
328 # attribute must not be a class
329 return False
330 if isinstance(unwrapped, _DESCRIPTOR_LIKE):
331 # attribute must not be a method descriptor
332 return False
333 # attribute must not be an instancemethod (C-API)
334 return type(unwrapped).__name__ != 'instancemethod'
335 return False
336
337
338def is_singledispatch_function(obj: Any) -> bool:
339 """Check if the object is a :func:`~functools.singledispatch` function."""
340 return (
341 inspect.isfunction(obj)
342 and hasattr(obj, 'dispatch')
343 and hasattr(obj, 'register')
344 and obj.dispatch.__module__ == 'functools'
345 )
346
347
348def is_singledispatch_method(obj: Any) -> TypeIs[singledispatchmethod]:
349 """Check if the object is a :class:`~functools.singledispatchmethod`."""
350 return isinstance(obj, singledispatchmethod)
351
352
353def isfunction(obj: Any) -> TypeIs[types.FunctionType]:
354 """Check if the object is a user-defined function.
355
356 Partial objects are unwrapped before checking them.
357
358 .. seealso:: :external+python:func:`inspect.isfunction`
359 """
360 return inspect.isfunction(unpartial(obj))
361
362
363def isbuiltin(obj: Any) -> TypeIs[types.BuiltinFunctionType]:
364 """Check if the object is a built-in function or method.
365
366 Partial objects are unwrapped before checking them.
367
368 .. seealso:: :external+python:func:`inspect.isbuiltin`
369 """
370 return inspect.isbuiltin(unpartial(obj))
371
372
373def isroutine(obj: Any) -> TypeIs[_RoutineType]:
374 """Check if the object is a kind of function or method.
375
376 Partial objects are unwrapped before checking them.
377
378 .. seealso:: :external+python:func:`inspect.isroutine`
379 """
380 return inspect.isroutine(unpartial(obj))
381
382
383def iscoroutinefunction(obj: Any) -> TypeIs[Callable[..., types.CoroutineType]]:
384 """Check if the object is a :external+python:term:`coroutine` function."""
385 obj = unwrap_all(obj, stop=_is_wrapped_coroutine)
386 return inspect.iscoroutinefunction(obj)
387
388
389def _is_wrapped_coroutine(obj: Any) -> bool:
390 """Check if the object is wrapped coroutine-function."""
391 if isstaticmethod(obj) or isclassmethod(obj) or ispartial(obj):
392 # staticmethod, classmethod and partial method are not a wrapped coroutine-function
393 # Note: Since 3.10, staticmethod and classmethod becomes a kind of wrappers
394 return False
395 return hasattr(obj, '__wrapped__')
396
397
398def isproperty(obj: Any) -> TypeIs[property | cached_property]:
399 """Check if the object is property (possibly cached)."""
400 return isinstance(obj, (property, cached_property))
401
402
403def isgenericalias(obj: Any) -> TypeIs[types.GenericAlias]:
404 """Check if the object is a generic alias."""
405 return isinstance(obj, (types.GenericAlias, typing._BaseGenericAlias)) # type: ignore[attr-defined]
406
407
408def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
409 """A getattr() that turns all exceptions into AttributeErrors."""
410 try:
411 return getattr(obj, name, *defargs)
412 except Exception as exc:
413 # sometimes accessing a property raises an exception (e.g.
414 # NotImplementedError), so let's try to read the attribute directly
415 try:
416 # In case the object does weird things with attribute access
417 # such that accessing `obj.__dict__` may raise an exception
418 return obj.__dict__[name]
419 except Exception:
420 pass
421
422 # this is a catch-all for all the weird things that some modules do
423 # with attribute access
424 if defargs:
425 return defargs[0]
426
427 raise AttributeError(name) from exc
428
429
430def object_description(obj: Any, *, _seen: frozenset[int] = frozenset()) -> str:
431 """A repr() implementation that returns text safe to use in reST context.
432
433 Maintains a set of 'seen' object IDs to detect and avoid infinite recursion.
434 """
435 seen = _seen
436 if isinstance(obj, dict):
437 if id(obj) in seen:
438 return 'dict(...)'
439 seen |= {id(obj)}
440 try:
441 sorted_keys = sorted(obj)
442 except TypeError:
443 # Cannot sort dict keys, fall back to using descriptions as a sort key
444 sorted_keys = sorted(obj, key=lambda k: object_description(k, _seen=seen))
445
446 items = (
447 (object_description(key, _seen=seen), object_description(obj[key], _seen=seen))
448 for key in sorted_keys
449 )
450 return '{%s}' % ', '.join(f'{key}: {value}' for (key, value) in items)
451 elif isinstance(obj, set):
452 if id(obj) in seen:
453 return 'set(...)'
454 seen |= {id(obj)}
455 try:
456 sorted_values = sorted(obj)
457 except TypeError:
458 # Cannot sort set values, fall back to using descriptions as a sort key
459 sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen))
460 return '{%s}' % ', '.join(object_description(x, _seen=seen) for x in sorted_values)
461 elif isinstance(obj, frozenset):
462 if id(obj) in seen:
463 return 'frozenset(...)'
464 seen |= {id(obj)}
465 try:
466 sorted_values = sorted(obj)
467 except TypeError:
468 # Cannot sort frozenset values, fall back to using descriptions as a sort key
469 sorted_values = sorted(obj, key=lambda x: object_description(x, _seen=seen))
470 return 'frozenset({%s})' % ', '.join(
471 object_description(x, _seen=seen) for x in sorted_values
472 )
473 elif isinstance(obj, enum.Enum):
474 if obj.__repr__.__func__ is not enum.Enum.__repr__: # type: ignore[attr-defined]
475 return repr(obj)
476 return f'{obj.__class__.__name__}.{obj.name}'
477 elif isinstance(obj, tuple):
478 if id(obj) in seen:
479 return 'tuple(...)'
480 seen |= frozenset([id(obj)])
481 return '({}{})'.format(
482 ', '.join(object_description(x, _seen=seen) for x in obj),
483 ',' * (len(obj) == 1),
484 )
485 elif isinstance(obj, list):
486 if id(obj) in seen:
487 return 'list(...)'
488 seen |= {id(obj)}
489 return '[%s]' % ', '.join(object_description(x, _seen=seen) for x in obj)
490
491 try:
492 s = repr(obj)
493 except Exception as exc:
494 raise ValueError from exc
495 # Strip non-deterministic memory addresses such as
496 # ``<__main__.A at 0x7f68cb685710>``
497 s = memory_address_re.sub('', s)
498 return s.replace('\n', ' ')
499
500
501def is_builtin_class_method(obj: Any, attr_name: str) -> bool:
502 """Check whether *attr_name* is implemented on a builtin class.
503
504 >>> is_builtin_class_method(int, '__init__')
505 True
506
507
508 This function is needed since CPython implements ``int.__init__`` via
509 descriptors, but PyPy implementation is written in pure Python code.
510 """
511 mro = getmro(obj)
512
513 try:
514 cls = next(c for c in mro if attr_name in safe_getattr(c, '__dict__', {}))
515 except StopIteration:
516 return False
517
518 try:
519 name = safe_getattr(cls, '__name__')
520 except AttributeError:
521 return False
522
523 return getattr(builtins, name, None) is cls
524
525
526class DefaultValue:
527 """A simple wrapper for default value of the parameters of overload functions."""
528
529 def __init__(self, value: str) -> None:
530 self.value = value
531
532 def __eq__(self, other: object) -> bool:
533 return self.value == other
534
535 def __repr__(self) -> str:
536 return self.value
537
538
539class TypeAliasForwardRef:
540 """Pseudo typing class for :confval:`autodoc_type_aliases`.
541
542 This avoids the error on evaluating the type inside :func:`typing.get_type_hints()`.
543 """
544
545 def __init__(self, name: str) -> None:
546 self.name = name
547
548 def __call__(self) -> None:
549 # Dummy method to imitate special typing classes
550 pass
551
552 def __eq__(self, other: Any) -> bool:
553 return self.name == other
554
555 def __hash__(self) -> int:
556 return hash(self.name)
557
558 def __repr__(self) -> str:
559 return self.name
560
561
562class TypeAliasModule:
563 """Pseudo module class for :confval:`autodoc_type_aliases`."""
564
565 def __init__(self, modname: str, mapping: Mapping[str, str]) -> None:
566 self.__modname = modname
567 self.__mapping = mapping
568
569 self.__module: ModuleType | None = None
570
571 def __getattr__(self, name: str) -> Any:
572 fullname = '.'.join(filter(None, [self.__modname, name]))
573 if fullname in self.__mapping:
574 # exactly matched
575 return TypeAliasForwardRef(self.__mapping[fullname])
576 else:
577 prefix = fullname + '.'
578 nested = {k: v for k, v in self.__mapping.items() if k.startswith(prefix)}
579 if nested:
580 # sub modules or classes found
581 return TypeAliasModule(fullname, nested)
582 else:
583 # no sub modules or classes found.
584 try:
585 # return the real submodule if exists
586 return import_module(fullname)
587 except ImportError:
588 # return the real class
589 if self.__module is None:
590 self.__module = import_module(self.__modname)
591
592 return getattr(self.__module, name)
593
594
595class TypeAliasNamespace(dict[str, Any]):
596 """Pseudo namespace class for :confval:`autodoc_type_aliases`.
597
598 Useful for looking up nested objects via ``namespace.foo.bar.Class``.
599 """
600
601 def __init__(self, mapping: Mapping[str, str]) -> None:
602 super().__init__()
603 self.__mapping = mapping
604
605 def __getitem__(self, key: str) -> Any:
606 if key in self.__mapping:
607 # exactly matched
608 return TypeAliasForwardRef(self.__mapping[key])
609 else:
610 prefix = key + '.'
611 nested = {k: v for k, v in self.__mapping.items() if k.startswith(prefix)}
612 if nested:
613 # sub modules or classes found
614 return TypeAliasModule(key, nested)
615 else:
616 raise KeyError
617
618
619def _should_unwrap(subject: _SignatureType) -> bool:
620 """Check the function should be unwrapped on getting signature."""
621 __globals__ = getglobals(subject)
622 # contextmanger should be unwrapped
623 return (
624 __globals__.get('__name__') == 'contextlib'
625 and __globals__.get('__file__') == contextlib.__file__
626 )
627
628
629def signature(
630 subject: _SignatureType,
631 bound_method: bool = False,
632 type_aliases: Mapping[str, str] | None = None,
633) -> Signature:
634 """Return a Signature object for the given *subject*.
635
636 :param bound_method: Specify *subject* is a bound method or not
637 """
638 if type_aliases is None:
639 type_aliases = {}
640
641 try:
642 if _should_unwrap(subject):
643 signature = inspect.signature(subject) # type: ignore[arg-type]
644 else:
645 signature = inspect.signature(subject, follow_wrapped=True) # type: ignore[arg-type]
646 except ValueError:
647 # follow built-in wrappers up (ex. functools.lru_cache)
648 signature = inspect.signature(subject) # type: ignore[arg-type]
649 parameters = list(signature.parameters.values())
650 return_annotation = signature.return_annotation
651
652 try:
653 # Resolve annotations using ``get_type_hints()`` and type_aliases.
654 localns = TypeAliasNamespace(type_aliases)
655 annotations = typing.get_type_hints(subject, None, localns, include_extras=True)
656 for i, param in enumerate(parameters):
657 if param.name in annotations:
658 annotation = annotations[param.name]
659 if isinstance(annotation, TypeAliasForwardRef):
660 annotation = annotation.name
661 parameters[i] = param.replace(annotation=annotation)
662 if 'return' in annotations:
663 if isinstance(annotations['return'], TypeAliasForwardRef):
664 return_annotation = annotations['return'].name
665 else:
666 return_annotation = annotations['return']
667 except Exception:
668 # ``get_type_hints()`` does not support some kind of objects like partial,
669 # ForwardRef and so on.
670 pass
671
672 if bound_method:
673 if inspect.ismethod(subject):
674 # ``inspect.signature()`` considers the subject is a bound method and removes
675 # first argument from signature. Therefore no skips are needed here.
676 pass
677 else:
678 if len(parameters) > 0:
679 parameters.pop(0)
680
681 # To allow to create signature object correctly for pure python functions,
682 # pass an internal parameter __validate_parameters__=False to Signature
683 #
684 # For example, this helps a function having a default value `inspect._empty`.
685 # refs: https://github.com/sphinx-doc/sphinx/issues/7935
686 return Signature(
687 parameters, return_annotation=return_annotation, __validate_parameters__=False
688 )
689
690
691def evaluate_signature(
692 sig: Signature,
693 globalns: dict[str, Any] | None = None,
694 localns: dict[str, Any] | None = None,
695) -> Signature:
696 """Evaluate unresolved type annotations in a signature object."""
697 if globalns is None:
698 globalns = {}
699 if localns is None:
700 localns = globalns
701
702 parameters = list(sig.parameters.values())
703 for i, param in enumerate(parameters):
704 if param.annotation:
705 annotation = _evaluate(param.annotation, globalns, localns)
706 parameters[i] = param.replace(annotation=annotation)
707
708 return_annotation = sig.return_annotation
709 if return_annotation:
710 return_annotation = _evaluate(return_annotation, globalns, localns)
711
712 return sig.replace(parameters=parameters, return_annotation=return_annotation)
713
714
715def _evaluate_forwardref(
716 ref: ForwardRef,
717 globalns: dict[str, Any] | None,
718 localns: dict[str, Any] | None,
719) -> Any:
720 """Evaluate a forward reference."""
721 if sys.version_info >= (3, 12, 4):
722 # ``type_params`` were added in 3.13 and the signature of _evaluate()
723 # is not backward-compatible (it was backported to 3.12.4, so anything
724 # before 3.12.4 still has the old signature).
725 #
726 # See: https://github.com/python/cpython/pull/118104.
727 return ref._evaluate(globalns, localns, {}, recursive_guard=frozenset()) # type: ignore[arg-type, misc]
728 return ref._evaluate(globalns, localns, frozenset())
729
730
731def _evaluate(
732 annotation: Any,
733 globalns: dict[str, Any],
734 localns: dict[str, Any],
735) -> Any:
736 """Evaluate unresolved type annotation."""
737 try:
738 if isinstance(annotation, str):
739 ref = ForwardRef(annotation, True)
740 annotation = _evaluate_forwardref(ref, globalns, localns)
741
742 if isinstance(annotation, ForwardRef):
743 annotation = _evaluate_forwardref(ref, globalns, localns)
744 elif isinstance(annotation, str):
745 # might be a ForwardRef'ed annotation in overloaded functions
746 ref = ForwardRef(annotation, True)
747 annotation = _evaluate_forwardref(ref, globalns, localns)
748 except (NameError, TypeError):
749 # failed to evaluate type. skipped.
750 pass
751
752 return annotation
753
754
755def stringify_signature(
756 sig: Signature,
757 show_annotation: bool = True,
758 show_return_annotation: bool = True,
759 unqualified_typehints: bool = False,
760) -> str:
761 """Stringify a :class:`~inspect.Signature` object.
762
763 :param show_annotation: If enabled, show annotations on the signature
764 :param show_return_annotation: If enabled, show annotation of the return value
765 :param unqualified_typehints: If enabled, show annotations as unqualified
766 (ex. io.StringIO -> StringIO)
767 """
768 if unqualified_typehints:
769 mode = 'smart'
770 else:
771 mode = 'fully-qualified'
772
773 EMPTY = Parameter.empty
774
775 args = []
776 last_kind = None
777 for param in sig.parameters.values():
778 if param.kind != Parameter.POSITIONAL_ONLY and last_kind == Parameter.POSITIONAL_ONLY:
779 # PEP-570: Separator for Positional Only Parameter: /
780 args.append('/')
781 if param.kind == Parameter.KEYWORD_ONLY and last_kind in (
782 Parameter.POSITIONAL_OR_KEYWORD,
783 Parameter.POSITIONAL_ONLY,
784 None,
785 ):
786 # PEP-3102: Separator for Keyword Only Parameter: *
787 args.append('*')
788
789 arg = StringIO()
790 if param.kind is Parameter.VAR_POSITIONAL:
791 arg.write('*' + param.name)
792 elif param.kind is Parameter.VAR_KEYWORD:
793 arg.write('**' + param.name)
794 else:
795 arg.write(param.name)
796
797 if show_annotation and param.annotation is not EMPTY:
798 arg.write(': ')
799 arg.write(stringify_annotation(param.annotation, mode)) # type: ignore[arg-type]
800 if param.default is not EMPTY:
801 if show_annotation and param.annotation is not EMPTY:
802 arg.write(' = ')
803 else:
804 arg.write('=')
805 arg.write(object_description(param.default))
806
807 args.append(arg.getvalue())
808 last_kind = param.kind
809
810 if last_kind is Parameter.POSITIONAL_ONLY:
811 # PEP-570: Separator for Positional Only Parameter: /
812 args.append('/')
813
814 concatenated_args = ', '.join(args)
815 if sig.return_annotation is EMPTY or not show_annotation or not show_return_annotation:
816 return f'({concatenated_args})'
817 else:
818 retann = stringify_annotation(sig.return_annotation, mode) # type: ignore[arg-type]
819 return f'({concatenated_args}) -> {retann}'
820
821
822def signature_from_str(signature: str) -> Signature:
823 """Create a :class:`~inspect.Signature` object from a string."""
824 code = 'def func' + signature + ': pass'
825 module = ast.parse(code)
826 function = typing.cast(ast.FunctionDef, module.body[0])
827
828 return signature_from_ast(function, code)
829
830
831def signature_from_ast(node: ast.FunctionDef, code: str = '') -> Signature:
832 """Create a :class:`~inspect.Signature` object from an AST node."""
833 EMPTY = Parameter.empty
834
835 args: ast.arguments = node.args
836 defaults: tuple[ast.expr | None, ...] = tuple(args.defaults)
837 pos_only_offset = len(args.posonlyargs)
838 defaults_offset = pos_only_offset + len(args.args) - len(defaults)
839 # The sequence ``D = args.defaults`` contains non-None AST expressions,
840 # so we can use ``None`` as a sentinel value for that to indicate that
841 # there is no default value for a specific parameter.
842 #
843 # Let *p* be the number of positional-only and positional-or-keyword
844 # arguments. Note that ``0 <= len(D) <= p`` and ``D[0]`` is the default
845 # value corresponding to a positional-only *or* a positional-or-keyword
846 # argument. Since a non-default argument cannot follow a default argument,
847 # the sequence *D* can be completed on the left by adding None sentinels
848 # so that ``len(D) == p`` and ``D[i]`` is the *i*-th default argument.
849 defaults = (None,) * defaults_offset + defaults
850
851 # construct the parameter list
852 params: list[Parameter] = []
853
854 # positional-only arguments (introduced in Python 3.8)
855 for arg, defexpr in zip(args.posonlyargs, defaults):
856 params.append(_define(Parameter.POSITIONAL_ONLY, arg, code, defexpr=defexpr))
857
858 # normal arguments
859 for arg, defexpr in zip(args.args, defaults[pos_only_offset:]):
860 params.append(_define(Parameter.POSITIONAL_OR_KEYWORD, arg, code, defexpr=defexpr))
861
862 # variadic positional argument (no possible default expression)
863 if args.vararg:
864 params.append(_define(Parameter.VAR_POSITIONAL, args.vararg, code, defexpr=None))
865
866 # keyword-only arguments
867 for arg, defexpr in zip(args.kwonlyargs, args.kw_defaults):
868 params.append(_define(Parameter.KEYWORD_ONLY, arg, code, defexpr=defexpr))
869
870 # variadic keyword argument (no possible default expression)
871 if args.kwarg:
872 params.append(_define(Parameter.VAR_KEYWORD, args.kwarg, code, defexpr=None))
873
874 return_annotation = ast_unparse(node.returns, code) or EMPTY
875 return Signature(params, return_annotation=return_annotation)
876
877
878def _define(
879 kind: _ParameterKind,
880 arg: ast.arg,
881 code: str,
882 *,
883 defexpr: ast.expr | None,
884) -> Parameter:
885 EMPTY = Parameter.empty
886
887 default = EMPTY if defexpr is None else DefaultValue(ast_unparse(defexpr, code))
888 annotation = ast_unparse(arg.annotation, code) or EMPTY
889 return Parameter(arg.arg, kind, default=default, annotation=annotation)
890
891
892def getdoc(
893 obj: Any,
894 attrgetter: Callable = safe_getattr,
895 allow_inherited: bool = False,
896 cls: Any = None,
897 name: str | None = None,
898) -> str | None:
899 """Get the docstring for the object.
900
901 This tries to obtain the docstring for some kind of objects additionally:
902
903 * partial functions
904 * inherited docstring
905 * inherited decorated methods
906 """
907 if cls and name and isclassmethod(obj, cls, name):
908 for basecls in getmro(cls):
909 meth = basecls.__dict__.get(name)
910 if meth and hasattr(meth, '__func__'):
911 doc: str | None = getdoc(meth.__func__)
912 if doc is not None or not allow_inherited:
913 return doc
914
915 doc = _getdoc_internal(obj)
916 if ispartial(obj) and doc == obj.__class__.__doc__:
917 return getdoc(obj.func)
918 elif doc is None and allow_inherited:
919 if cls and name:
920 # Check a docstring of the attribute or method from super classes.
921 for basecls in getmro(cls):
922 meth = safe_getattr(basecls, name, None)
923 if meth is not None:
924 doc = _getdoc_internal(meth)
925 if doc is not None:
926 break
927
928 if doc is None:
929 # retry using `inspect.getdoc()`
930 for basecls in getmro(cls):
931 meth = safe_getattr(basecls, name, None)
932 if meth is not None:
933 doc = inspect.getdoc(meth)
934 if doc is not None:
935 break
936
937 if doc is None:
938 doc = inspect.getdoc(obj)
939
940 return doc
941
942
943def _getdoc_internal(
944 obj: Any, attrgetter: Callable[[Any, str, Any], Any] = safe_getattr
945) -> str | None:
946 doc = attrgetter(obj, '__doc__', None)
947 if isinstance(doc, str):
948 return doc
949 return None