Refactor `sphinx.util.inspect` and tests (#11527)

This commit is contained in:
Adam Turner
2023-07-28 02:05:40 +01:00
committed by GitHub
parent c9d4a1a204
commit 7cce00aa7d
2 changed files with 112 additions and 64 deletions

View File

@@ -43,12 +43,11 @@ memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
def unwrap(obj: Any) -> Any:
"""Get an original object from wrapped object (wrapped functions)."""
if hasattr(obj, '__sphinx_mock__'):
# Skip unwrapping mock object to avoid RecursionError
return obj
try:
if hasattr(obj, '__sphinx_mock__'):
# Skip unwrapping mock object to avoid RecursionError
return obj
else:
return inspect.unwrap(obj)
return inspect.unwrap(obj)
except ValueError:
# might be a mock object
return obj
@@ -81,11 +80,9 @@ def getall(obj: Any) -> Sequence[str] | None:
__all__ = safe_getattr(obj, '__all__', None)
if __all__ is None:
return None
else:
if (isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__)):
return __all__
else:
raise ValueError(__all__)
if isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__):
return __all__
raise ValueError(__all__)
def getannotations(obj: Any) -> Mapping[str, Any]:
@@ -157,10 +154,9 @@ def isNewType(obj: Any) -> bool:
"""Check the if object is a kind of NewType."""
if sys.version_info[:2] >= (3, 10):
return isinstance(obj, typing.NewType)
else:
__module__ = safe_getattr(obj, '__module__', None)
__qualname__ = safe_getattr(obj, '__qualname__', None)
return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
__module__ = safe_getattr(obj, '__module__', None)
__qualname__ = safe_getattr(obj, '__qualname__', None)
return __module__ == 'typing' and __qualname__ == 'NewType.<locals>.new_type'
def isenumclass(x: Any) -> bool:
@@ -209,7 +205,7 @@ def isstaticmethod(obj: Any, cls: Any = None, name: str | None = None) -> bool:
"""Check if the object is staticmethod."""
if isinstance(obj, staticmethod):
return True
elif cls and name:
if cls and name:
# trace __mro__ if the method is defined in parent class
#
# .. note:: This only works well with new style classes.
@@ -217,7 +213,6 @@ def isstaticmethod(obj: Any, cls: Any = None, name: str | None = None) -> bool:
meth = basecls.__dict__.get(name)
if meth:
return isinstance(meth, staticmethod)
return False
@@ -291,17 +286,17 @@ def is_singledispatch_method(obj: Any) -> bool:
def isfunction(obj: Any) -> bool:
"""Check if the object is function."""
return inspect.isfunction(unwrap_all(obj))
return inspect.isfunction(unpartial(obj))
def isbuiltin(obj: Any) -> bool:
"""Check if the object is builtin."""
return inspect.isbuiltin(unwrap_all(obj))
"""Check if the object is function."""
return inspect.isbuiltin(unpartial(obj))
def isroutine(obj: Any) -> bool:
"""Check is any kind of function or method."""
return inspect.isroutine(unwrap_all(obj))
return inspect.isroutine(unpartial(obj))
def iscoroutinefunction(obj: Any) -> bool:

View File

@@ -9,7 +9,7 @@ import functools
import sys
import types
from inspect import Parameter
from typing import Optional
from typing import Callable, List, Optional, Union # NoQA: UP035
import pytest
@@ -18,6 +18,74 @@ from sphinx.util.inspect import TypeAliasForwardRef, TypeAliasNamespace, stringi
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'
@@ -619,47 +687,39 @@ def test_getslots():
inspect.getslots(Bar())
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isclassmethod(app):
from target.methods import Base, Inherited
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
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isstaticmethod(app):
from target.methods import Base, Inherited
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
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_iscoroutinefunction(app):
from target.functions import coroutinefunc, func, partial_coroutinefunc
from target.methods import Base
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
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isfunction(app):
from target.functions import builtin_func, func, partial_builtin_func, partial_func
from target.methods import Base
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
@@ -669,11 +729,12 @@ def test_isfunction(app):
assert inspect.isfunction(partial_builtin_func) is False # partial-ed builtin function
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isbuiltin(app):
from target.functions import builtin_func, func, partial_builtin_func, partial_func
from target.methods import Base
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
@@ -682,11 +743,7 @@ def test_isbuiltin(app):
assert inspect.isbuiltin(Base().meth) is False # method of instance
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isdescriptor(app):
from target.functions import func
from target.methods import Base
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
@@ -694,14 +751,7 @@ def test_isdescriptor(app):
assert inspect.isdescriptor(func) is True # function
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isattributedescriptor(app):
from target.methods import Base
class Descriptor:
def __get__(self, obj, typ=None):
pass
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
@@ -724,11 +774,7 @@ def test_isattributedescriptor(app):
pass
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isproperty(app):
from target.functions import func
from target.methods import Base
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
@@ -736,13 +782,20 @@ def test_isproperty(app):
assert inspect.isproperty(func) is False # function
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isgenericalias(app):
from target.genericalias import C, T
from target.methods import Base
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