mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
autodoc: Support singledispatch methods
This commit is contained in:
parent
961b4d1545
commit
8f7cc26b20
2
CHANGES
2
CHANGES
@ -58,7 +58,7 @@ Features added
|
||||
* #6830: autodoc: consider a member private if docstring contains
|
||||
``:meta private:`` in info-field-list
|
||||
* #7165: autodoc: Support Annotated type (PEP-593)
|
||||
* #2815: autodoc: Support singledispatch functions
|
||||
* #2815: autodoc: Support singledispatch functions and methods
|
||||
* #6558: glossary: emit a warning for duplicated glossary entry
|
||||
* #3106: domain: Register hyperlink target for index page automatically
|
||||
* #6558: std domain: emit a warning for duplicated generic objects
|
||||
|
@ -1457,6 +1457,66 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
pass
|
||||
|
||||
|
||||
class SingledispatchMethodDocumenter(MethodDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for singledispatch'ed methods.
|
||||
"""
|
||||
objtype = 'singledispatch_method'
|
||||
directivetype = 'method'
|
||||
member_order = 50
|
||||
|
||||
# before MethodDocumenter
|
||||
priority = MethodDocumenter.priority + 1
|
||||
|
||||
@classmethod
|
||||
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
|
||||
) -> bool:
|
||||
if super().can_document_member(member, membername, isattr, parent) and parent.object:
|
||||
meth = parent.object.__dict__.get(membername)
|
||||
return inspect.is_singledispatch_method(meth)
|
||||
else:
|
||||
return False
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
sourcename = self.get_sourcename()
|
||||
|
||||
# intercept generated directive headers
|
||||
# TODO: It is very hacky to use mock to intercept header generation
|
||||
with patch.object(self, 'add_line') as add_line:
|
||||
super().add_directive_header(sig)
|
||||
|
||||
# output first line of header
|
||||
self.add_line(*add_line.call_args_list[0][0])
|
||||
|
||||
# inserts signature of singledispatch'ed functions
|
||||
meth = self.parent.__dict__.get(self.objpath[-1])
|
||||
for typ, func in meth.dispatcher.registry.items():
|
||||
if typ is object:
|
||||
pass # default implementation. skipped.
|
||||
else:
|
||||
self.annotate_to_first_argument(func, typ)
|
||||
|
||||
documenter = MethodDocumenter(self.directive, '')
|
||||
documenter.object = func
|
||||
self.add_line(' %s%s' % (self.format_name(),
|
||||
documenter.format_signature()),
|
||||
sourcename)
|
||||
|
||||
# output remains of directive header
|
||||
for call in add_line.call_args_list[1:]:
|
||||
self.add_line(*call[0])
|
||||
|
||||
def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
|
||||
"""Annotate type hint to the first argument of function if needed."""
|
||||
sig = inspect.signature(func, bound_method=True)
|
||||
if len(sig.parameters) == 0:
|
||||
return
|
||||
|
||||
name = list(sig.parameters)[0]
|
||||
if name not in func.__annotations__:
|
||||
func.__annotations__[name] = typ
|
||||
|
||||
|
||||
class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore
|
||||
"""
|
||||
Specialized Documenter subclass for attributes.
|
||||
@ -1672,6 +1732,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_autodocumenter(SingledispatchFunctionDocumenter)
|
||||
app.add_autodocumenter(DecoratorDocumenter)
|
||||
app.add_autodocumenter(MethodDocumenter)
|
||||
app.add_autodocumenter(SingledispatchMethodDocumenter)
|
||||
app.add_autodocumenter(AttributeDocumenter)
|
||||
app.add_autodocumenter(PropertyDocumenter)
|
||||
app.add_autodocumenter(InstanceAttributeDocumenter)
|
||||
|
@ -235,6 +235,15 @@ def is_singledispatch_function(obj: Any) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def is_singledispatch_method(obj: Any) -> bool:
|
||||
"""Check if the object is singledispatch method."""
|
||||
try:
|
||||
from functools import singledispatchmethod # type: ignore
|
||||
return isinstance(obj, singledispatchmethod)
|
||||
except ImportError: # py35-37
|
||||
return False
|
||||
|
||||
|
||||
def isfunction(obj: Any) -> bool:
|
||||
"""Check if the object is function."""
|
||||
return inspect.isfunction(unwrap(obj))
|
||||
|
20
tests/roots/test-ext-autodoc/target/singledispatchmethod.py
Normal file
20
tests/roots/test-ext-autodoc/target/singledispatchmethod.py
Normal file
@ -0,0 +1,20 @@
|
||||
from functools import singledispatchmethod
|
||||
|
||||
|
||||
class Foo:
|
||||
"""docstring"""
|
||||
|
||||
@singledispatchmethod
|
||||
def meth(self, arg, kwarg=None):
|
||||
"""A method for general use."""
|
||||
pass
|
||||
|
||||
@meth.register(int)
|
||||
def _meth_int(self, arg, kwarg=None):
|
||||
"""A method for int."""
|
||||
pass
|
||||
|
||||
@meth.register(str)
|
||||
def _meth_str(self, arg, kwarg=None):
|
||||
"""A method for str."""
|
||||
pass
|
@ -1582,3 +1582,30 @@ def test_singledispatch():
|
||||
' A function for general use.',
|
||||
' '
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 8),
|
||||
reason='singledispatchmethod is available since python3.8')
|
||||
@pytest.mark.usefixtures('setup_test')
|
||||
def test_singledispatchmethod():
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.singledispatchmethod',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo',
|
||||
' :module: target.singledispatchmethod',
|
||||
'',
|
||||
' docstring',
|
||||
' ',
|
||||
' ',
|
||||
' .. py:method:: Foo.meth(arg, kwarg=None)',
|
||||
' Foo.meth(arg: int, kwarg=None)',
|
||||
' Foo.meth(arg: str, kwarg=None)',
|
||||
' :module: target.singledispatchmethod',
|
||||
' ',
|
||||
' A method for general use.',
|
||||
' '
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user