Skip to content

Commit

Permalink
Close #4257: autodoc: Add autodoc_class_signature
Browse files Browse the repository at this point in the history
Add `autodoc_class_signature` to separate the class entry and the
definition of `__init__()` method.
  • Loading branch information
tk0miya committed May 15, 2021
1 parent 1513d50 commit 51ae283
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -28,6 +28,8 @@ Features added
* #9195: autodoc: The arguments of ``typing.Literal`` are wrongly rendered
* #9185: autodoc: :confval:`autodoc_typehints` allows ``'both'`` setting to
allow typehints to be included both in the signature and description
* #4257: autodoc: Add :confval:`autodoc_class_signature` to separate the class
entry and the definition of ``__init__()`` method
* #3257: autosummary: Support instance attributes for classes
* #9129: html search: Show search summaries when html_copy_source = False
* #9120: html theme: Eliminate prompt characters of code-block from copyable
Expand Down
14 changes: 14 additions & 0 deletions doc/usage/extensions/autodoc.rst
Expand Up @@ -463,6 +463,20 @@ There are also config values that you can set:

.. versionadded:: 1.4

.. confval:: autodoc_class_signature

This value selects how the signautre will be displayed for the class defined
by :rst:dir:`autoclass` directive. The possible values are:

``"mixed"``
Display the signature with the class name.
``"separated"``
Display the signature as a method.

The default is ``"mixed"``.

.. versionadded:: 4.1

.. confval:: autodoc_member_order

This value selects if automatically documented members are sorted
Expand Down
48 changes: 48 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -70,6 +70,9 @@ class _All:
def __contains__(self, item: Any) -> bool:
return True

def append(self, item: Any) -> None:
pass # nothing


class _Empty:
"""A special value for :exclude-members: that never matches to any member."""
Expand Down Expand Up @@ -1440,6 +1443,15 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:

def __init__(self, *args: Any) -> None:
super().__init__(*args)

if self.config.autodoc_class_signature == 'separated':
# show __init__() method
if self.options.special_members is None:
self.options['special-members'] = {'__new__', '__init__'}
else:
self.options.special_members.append('__new__')
self.options.special_members.append('__init__')

merge_members_option(self.options)

@classmethod
Expand Down Expand Up @@ -1556,6 +1568,9 @@ def format_args(self, **kwargs: Any) -> str:
def format_signature(self, **kwargs: Any) -> str:
if self.doc_as_attr:
return ''
if self.config.autodoc_class_signature == 'separated':
# do not show signatures
return ''

sig = super().format_signature()
sigs = []
Expand Down Expand Up @@ -2193,6 +2208,38 @@ def dummy():
else:
return None

def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]:
if self.objpath[-1] == '__init__':
docstring = getdoc(self.object, self.get_attr,
self.config.autodoc_inherit_docstrings,
self.parent, self.object_name)
if (docstring is not None and
(docstring == object.__init__.__doc__ or # for pypy
docstring.strip() == object.__init__.__doc__)): # for !pypy
docstring = None
if docstring:
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, tabsize=tab_width)]
else:
return []
elif self.objpath[-1] == '__new__':
__new__ = self.get_attr(self.object, '__new__', None)
if __new__:
docstring = getdoc(__new__, self.get_attr,
self.config.autodoc_inherit_docstrings,
self.parent, self.object_name)
if (docstring is not None and
(docstring == object.__new__.__doc__ or # for pypy
docstring.strip() == object.__new__.__doc__)): # for !pypy
docstring = None
if docstring:
tab_width = self.directive.state.document.settings.tab_width
return [prepare_docstring(docstring, tabsize=tab_width)]
else:
return []
else:
return super().get_doc()


class NonDataDescriptorMixin(DataDocumenterMixinBase):
"""
Expand Down Expand Up @@ -2662,6 +2709,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))
app.add_config_value('autodoc_member_order', 'alphabetical', True,
ENUM('alphabetic', 'alphabetical', 'bysource', 'groupwise'))
app.add_config_value('autodoc_class_signature', 'mixed', True, ENUM('mixed', 'separated'))
app.add_config_value('autodoc_default_options', {}, True)
app.add_config_value('autodoc_docstring_signature', True, True)
app.add_config_value('autodoc_mock_imports', [], True)
Expand Down
1 change: 1 addition & 0 deletions sphinx/ext/autosummary/__init__.py
Expand Up @@ -174,6 +174,7 @@ def __init__(self) -> None:
document = Struct(settings=settings)
env = BuildEnvironment()
env.config = Config()
env.config.add('autodoc_class_signature', 'mixed', True, None)
state = Struct(document=document)
super().__init__(env, None, Options(), 0, state)

Expand Down
51 changes: 51 additions & 0 deletions tests/test_ext_autodoc_configs.py
Expand Up @@ -140,6 +140,57 @@ def test_autoclass_content_init(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_class_signature_mixed(app):
app.config.autodoc_class_signature = 'mixed'
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'class', 'target.classes.Bar', options)
assert list(actual) == [
'',
'.. py:class:: Bar(x, y)',
' :module: target.classes',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_class_signature_separated_init(app):
app.config.autodoc_class_signature = 'separated'
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'class', 'target.classes.Bar', options)
assert list(actual) == [
'',
'.. py:class:: Bar',
' :module: target.classes',
'',
'',
' .. py:method:: Bar.__init__(x, y)',
' :module: target.classes',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_class_signature_separated_new(app):
app.config.autodoc_class_signature = 'separated'
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'class', 'target.classes.Baz', options)
assert list(actual) == [
'',
'.. py:class:: Baz',
' :module: target.classes',
'',
'',
' .. py:method:: Baz.__new__(cls, x, y)',
' :module: target.classes',
' :staticmethod:',
'',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoclass_content_both(app):
app.config.autoclass_content = 'both'
Expand Down

0 comments on commit 51ae283

Please sign in to comment.