summaryrefslogtreecommitdiffstats
path: root/tests/test_util_inspect.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_util_inspect.py')
-rw-r--r--tests/test_util_inspect.py869
1 files changed, 869 insertions, 0 deletions
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
new file mode 100644
index 0000000..73f9656
--- /dev/null
+++ b/tests/test_util_inspect.py
@@ -0,0 +1,869 @@
+"""Tests util.inspect functions."""
+
+from __future__ import annotations
+
+import ast
+import datetime
+import enum
+import functools
+import sys
+import types
+from inspect import Parameter
+from typing import Callable, List, Optional, Union # NoQA: UP035
+
+import pytest
+
+from sphinx.util import inspect
+from sphinx.util.inspect import TypeAliasForwardRef, TypeAliasNamespace, stringify_signature
+from sphinx.util.typing import stringify_annotation
+
+
+class Base:
+ def meth(self):
+ pass
+
+ @staticmethod
+ def staticmeth():
+ pass
+
+ @classmethod
+ def classmeth(cls):
+ pass
+
+ @property
+ def prop(self):
+ pass
+
+ partialmeth = functools.partialmethod(meth)
+
+ async def coroutinemeth(self):
+ pass
+
+ partial_coroutinemeth = functools.partialmethod(coroutinemeth)
+
+ @classmethod
+ async def coroutineclassmeth(cls):
+ """A documented coroutine classmethod"""
+ pass
+
+
+class Inherited(Base):
+ pass
+
+
+def func():
+ pass
+
+
+async def coroutinefunc():
+ pass
+
+
+async def asyncgenerator():
+ yield
+
+partial_func = functools.partial(func)
+partial_coroutinefunc = functools.partial(coroutinefunc)
+
+builtin_func = print
+partial_builtin_func = functools.partial(print)
+
+
+class Descriptor:
+ def __get__(self, obj, typ=None):
+ pass
+
+
+class _Callable:
+ def __call__(self):
+ pass
+
+
+def _decorator(f):
+ @functools.wraps(f)
+ def wrapper():
+ return f()
+ return wrapper
+
+
+def test_TypeAliasForwardRef():
+ alias = TypeAliasForwardRef('example')
+ assert stringify_annotation(alias, 'fully-qualified-except-typing') == 'example'
+
+ alias = Optional[alias]
+ assert stringify_annotation(alias, 'fully-qualified-except-typing') == 'example | None'
+
+
+def test_TypeAliasNamespace():
+ import logging.config
+ type_alias = TypeAliasNamespace({'logging.Filter': 'MyFilter',
+ 'logging.Handler': 'MyHandler',
+ 'logging.handlers.SyslogHandler': 'MySyslogHandler'})
+
+ assert type_alias['logging'].Filter == 'MyFilter'
+ assert type_alias['logging'].Handler == 'MyHandler'
+ assert type_alias['logging'].handlers.SyslogHandler == 'MySyslogHandler'
+ assert type_alias['logging'].Logger == logging.Logger
+ assert type_alias['logging'].config == logging.config
+
+ with pytest.raises(KeyError):
+ assert type_alias['log']
+
+ with pytest.raises(KeyError):
+ assert type_alias['unknown']
+
+
+def test_signature():
+ # literals
+ with pytest.raises(TypeError):
+ inspect.signature(1)
+
+ with pytest.raises(TypeError):
+ inspect.signature('')
+
+ # builtins are supported on a case-by-case basis, depending on whether
+ # they define __text_signature__
+ if getattr(list, '__text_signature__', None):
+ sig = inspect.stringify_signature(inspect.signature(list))
+ assert sig == '(iterable=(), /)'
+ else:
+ with pytest.raises(ValueError, match='no signature found for builtin type'):
+ inspect.signature(list)
+ with pytest.raises(ValueError, match='no signature found for builtin type'):
+ inspect.signature(range)
+
+ # normal function
+ def func(a, b, c=1, d=2, *e, **f):
+ pass
+
+ sig = inspect.stringify_signature(inspect.signature(func))
+ assert sig == '(a, b, c=1, d=2, *e, **f)'
+
+
+def test_signature_partial():
+ def fun(a, b, c=1, d=2):
+ pass
+ p = functools.partial(fun, 10, c=11)
+
+ sig = inspect.signature(p)
+ assert stringify_signature(sig) == '(b, *, c=11, d=2)'
+
+
+def test_signature_methods():
+ class Foo:
+ def meth1(self, arg1, **kwargs):
+ pass
+
+ @classmethod
+ def meth2(cls, arg1, *args, **kwargs):
+ pass
+
+ @staticmethod
+ def meth3(arg1, *args, **kwargs):
+ pass
+
+ @functools.wraps(Foo().meth1)
+ def wrapped_bound_method(*args, **kwargs):
+ pass
+
+ # unbound method
+ sig = inspect.signature(Foo.meth1)
+ assert stringify_signature(sig) == '(self, arg1, **kwargs)'
+
+ sig = inspect.signature(Foo.meth1, bound_method=True)
+ assert stringify_signature(sig) == '(arg1, **kwargs)'
+
+ # bound method
+ sig = inspect.signature(Foo().meth1)
+ assert stringify_signature(sig) == '(arg1, **kwargs)'
+
+ # class method
+ sig = inspect.signature(Foo.meth2)
+ assert stringify_signature(sig) == '(arg1, *args, **kwargs)'
+
+ sig = inspect.signature(Foo().meth2)
+ assert stringify_signature(sig) == '(arg1, *args, **kwargs)'
+
+ # static method
+ sig = inspect.signature(Foo.meth3)
+ assert stringify_signature(sig) == '(arg1, *args, **kwargs)'
+
+ sig = inspect.signature(Foo().meth3)
+ assert stringify_signature(sig) == '(arg1, *args, **kwargs)'
+
+ # wrapped bound method
+ sig = inspect.signature(wrapped_bound_method)
+ assert stringify_signature(sig) == '(arg1, **kwargs)'
+
+
+def test_signature_partialmethod():
+ from functools import partialmethod
+
+ class Foo:
+ def meth1(self, arg1, arg2, arg3=None, arg4=None):
+ pass
+
+ def meth2(self, arg1, arg2):
+ pass
+
+ foo = partialmethod(meth1, 1, 2)
+ bar = partialmethod(meth1, 1, arg3=3)
+ baz = partialmethod(meth2, 1, 2)
+
+ subject = Foo()
+ sig = inspect.signature(subject.foo)
+ assert stringify_signature(sig) == '(arg3=None, arg4=None)'
+
+ sig = inspect.signature(subject.bar)
+ assert stringify_signature(sig) == '(arg2, *, arg3=3, arg4=None)'
+
+ sig = inspect.signature(subject.baz)
+ assert stringify_signature(sig) == '()'
+
+
+def test_signature_annotations():
+ from .typing_test_data import (
+ Node,
+ f0,
+ f1,
+ f2,
+ f3,
+ f4,
+ f5,
+ f6,
+ f7,
+ f8,
+ f9,
+ f10,
+ f11,
+ f12,
+ f13,
+ f14,
+ f15,
+ f16,
+ f17,
+ f18,
+ f19,
+ f20,
+ f21,
+ f22,
+ f23,
+ f24,
+ f25,
+ )
+
+ # Class annotations
+ sig = inspect.signature(f0)
+ assert stringify_signature(sig) == '(x: int, y: numbers.Integral) -> None'
+
+ # Generic types with concrete parameters
+ sig = inspect.signature(f1)
+ assert stringify_signature(sig) == '(x: list[int]) -> typing.List[int]'
+
+ # TypeVars and generic types with TypeVars
+ sig = inspect.signature(f2)
+ assert stringify_signature(sig) == ('(x: typing.List[tests.typing_test_data.T],'
+ ' y: typing.List[tests.typing_test_data.T_co],'
+ ' z: tests.typing_test_data.T'
+ ') -> typing.List[tests.typing_test_data.T_contra]')
+
+ # Union types
+ sig = inspect.signature(f3)
+ assert stringify_signature(sig) == '(x: str | numbers.Integral) -> None'
+
+ # Quoted annotations
+ sig = inspect.signature(f4)
+ assert stringify_signature(sig) == '(x: str, y: str) -> None'
+
+ # Keyword-only arguments
+ sig = inspect.signature(f5)
+ assert stringify_signature(sig) == '(x: int, *, y: str, z: str) -> None'
+
+ # Keyword-only arguments with varargs
+ sig = inspect.signature(f6)
+ assert stringify_signature(sig) == '(x: int, *args, y: str, z: str) -> None'
+
+ # Space around '=' for defaults
+ sig = inspect.signature(f7)
+ if sys.version_info[:2] <= (3, 10):
+ assert stringify_signature(sig) == '(x: int | None = None, y: dict = {}) -> None'
+ else:
+ assert stringify_signature(sig) == '(x: int = None, y: dict = {}) -> None'
+
+ # Callable types
+ sig = inspect.signature(f8)
+ assert stringify_signature(sig) == '(x: typing.Callable[[int, str], int]) -> None'
+
+ sig = inspect.signature(f9)
+ assert stringify_signature(sig) == '(x: typing.Callable) -> None'
+
+ # Tuple types
+ sig = inspect.signature(f10)
+ assert stringify_signature(sig) == '(x: typing.Tuple[int, str], y: typing.Tuple[int, ...]) -> None'
+
+ # Instance annotations
+ sig = inspect.signature(f11)
+ assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
+
+ # tuple with more than two items
+ sig = inspect.signature(f12)
+ assert stringify_signature(sig) == '() -> typing.Tuple[int, str, int]'
+
+ # optional
+ sig = inspect.signature(f13)
+ assert stringify_signature(sig) == '() -> str | None'
+
+ # optional union
+ sig = inspect.signature(f20)
+ assert stringify_signature(sig) in ('() -> int | str | None',
+ '() -> str | int | None')
+
+ # Any
+ sig = inspect.signature(f14)
+ assert stringify_signature(sig) == '() -> typing.Any'
+
+ # ForwardRef
+ sig = inspect.signature(f15)
+ assert stringify_signature(sig) == '(x: Unknown, y: int) -> typing.Any'
+
+ # keyword only arguments (1)
+ sig = inspect.signature(f16)
+ assert stringify_signature(sig) == '(arg1, arg2, *, arg3=None, arg4=None)'
+
+ # keyword only arguments (2)
+ sig = inspect.signature(f17)
+ assert stringify_signature(sig) == '(*, arg3, arg4)'
+
+ sig = inspect.signature(f18)
+ assert stringify_signature(sig) == ('(self, arg1: int | typing.Tuple = 10) -> '
+ 'typing.List[typing.Dict]')
+
+ # annotations for variadic and keyword parameters
+ sig = inspect.signature(f19)
+ assert stringify_signature(sig) == '(*args: int, **kwargs: str)'
+
+ # default value is inspect.Signature.empty
+ sig = inspect.signature(f21)
+ assert stringify_signature(sig) == "(arg1='whatever', arg2)"
+
+ # type hints by string
+ sig = inspect.signature(Node.children)
+ assert stringify_signature(sig) == '(self) -> typing.List[tests.typing_test_data.Node]'
+
+ sig = inspect.signature(Node.__init__)
+ assert stringify_signature(sig) == '(self, parent: tests.typing_test_data.Node | None) -> None'
+
+ # show_annotation is False
+ sig = inspect.signature(f7)
+ assert stringify_signature(sig, show_annotation=False) == '(x=None, y={})'
+
+ # show_return_annotation is False
+ sig = inspect.signature(f7)
+ if sys.version_info[:2] <= (3, 10):
+ assert stringify_signature(sig, show_return_annotation=False) == '(x: int | None = None, y: dict = {})'
+ else:
+ assert stringify_signature(sig, show_return_annotation=False) == '(x: int = None, y: dict = {})'
+
+ # unqualified_typehints is True
+ sig = inspect.signature(f7)
+ if sys.version_info[:2] <= (3, 10):
+ assert stringify_signature(sig, unqualified_typehints=True) == '(x: int | None = None, y: dict = {}) -> None'
+ else:
+ assert stringify_signature(sig, unqualified_typehints=True) == '(x: int = None, y: dict = {}) -> None'
+
+ # case: separator at head
+ sig = inspect.signature(f22)
+ assert stringify_signature(sig) == '(*, a, b)'
+
+ # case: separator in the middle
+ sig = inspect.signature(f23)
+ assert stringify_signature(sig) == '(a, b, /, c, d)'
+
+ sig = inspect.signature(f24)
+ assert stringify_signature(sig) == '(a, /, *, b)'
+
+ # case: separator at tail
+ sig = inspect.signature(f25)
+ assert stringify_signature(sig) == '(a, b, /)'
+
+
+def test_signature_from_str_basic():
+ signature = '(a, b, *args, c=0, d="blah", **kwargs)'
+ sig = inspect.signature_from_str(signature)
+ assert list(sig.parameters.keys()) == ['a', 'b', 'args', 'c', 'd', 'kwargs']
+ assert sig.parameters['a'].name == 'a'
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['a'].default == Parameter.empty
+ assert sig.parameters['a'].annotation == Parameter.empty
+ assert sig.parameters['b'].name == 'b'
+ assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['b'].default == Parameter.empty
+ assert sig.parameters['b'].annotation == Parameter.empty
+ assert sig.parameters['args'].name == 'args'
+ assert sig.parameters['args'].kind == Parameter.VAR_POSITIONAL
+ assert sig.parameters['args'].default == Parameter.empty
+ assert sig.parameters['args'].annotation == Parameter.empty
+ assert sig.parameters['c'].name == 'c'
+ assert sig.parameters['c'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['c'].default == '0'
+ assert sig.parameters['c'].annotation == Parameter.empty
+ assert sig.parameters['d'].name == 'd'
+ assert sig.parameters['d'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['d'].default == "'blah'"
+ assert sig.parameters['d'].annotation == Parameter.empty
+ assert sig.parameters['kwargs'].name == 'kwargs'
+ assert sig.parameters['kwargs'].kind == Parameter.VAR_KEYWORD
+ assert sig.parameters['kwargs'].default == Parameter.empty
+ assert sig.parameters['kwargs'].annotation == Parameter.empty
+ assert sig.return_annotation == Parameter.empty
+
+
+def test_signature_from_str_default_values():
+ signature = ('(a=0, b=0.0, c="str", d=b"bytes", e=..., f=True, '
+ 'g=[1, 2, 3], h={"a": 1}, i={1, 2, 3}, '
+ 'j=lambda x, y: None, k=None, l=object(), m=foo.bar.CONSTANT)')
+ sig = inspect.signature_from_str(signature)
+ assert sig.parameters['a'].default == '0'
+ assert sig.parameters['b'].default == '0.0'
+ assert sig.parameters['c'].default == "'str'"
+ assert sig.parameters['d'].default == "b'bytes'"
+ assert sig.parameters['e'].default == '...'
+ assert sig.parameters['f'].default == 'True'
+ assert sig.parameters['g'].default == '[1, 2, 3]'
+ assert sig.parameters['h'].default == "{'a': 1}"
+ assert sig.parameters['i'].default == '{1, 2, 3}'
+ assert sig.parameters['j'].default == 'lambda x, y: ...'
+ assert sig.parameters['k'].default == 'None'
+ assert sig.parameters['l'].default == 'object()'
+ assert sig.parameters['m'].default == 'foo.bar.CONSTANT'
+
+
+def test_signature_from_str_annotations():
+ signature = '(a: int, *args: bytes, b: str = "blah", **kwargs: float) -> None'
+ sig = inspect.signature_from_str(signature)
+ assert list(sig.parameters.keys()) == ['a', 'args', 'b', 'kwargs']
+ assert sig.parameters['a'].annotation == "int"
+ assert sig.parameters['args'].annotation == "bytes"
+ assert sig.parameters['b'].annotation == "str"
+ assert sig.parameters['kwargs'].annotation == "float"
+ assert sig.return_annotation == 'None'
+
+
+def test_signature_from_str_complex_annotations():
+ sig = inspect.signature_from_str('() -> Tuple[str, int, ...]')
+ assert sig.return_annotation == 'Tuple[str, int, ...]'
+
+ sig = inspect.signature_from_str('() -> Callable[[int, int], int]')
+ assert sig.return_annotation == 'Callable[[int, int], int]'
+
+
+def test_signature_from_str_kwonly_args():
+ sig = inspect.signature_from_str('(a, *, b)')
+ assert list(sig.parameters.keys()) == ['a', 'b']
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['a'].default == Parameter.empty
+ assert sig.parameters['b'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['b'].default == Parameter.empty
+
+
+def test_signature_from_str_positionaly_only_args():
+ sig = inspect.signature_from_str('(a, b=0, /, c=1)')
+ assert list(sig.parameters.keys()) == ['a', 'b', 'c']
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_ONLY
+ assert sig.parameters['a'].default == Parameter.empty
+ assert sig.parameters['b'].kind == Parameter.POSITIONAL_ONLY
+ assert sig.parameters['b'].default == '0'
+ assert sig.parameters['c'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['c'].default == '1'
+
+
+def test_signature_from_str_invalid():
+ with pytest.raises(SyntaxError):
+ inspect.signature_from_str('')
+
+
+def test_signature_from_ast():
+ signature = 'def func(a, b, *args, c=0, d="blah", **kwargs): pass'
+ tree = ast.parse(signature)
+ sig = inspect.signature_from_ast(tree.body[0])
+ assert list(sig.parameters.keys()) == ['a', 'b', 'args', 'c', 'd', 'kwargs']
+ assert sig.parameters['a'].name == 'a'
+ assert sig.parameters['a'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['a'].default == Parameter.empty
+ assert sig.parameters['a'].annotation == Parameter.empty
+ assert sig.parameters['b'].name == 'b'
+ assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
+ assert sig.parameters['b'].default == Parameter.empty
+ assert sig.parameters['b'].annotation == Parameter.empty
+ assert sig.parameters['args'].name == 'args'
+ assert sig.parameters['args'].kind == Parameter.VAR_POSITIONAL
+ assert sig.parameters['args'].default == Parameter.empty
+ assert sig.parameters['args'].annotation == Parameter.empty
+ assert sig.parameters['c'].name == 'c'
+ assert sig.parameters['c'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['c'].default == '0'
+ assert sig.parameters['c'].annotation == Parameter.empty
+ assert sig.parameters['d'].name == 'd'
+ assert sig.parameters['d'].kind == Parameter.KEYWORD_ONLY
+ assert sig.parameters['d'].default == "'blah'"
+ assert sig.parameters['d'].annotation == Parameter.empty
+ assert sig.parameters['kwargs'].name == 'kwargs'
+ assert sig.parameters['kwargs'].kind == Parameter.VAR_KEYWORD
+ assert sig.parameters['kwargs'].default == Parameter.empty
+ assert sig.parameters['kwargs'].annotation == Parameter.empty
+ assert sig.return_annotation == Parameter.empty
+
+
+def test_safe_getattr_with_default():
+ class Foo:
+ def __getattr__(self, item):
+ raise Exception
+
+ obj = Foo()
+
+ result = inspect.safe_getattr(obj, 'bar', 'baz')
+
+ assert result == 'baz'
+
+
+def test_safe_getattr_with_exception():
+ class Foo:
+ def __getattr__(self, item):
+ raise Exception
+
+ obj = Foo()
+
+ with pytest.raises(AttributeError, match='bar'):
+ inspect.safe_getattr(obj, 'bar')
+
+
+def test_safe_getattr_with_property_exception():
+ class Foo:
+ @property
+ def bar(self):
+ raise Exception
+
+ obj = Foo()
+
+ with pytest.raises(AttributeError, match='bar'):
+ inspect.safe_getattr(obj, 'bar')
+
+
+def test_safe_getattr_with___dict___override():
+ class Foo:
+ @property
+ def __dict__(self):
+ raise Exception
+
+ obj = Foo()
+
+ with pytest.raises(AttributeError, match='bar'):
+ inspect.safe_getattr(obj, 'bar')
+
+
+def test_dictionary_sorting():
+ dictionary = {"c": 3, "a": 1, "d": 2, "b": 4}
+ description = inspect.object_description(dictionary)
+ assert description == "{'a': 1, 'b': 4, 'c': 3, 'd': 2}"
+
+
+def test_set_sorting():
+ set_ = set("gfedcba")
+ description = inspect.object_description(set_)
+ assert description == "{'a', 'b', 'c', 'd', 'e', 'f', 'g'}"
+
+
+def test_set_sorting_enum():
+ class MyEnum(enum.Enum):
+ a = 1
+ b = 2
+ c = 3
+
+ set_ = set(MyEnum)
+ description = inspect.object_description(set_)
+ assert description == "{MyEnum.a, MyEnum.b, MyEnum.c}"
+
+
+def test_set_sorting_fallback():
+ set_ = {None, 1}
+ description = inspect.object_description(set_)
+ assert description == "{1, None}"
+
+
+def test_deterministic_nested_collection_descriptions():
+ # sortable
+ assert inspect.object_description([{1, 2, 3, 10}]) == "[{1, 2, 3, 10}]"
+ assert inspect.object_description(({1, 2, 3, 10},)) == "({1, 2, 3, 10},)"
+ # non-sortable (elements of varying datatype)
+ assert inspect.object_description([{None, 1}]) == "[{1, None}]"
+ assert inspect.object_description(({None, 1},)) == "({1, None},)"
+ assert inspect.object_description([{None, 1, 'A'}]) == "[{'A', 1, None}]"
+ assert inspect.object_description(({None, 1, 'A'},)) == "({'A', 1, None},)"
+
+
+def test_frozenset_sorting():
+ frozenset_ = frozenset("gfedcba")
+ description = inspect.object_description(frozenset_)
+ assert description == "frozenset({'a', 'b', 'c', 'd', 'e', 'f', 'g'})"
+
+
+def test_frozenset_sorting_fallback():
+ frozenset_ = frozenset((None, 1))
+ description = inspect.object_description(frozenset_)
+ assert description == "frozenset({1, None})"
+
+
+def test_nested_tuple_sorting():
+ tuple_ = ({"c", "b", "a"},) # nb. trailing comma
+ description = inspect.object_description(tuple_)
+ assert description == "({'a', 'b', 'c'},)"
+
+ tuple_ = ({"c", "b", "a"}, {"f", "e", "d"})
+ description = inspect.object_description(tuple_)
+ assert description == "({'a', 'b', 'c'}, {'d', 'e', 'f'})"
+
+
+def test_recursive_collection_description():
+ dict_a_, dict_b_ = {"a": 1}, {"b": 2}
+ dict_a_["link"], dict_b_["link"] = dict_b_, dict_a_
+ description_a, description_b = (
+ inspect.object_description(dict_a_),
+ inspect.object_description(dict_b_),
+ )
+ assert description_a == "{'a': 1, 'link': {'b': 2, 'link': dict(...)}}"
+ assert description_b == "{'b': 2, 'link': {'a': 1, 'link': dict(...)}}"
+
+ list_c_, list_d_ = [1, 2, 3, 4], [5, 6, 7, 8]
+ list_c_.append(list_d_)
+ list_d_.append(list_c_)
+ description_c, description_d = (
+ inspect.object_description(list_c_),
+ inspect.object_description(list_d_),
+ )
+
+ assert description_c == "[1, 2, 3, 4, [5, 6, 7, 8, list(...)]]"
+ assert description_d == "[5, 6, 7, 8, [1, 2, 3, 4, list(...)]]"
+
+
+def test_dict_customtype():
+ class CustomType:
+ def __init__(self, value):
+ self._value = value
+
+ def __repr__(self):
+ return "<CustomType(%r)>" % self._value
+
+ dictionary = {CustomType(2): 2, CustomType(1): 1}
+ description = inspect.object_description(dictionary)
+ # Type is unsortable, just check that it does not crash
+ assert "<CustomType(2)>: 2" in description
+
+
+def test_object_description_enum():
+ class MyEnum(enum.Enum):
+ FOO = 1
+ BAR = 2
+
+ assert inspect.object_description(MyEnum.FOO) == "MyEnum.FOO"
+
+
+def test_getslots():
+ class Foo:
+ pass
+
+ class Bar:
+ __slots__ = ['attr']
+
+ class Baz:
+ __slots__ = {'attr': 'docstring'}
+
+ class Qux:
+ __slots__ = 'attr'
+
+ assert inspect.getslots(Foo) is None
+ assert inspect.getslots(Bar) == {'attr': None}
+ assert inspect.getslots(Baz) == {'attr': 'docstring'}
+ assert inspect.getslots(Qux) == {'attr': None}
+
+ with pytest.raises(TypeError):
+ inspect.getslots(Bar())
+
+
+def test_isclassmethod():
+ assert inspect.isclassmethod(Base.classmeth) is True
+ assert inspect.isclassmethod(Base.meth) is False
+ assert inspect.isclassmethod(Inherited.classmeth) is True
+ assert inspect.isclassmethod(Inherited.meth) is False
+
+
+def test_isstaticmethod():
+ assert inspect.isstaticmethod(Base.staticmeth, Base, 'staticmeth') is True
+ assert inspect.isstaticmethod(Base.meth, Base, 'meth') is False
+ assert inspect.isstaticmethod(Inherited.staticmeth, Inherited, 'staticmeth') is True
+ assert inspect.isstaticmethod(Inherited.meth, Inherited, 'meth') is False
+
+
+def test_iscoroutinefunction():
+ assert inspect.iscoroutinefunction(func) is False # function
+ assert inspect.iscoroutinefunction(coroutinefunc) is True # coroutine
+ assert inspect.iscoroutinefunction(partial_coroutinefunc) is True # partial-ed coroutine
+ assert inspect.iscoroutinefunction(Base.meth) is False # method
+ assert inspect.iscoroutinefunction(Base.coroutinemeth) is True # coroutine-method
+ assert inspect.iscoroutinefunction(Base.__dict__["coroutineclassmeth"]) is True # coroutine classmethod
+
+ # partial-ed coroutine-method
+ partial_coroutinemeth = Base.__dict__['partial_coroutinemeth']
+ assert inspect.iscoroutinefunction(partial_coroutinemeth) is True
+
+
+def test_iscoroutinefunction_wrapped():
+ # function wrapping a callable obj
+ assert inspect.isfunction(_decorator(coroutinefunc)) is True
+
+
+def test_isfunction():
+ assert inspect.isfunction(func) is True # function
+ assert inspect.isfunction(partial_func) is True # partial-ed function
+ assert inspect.isfunction(Base.meth) is True # method of class
+ assert inspect.isfunction(Base.partialmeth) is True # partial-ed method of class
+ assert inspect.isfunction(Base().meth) is False # method of instance
+ assert inspect.isfunction(builtin_func) is False # builtin function
+ assert inspect.isfunction(partial_builtin_func) is False # partial-ed builtin function
+
+
+def test_isfunction_wrapped():
+ # function wrapping a callable obj
+ assert inspect.isfunction(_decorator(_Callable())) is True
+
+
+def test_isbuiltin():
+ assert inspect.isbuiltin(builtin_func) is True # builtin function
+ assert inspect.isbuiltin(partial_builtin_func) is True # partial-ed builtin function
+ assert inspect.isbuiltin(func) is False # function
+ assert inspect.isbuiltin(partial_func) is False # partial-ed function
+ assert inspect.isbuiltin(Base.meth) is False # method of class
+ assert inspect.isbuiltin(Base().meth) is False # method of instance
+
+
+def test_isdescriptor():
+ assert inspect.isdescriptor(Base.prop) is True # property of class
+ assert inspect.isdescriptor(Base().prop) is False # property of instance
+ assert inspect.isdescriptor(Base.meth) is True # method of class
+ assert inspect.isdescriptor(Base().meth) is True # method of instance
+ assert inspect.isdescriptor(func) is True # function
+
+
+def test_isattributedescriptor():
+ assert inspect.isattributedescriptor(Base.prop) is True # property
+ assert inspect.isattributedescriptor(Base.meth) is False # method
+ assert inspect.isattributedescriptor(Base.staticmeth) is False # staticmethod
+ assert inspect.isattributedescriptor(Base.classmeth) is False # classmetho
+ assert inspect.isattributedescriptor(Descriptor) is False # custom descriptor class
+ assert inspect.isattributedescriptor(str.join) is False # MethodDescriptorType
+ assert inspect.isattributedescriptor(object.__init__) is False # WrapperDescriptorType
+ assert inspect.isattributedescriptor(dict.__dict__['fromkeys']) is False # ClassMethodDescriptorType
+ assert inspect.isattributedescriptor(types.FrameType.f_locals) is True # GetSetDescriptorType
+ assert inspect.isattributedescriptor(datetime.timedelta.days) is True # MemberDescriptorType
+
+ try:
+ # _testcapi module cannot be importable in some distro
+ # refs: https://github.com/sphinx-doc/sphinx/issues/9868
+ import _testcapi
+
+ testinstancemethod = _testcapi.instancemethod(str.__repr__)
+ assert inspect.isattributedescriptor(testinstancemethod) is False # instancemethod (C-API)
+ except ImportError:
+ pass
+
+
+def test_isproperty():
+ assert inspect.isproperty(Base.prop) is True # property of class
+ assert inspect.isproperty(Base().prop) is False # property of instance
+ assert inspect.isproperty(Base.meth) is False # method of class
+ assert inspect.isproperty(Base().meth) is False # method of instance
+ assert inspect.isproperty(func) is False # function
+
+
+def test_isgenericalias():
+ #: A list of int
+ T = List[int] # NoQA: UP006
+ S = list[Union[str, None]]
+
+ C = Callable[[int], None] # a generic alias not having a doccomment
+
+ assert inspect.isgenericalias(C) is True
+ assert inspect.isgenericalias(Callable) is True
+ assert inspect.isgenericalias(T) is True
+ assert inspect.isgenericalias(List) is True # NoQA: UP006
+ assert inspect.isgenericalias(S) is True
+ assert inspect.isgenericalias(list) is False
+ assert inspect.isgenericalias([]) is False
+ assert inspect.isgenericalias(object()) is False
+ assert inspect.isgenericalias(Base) is False
+
+
+def test_unpartial():
+ def func1(a, b, c):
+ pass
+
+ func2 = functools.partial(func1, 1)
+ func2.__doc__ = "func2"
+ func3 = functools.partial(func2, 2) # nested partial object
+
+ assert inspect.unpartial(func2) is func1
+ assert inspect.unpartial(func3) is func1
+
+
+def test_getdoc_inherited_classmethod():
+ class Foo:
+ @classmethod
+ def meth(self):
+ """
+ docstring
+ indented text
+ """
+
+ class Bar(Foo):
+ @classmethod
+ def meth(self):
+ # inherited classmethod
+ pass
+
+ assert inspect.getdoc(Bar.meth, getattr, False, Bar, "meth") is None
+ assert inspect.getdoc(Bar.meth, getattr, True, Bar, "meth") == Foo.meth.__doc__
+
+
+def test_getdoc_inherited_decorated_method():
+ class Foo:
+ def meth(self):
+ """
+ docstring
+ indented text
+ """
+
+ class Bar(Foo):
+ @functools.lru_cache # noqa: B019
+ def meth(self):
+ # inherited and decorated method
+ pass
+
+ assert inspect.getdoc(Bar.meth, getattr, False, Bar, "meth") is None
+ assert inspect.getdoc(Bar.meth, getattr, True, Bar, "meth") == Foo.meth.__doc__
+
+
+def test_is_builtin_class_method():
+ class MyInt(int):
+ def my_method(self):
+ pass
+
+ assert inspect.is_builtin_class_method(MyInt, 'to_bytes')
+ assert inspect.is_builtin_class_method(MyInt, '__init__')
+ assert not inspect.is_builtin_class_method(MyInt, 'my_method')
+ assert not inspect.is_builtin_class_method(MyInt, 'does_not_exist')
+ assert not inspect.is_builtin_class_method(4, 'still does not crash')
+
+ class ObjectWithMroAttr:
+ def __init__(self, mro_attr):
+ self.__mro__ = mro_attr
+
+ assert not inspect.is_builtin_class_method(ObjectWithMroAttr([1, 2, 3]), 'still does not crash')