sphinx/tests/test_util/test_util_inspect.py
2025-01-02 23:49:43 +00:00

896 lines
29 KiB
Python

"""Tests util.inspect functions."""
from __future__ import annotations
import ast
import datetime
import enum
import functools
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(): # NoQA: RUF029
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')
sig_str = stringify_annotation(alias, 'fully-qualified-except-typing')
assert sig_str == "TypeAliasForwardRef('example')"
alias = Optional[alias] # NoQA: UP007
sig_str = stringify_annotation(alias, 'fully-qualified-except-typing')
assert sig_str == "TypeAliasForwardRef('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():
import tests.test_util.typing_test_data as mod
# Class annotations
sig = inspect.signature(mod.f0)
assert stringify_signature(sig) == '(x: int, y: numbers.Integral) -> None'
# Generic types with concrete parameters
sig = inspect.signature(mod.f1)
assert stringify_signature(sig) == '(x: list[int]) -> typing.List[int]'
# TypeVars and generic types with TypeVars
sig = inspect.signature(mod.f2)
assert stringify_signature(sig) == (
'(x: typing.List[tests.test_util.typing_test_data.T],'
' y: typing.List[tests.test_util.typing_test_data.T_co],'
' z: tests.test_util.typing_test_data.T'
') -> typing.List[tests.test_util.typing_test_data.T_contra]'
)
# Union types
sig = inspect.signature(mod.f3)
assert stringify_signature(sig) == '(x: str | numbers.Integral) -> None'
# Quoted annotations
sig = inspect.signature(mod.f4)
assert stringify_signature(sig) == '(x: str, y: str) -> None'
# Keyword-only arguments
sig = inspect.signature(mod.f5)
assert stringify_signature(sig) == '(x: int, *, y: str, z: str) -> None'
# Keyword-only arguments with varargs
sig = inspect.signature(mod.f6)
assert stringify_signature(sig) == '(x: int, *args, y: str, z: str) -> None'
# Space around '=' for defaults
sig = inspect.signature(mod.f7)
sig_str = stringify_signature(sig)
assert sig_str == '(x: int = None, y: dict = {}) -> None'
# Callable types
sig = inspect.signature(mod.f8)
assert stringify_signature(sig) == '(x: typing.Callable[[int, str], int]) -> None'
sig = inspect.signature(mod.f9)
assert stringify_signature(sig) == '(x: typing.Callable) -> None'
# Tuple types
sig = inspect.signature(mod.f10)
sig_str = stringify_signature(sig)
assert sig_str == '(x: typing.Tuple[int, str], y: typing.Tuple[int, ...]) -> None'
# Instance annotations
sig = inspect.signature(mod.f11)
assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
# tuple with more than two items
sig = inspect.signature(mod.f12)
assert stringify_signature(sig) == '() -> typing.Tuple[int, str, int]'
# optional
sig = inspect.signature(mod.f13)
assert stringify_signature(sig) == '() -> str | None'
# optional union
sig = inspect.signature(mod.f20)
assert stringify_signature(sig) in {
'() -> int | str | None',
'() -> str | int | None',
}
# Any
sig = inspect.signature(mod.f14)
assert stringify_signature(sig) == '() -> typing.Any'
# ForwardRef
sig = inspect.signature(mod.f15)
assert stringify_signature(sig) == '(x: Unknown, y: int) -> typing.Any'
# keyword only arguments (1)
sig = inspect.signature(mod.f16)
assert stringify_signature(sig) == '(arg1, arg2, *, arg3=None, arg4=None)'
# keyword only arguments (2)
sig = inspect.signature(mod.f17)
assert stringify_signature(sig) == '(*, arg3, arg4)'
sig = inspect.signature(mod.f18)
assert stringify_signature(sig) == (
'(self, arg1: int | typing.Tuple = 10) -> typing.List[typing.Dict]'
)
# annotations for variadic and keyword parameters
sig = inspect.signature(mod.f19)
assert stringify_signature(sig) == '(*args: int, **kwargs: str)'
# default value is inspect.Signature.empty
sig = inspect.signature(mod.f21)
assert stringify_signature(sig) == "(arg1='whatever', arg2)"
# type hints by string
sig = inspect.signature(mod.Node.children)
sig_str = stringify_signature(sig)
assert sig_str == '(self) -> typing.List[tests.test_util.typing_test_data.Node]'
sig = inspect.signature(mod.Node.__init__)
sig_str = stringify_signature(sig)
assert sig_str == (
'(self, parent: tests.test_util.typing_test_data.Node | None) -> None'
)
# show_annotation is False
sig = inspect.signature(mod.f7)
assert stringify_signature(sig, show_annotation=False) == '(x=None, y={})'
# show_return_annotation is False
sig = inspect.signature(mod.f7)
sig_str = stringify_signature(sig, show_return_annotation=False)
assert sig_str == '(x: int = None, y: dict = {})'
# unqualified_typehints is True
sig = inspect.signature(mod.f7)
sig_str = stringify_signature(sig, unqualified_typehints=True)
assert sig_str == '(x: int = None, y: dict = {}) -> None'
# case: separator at head
sig = inspect.signature(mod.f22)
assert stringify_signature(sig) == '(*, a, b)'
# case: separator in the middle
sig = inspect.signature(mod.f23)
assert stringify_signature(sig) == '(a, b, /, c, d)'
sig = inspect.signature(mod.f24)
assert stringify_signature(sig) == '(a, /, *, b)'
# case: separator at tail
sig = inspect.signature(mod.f25)
assert stringify_signature(sig) == '(a, b, /)'
# collapse Literal types
sig = inspect.signature(mod.f26)
sig_str = stringify_signature(sig)
assert sig_str == (
"(x: typing.Literal[1, 2, 3] = 1, y: typing.Literal['a', 'b'] = 'a') -> None"
)
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_object_description_enum_custom_repr():
class MyEnum(enum.Enum):
FOO = 1
BAR = 2
def __repr__(self):
return self.name
assert inspect.object_description(MyEnum.FOO) == 'FOO'
def test_getslots():
class Foo:
pass
class Bar:
__slots__ = ['attr']
class Baz:
__slots__ = {'attr': 'docstring'}
class Qux:
__slots__ = 'attr' # NoQA: PLC0205
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)
assert not inspect.isclassmethod(Base.meth)
assert inspect.isclassmethod(Inherited.classmeth)
assert not inspect.isclassmethod(Inherited.meth)
def test_isstaticmethod():
assert inspect.isstaticmethod(Base.staticmeth, Base, 'staticmeth')
assert not inspect.isstaticmethod(Base.meth, Base, 'meth')
assert inspect.isstaticmethod(Inherited.staticmeth, Inherited, 'staticmeth')
assert not inspect.isstaticmethod(Inherited.meth, Inherited, 'meth')
def test_iscoroutinefunction():
# function
assert not inspect.iscoroutinefunction(func)
# coroutine
assert inspect.iscoroutinefunction(coroutinefunc)
# partial-ed coroutine
assert inspect.iscoroutinefunction(partial_coroutinefunc)
# method
assert not inspect.iscoroutinefunction(Base.meth)
# coroutine-method
assert inspect.iscoroutinefunction(Base.coroutinemeth)
# coroutine classmethod
assert inspect.iscoroutinefunction(Base.__dict__['coroutineclassmeth'])
# partial-ed coroutine-method
partial_coroutinemeth = Base.__dict__['partial_coroutinemeth']
assert inspect.iscoroutinefunction(partial_coroutinemeth)
def test_iscoroutinefunction_wrapped():
# function wrapping a callable obj
assert inspect.isfunction(_decorator(coroutinefunc))
def test_isfunction():
# fmt: off
assert inspect.isfunction(func) # function
assert inspect.isfunction(partial_func) # partial-ed function
assert inspect.isfunction(Base.meth) # method of class
assert inspect.isfunction(Base.partialmeth) # partial-ed method of class
assert not inspect.isfunction(Base().meth) # method of instance
assert not inspect.isfunction(builtin_func) # builtin function
assert not inspect.isfunction(partial_builtin_func) # partial-ed builtin function
# fmt: on
def test_isfunction_wrapped():
# function wrapping a callable obj
assert inspect.isfunction(_decorator(_Callable()))
def test_isbuiltin():
# fmt: off
assert inspect.isbuiltin(builtin_func) # builtin function
assert inspect.isbuiltin(partial_builtin_func) # partial-ed builtin function
assert not inspect.isbuiltin(func) # function
assert not inspect.isbuiltin(partial_func) # partial-ed function
assert not inspect.isbuiltin(Base.meth) # method of class
assert not inspect.isbuiltin(Base().meth) # method of instance
# fmt: on
def test_isdescriptor():
# fmt: off
assert inspect.isdescriptor(Base.prop) # property of class
assert not inspect.isdescriptor(Base().prop) # property of instance
assert inspect.isdescriptor(Base.meth) # method of class
assert inspect.isdescriptor(Base().meth) # method of instance
assert inspect.isdescriptor(func) # function
# fmt: on
def test_isattributedescriptor():
# fmt: off
assert inspect.isattributedescriptor(Base.prop) # property
assert not inspect.isattributedescriptor(Base.meth) # method
assert not inspect.isattributedescriptor(Base.staticmeth) # staticmethod
assert not inspect.isattributedescriptor(Base.classmeth) # classmetho
assert not inspect.isattributedescriptor(Descriptor) # custom descriptor class
assert not inspect.isattributedescriptor(str.join) # MethodDescriptorType
assert not inspect.isattributedescriptor(object.__init__) # WrapperDescriptorType
assert not inspect.isattributedescriptor(dict.__dict__['fromkeys']) # ClassMethodDescriptorType
assert inspect.isattributedescriptor(types.FrameType.f_locals) # GetSetDescriptorType
assert inspect.isattributedescriptor(datetime.timedelta.days) # MemberDescriptorType
# fmt: on
try:
# _testcapi module cannot be importable in some distro
# refs: https://github.com/sphinx-doc/sphinx/issues/9868
import _testcapi
# instancemethod (C-API)
testinstancemethod = _testcapi.instancemethod(str.__repr__)
assert not inspect.isattributedescriptor(testinstancemethod)
except ImportError:
pass
def test_isproperty():
# fmt: off
assert inspect.isproperty(Base.prop) # property of class
assert not inspect.isproperty(Base().prop) # property of instance
assert not inspect.isproperty(Base.meth) # method of class
assert not inspect.isproperty(Base().meth) # method of instance
assert not inspect.isproperty(func) # function
# fmt: on
def test_isgenericalias():
#: A list of int
T = List[int] # NoQA: UP006
S = list[Union[str, None]] # NoQA: UP007
C = Callable[[int], None] # NoQA: UP006 # a generic alias not having a doccomment
assert inspect.isgenericalias(C)
assert inspect.isgenericalias(Callable) # NoQA: UP006
assert inspect.isgenericalias(T)
assert inspect.isgenericalias(List) # NoQA: UP006
assert inspect.isgenericalias(S)
assert not inspect.isgenericalias(list)
assert not inspect.isgenericalias([])
assert not inspect.isgenericalias(object())
assert not inspect.isgenericalias(Base)
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'
)