mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Merge branch '3.x'
This commit is contained in:
82
CHANGES
82
CHANGES
@@ -54,7 +54,7 @@ Bugs fixed
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.4.0 (in development)
|
||||
Release 3.5.0 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
@@ -63,6 +63,45 @@ Dependencies
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.4.1 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.4.0 (released Dec 20, 2020)
|
||||
=====================================
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
* #8105: autodoc: the signature of class constructor will be shown for decorated
|
||||
classes, not a signature of decorator
|
||||
|
||||
@@ -79,7 +118,9 @@ Deprecated
|
||||
* ``sphinx.ext.autodoc.SlotsAttributeDocumenter``
|
||||
* ``sphinx.ext.autodoc.TypeVarDocumenter``
|
||||
* ``sphinx.ext.autodoc.importer._getannotations()``
|
||||
* ``sphinx.ext.autodoc.importer._getmro()``
|
||||
* ``sphinx.pycode.ModuleAnalyzer.parse()``
|
||||
* ``sphinx.util.osutil.movefile()``
|
||||
* ``sphinx.util.requests.is_ssl_error()``
|
||||
|
||||
Features added
|
||||
@@ -98,6 +139,7 @@ Features added
|
||||
* #8460: autodoc: Support custom types defined by typing.NewType
|
||||
* #8285: napoleon: Add :confval:`napoleon_attr_annotations` to merge type hints
|
||||
on source code automatically if any type is specified in docstring
|
||||
* #8236: napoleon: Support numpydoc's "Receives" section
|
||||
* #6914: Add a new event :event:`warn-missing-reference` to custom warning
|
||||
messages when failed to resolve a cross-reference
|
||||
* #6914: Emit a detailed warning when failed to resolve a ``:ref:`` reference
|
||||
@@ -121,49 +163,37 @@ Bugs fixed
|
||||
attributes
|
||||
* #8503: autodoc: autoattribute could not create document for a GenericAlias as
|
||||
class attributes correctly
|
||||
* #8534: autodoc: autoattribute could not create document for a commented
|
||||
attribute in alias class
|
||||
* #8452: autodoc: autodoc_type_aliases doesn't work when autodoc_typehints is
|
||||
set to "description"
|
||||
* #8541: autodoc: autodoc_type_aliases doesn't work for the type annotation to
|
||||
instance attributes
|
||||
* #8460: autodoc: autodata and autoattribute directives do not display type
|
||||
information of TypeVars
|
||||
* #8493: autodoc: references to builtins not working in class aliases
|
||||
* #8522: autodoc: ``__bool__`` method could be called
|
||||
* #8067: autodoc: A typehint for the instance variable having type_comment on
|
||||
super class is not displayed
|
||||
* #8545: autodoc: a __slots__ attribute is not documented even having docstring
|
||||
* #741: autodoc: inherited-members doesn't work for instance attributes on super
|
||||
class
|
||||
* #8477: autosummary: non utf-8 reST files are generated when template contains
|
||||
multibyte characters
|
||||
* #8501: autosummary: summary extraction splits text after "el at." unexpectedly
|
||||
* #8524: html: Wrong url_root has been generated on a document named "index"
|
||||
* #8419: html search: Do not load ``language_data.js`` in non-search pages
|
||||
* #8549: i18n: ``-D gettext_compact=0`` is no longer working
|
||||
* #8454: graphviz: The layout option for graph and digraph directives don't work
|
||||
* #8131: linkcheck: Use GET when HEAD requests cause Too Many Redirects, to
|
||||
accommodate infinite redirect loops on HEAD
|
||||
* #8437: Makefile: ``make clean`` with empty BUILDDIR is dangerous
|
||||
* #8365: py domain: ``:type:`` and ``:rtype:`` gives false ambiguous class
|
||||
lookup warnings
|
||||
* #8352: std domain: Failed to parse an option that starts with bracket
|
||||
* #8519: LaTeX: Prevent page brake in the middle of a seealso
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.3.2 (in development)
|
||||
==============================
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
Incompatible changes
|
||||
--------------------
|
||||
|
||||
Deprecated
|
||||
----------
|
||||
|
||||
Features added
|
||||
--------------
|
||||
|
||||
Bugs fixed
|
||||
----------
|
||||
|
||||
* #8520: C, fix copying of AliasNode.
|
||||
|
||||
Testing
|
||||
--------
|
||||
|
||||
Release 3.3.1 (released Nov 12, 2020)
|
||||
=====================================
|
||||
|
||||
|
||||
@@ -14,4 +14,5 @@ You can also browse it from this repository from
|
||||
``doc/internals/contributing.rst``
|
||||
|
||||
Sphinx uses GitHub to host source code, track patches and bugs, and more.
|
||||
Please make an effort to provide as much possible when filing bugs.
|
||||
Please make an effort to provide as much detail as possible when filing
|
||||
bugs.
|
||||
|
||||
@@ -102,11 +102,21 @@ The following is a list of deprecated interfaces.
|
||||
- 4.0
|
||||
- ``sphinx.util.inspect.getannotations()``
|
||||
|
||||
* - ``sphinx.ext.autodoc.importer._getmro()``
|
||||
- 3.4
|
||||
- 4.0
|
||||
- ``sphinx.util.inspect.getmro()``
|
||||
|
||||
* - ``sphinx.pycode.ModuleAnalyzer.parse()``
|
||||
- 3.4
|
||||
- 5.0
|
||||
- ``sphinx.pycode.ModuleAnalyzer.analyze()``
|
||||
|
||||
* - ``sphinx.util.osutil.movefile()``
|
||||
- 3.4
|
||||
- 5.0
|
||||
- ``os.replace()``
|
||||
|
||||
* - ``sphinx.util.requests.is_ssl_error()``
|
||||
- 3.4
|
||||
- 5.0
|
||||
|
||||
@@ -290,7 +290,7 @@ class MessageCatalogBuilder(I18nBuilder):
|
||||
def setup(app: Sphinx) -> Dict[str, Any]:
|
||||
app.add_builder(MessageCatalogBuilder)
|
||||
|
||||
app.add_config_value('gettext_compact', True, 'gettext', Any)
|
||||
app.add_config_value('gettext_compact', True, 'gettext', {bool, str})
|
||||
app.add_config_value('gettext_location', True, 'gettext')
|
||||
app.add_config_value('gettext_uuid', False, 'gettext')
|
||||
app.add_config_value('gettext_auto_build', True, 'env')
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"""
|
||||
|
||||
import html
|
||||
import os
|
||||
import posixpath
|
||||
import re
|
||||
import sys
|
||||
@@ -43,7 +44,7 @@ from sphinx.util.fileutil import copy_asset
|
||||
from sphinx.util.i18n import format_date
|
||||
from sphinx.util.inventory import InventoryFile
|
||||
from sphinx.util.matching import DOTFILES, Matcher, patmatch
|
||||
from sphinx.util.osutil import copyfile, ensuredir, movefile, os_path, relative_uri
|
||||
from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri
|
||||
from sphinx.util.tags import Tags
|
||||
from sphinx.writers.html import HTMLTranslator, HTMLWriter
|
||||
|
||||
@@ -1064,7 +1065,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
else:
|
||||
with open(searchindexfn + '.tmp', 'wb') as fb:
|
||||
self.indexer.dump(fb, self.indexer_format)
|
||||
movefile(searchindexfn + '.tmp', searchindexfn)
|
||||
os.replace(searchindexfn + '.tmp', searchindexfn)
|
||||
|
||||
|
||||
def convert_html_css_files(app: Sphinx, config: Config) -> None:
|
||||
|
||||
@@ -173,6 +173,14 @@ class Config:
|
||||
defvalue = self.values[name][0]
|
||||
if self.values[name][2] == Any:
|
||||
return value
|
||||
elif self.values[name][2] == {bool, str}:
|
||||
if value == '0':
|
||||
# given falsy string from command line option
|
||||
return False
|
||||
elif value == '1':
|
||||
return True
|
||||
else:
|
||||
return value
|
||||
elif type(defvalue) is bool or self.values[name][2] == [bool]:
|
||||
if value == '0':
|
||||
# given falsy string from command line option
|
||||
|
||||
@@ -271,6 +271,8 @@ class PyXrefMixin:
|
||||
result = super().make_xref(rolename, domain, target, # type: ignore
|
||||
innernode, contnode, env)
|
||||
result['refspecific'] = True
|
||||
result['py:module'] = env.ref_context.get('py:module')
|
||||
result['py:class'] = env.ref_context.get('py:class')
|
||||
if target.startswith(('.', '~')):
|
||||
prefix, result['reftarget'] = target[0], target[1:]
|
||||
if prefix == '.':
|
||||
|
||||
@@ -25,7 +25,8 @@ from sphinx.application import Sphinx
|
||||
from sphinx.config import ENUM, Config
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
|
||||
from sphinx.environment import BuildEnvironment
|
||||
from sphinx.ext.autodoc.importer import get_module_members, get_object_members, import_object
|
||||
from sphinx.ext.autodoc.importer import (get_class_members, get_module_members,
|
||||
get_object_members, import_object)
|
||||
from sphinx.ext.autodoc.mock import mock
|
||||
from sphinx.locale import _, __
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
@@ -270,9 +271,11 @@ class ObjectMember(tuple):
|
||||
def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any:
|
||||
return super().__new__(cls, (name, obj)) # type: ignore
|
||||
|
||||
def __init__(self, name: str, obj: Any, skipped: bool = False) -> None:
|
||||
def __init__(self, name: str, obj: Any, docstring: Optional[str] = None,
|
||||
skipped: bool = False) -> None:
|
||||
self.__name__ = name
|
||||
self.object = obj
|
||||
self.docstring = docstring
|
||||
self.skipped = skipped
|
||||
|
||||
|
||||
@@ -700,6 +703,11 @@ class Documenter:
|
||||
cls_doc = self.get_attr(cls, '__doc__', None)
|
||||
if cls_doc == doc:
|
||||
doc = None
|
||||
|
||||
if isinstance(obj, ObjectMember) and obj.docstring:
|
||||
# hack for ClassDocumenter to inject docstring via ObjectMember
|
||||
doc = obj.docstring
|
||||
|
||||
has_doc = bool(doc)
|
||||
|
||||
metadata = extract_metadata(doc)
|
||||
@@ -1559,7 +1567,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename)
|
||||
|
||||
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
|
||||
members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer)
|
||||
members = get_class_members(self.object, self.objpath, self.get_attr)
|
||||
if not want_all:
|
||||
if not self.options.members:
|
||||
return False, [] # type: ignore
|
||||
@@ -1567,16 +1575,18 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
selected = []
|
||||
for name in self.options.members: # type: str
|
||||
if name in members:
|
||||
selected.append((name, members[name].value))
|
||||
selected.append(ObjectMember(name, members[name].value,
|
||||
docstring=members[name].docstring))
|
||||
else:
|
||||
logger.warning(__('missing attribute %s in object %s') %
|
||||
(name, self.fullname), type='autodoc')
|
||||
return False, selected
|
||||
elif self.options.inherited_members:
|
||||
return False, [(m.name, m.value) for m in members.values()]
|
||||
return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
|
||||
for m in members.values()]
|
||||
else:
|
||||
return False, [(m.name, m.value) for m in members.values()
|
||||
if m.directly_defined]
|
||||
return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
|
||||
for m in members.values() if m.class_ == self.object]
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
if self.doc_as_attr:
|
||||
@@ -1822,6 +1832,26 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
|
||||
) -> bool:
|
||||
return isinstance(parent, ModuleDocumenter) and isattr
|
||||
|
||||
def update_annotations(self, parent: Any) -> None:
|
||||
"""Update __annotations__ to support type_comment and so on."""
|
||||
try:
|
||||
annotations = inspect.getannotations(parent)
|
||||
|
||||
analyzer = ModuleAnalyzer.for_module(self.modname)
|
||||
analyzer.analyze()
|
||||
for (classname, attrname), annotation in analyzer.annotations.items():
|
||||
if classname == '' and attrname not in annotations:
|
||||
annotations[attrname] = annotation # type: ignore
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
ret = super().import_object(raiseerror)
|
||||
if self.parent:
|
||||
self.update_annotations(self.parent)
|
||||
|
||||
return ret
|
||||
|
||||
def add_directive_header(self, sig: str) -> None:
|
||||
super().add_directive_header(sig)
|
||||
sourcename = self.get_sourcename()
|
||||
@@ -1836,11 +1866,6 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
else:
|
||||
key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
|
||||
if self.analyzer and key in self.analyzer.annotations:
|
||||
self.add_line(' :type: ' + self.analyzer.annotations[key],
|
||||
sourcename)
|
||||
|
||||
try:
|
||||
if self.options.no_value or self.should_suppress_value_header():
|
||||
@@ -2033,6 +2058,28 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
return
|
||||
|
||||
|
||||
class NonDataDescriptorMixin(DataDocumenterMixinBase):
|
||||
"""
|
||||
Mixin for AttributeDocumenter to provide the feature for supporting non
|
||||
data-descriptors.
|
||||
|
||||
.. note:: This mix-in must be inherited after other mix-ins. Otherwise, docstring
|
||||
and :value: header will be suppressed unexpectedly.
|
||||
"""
|
||||
|
||||
def should_suppress_value_header(self) -> bool:
|
||||
return (inspect.isattributedescriptor(self.object) or
|
||||
super().should_suppress_directive_header())
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
if not inspect.isattributedescriptor(self.object):
|
||||
# the docstring of non datadescriptor is very probably the wrong thing
|
||||
# to display
|
||||
return []
|
||||
else:
|
||||
return super().get_doc(ignore) # type: ignore
|
||||
|
||||
|
||||
class SlotsMixin(DataDocumenterMixinBase):
|
||||
"""
|
||||
Mixin for AttributeDocumenter to provide the feature for supporting __slots__.
|
||||
@@ -2080,8 +2127,96 @@ class SlotsMixin(DataDocumenterMixinBase):
|
||||
return super().get_doc(ignore) # type: ignore
|
||||
|
||||
|
||||
class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase):
|
||||
"""
|
||||
Mixin for AttributeDocumenter to provide the feature for supporting uninitialized
|
||||
instance attributes (that are defined in __init__() methods with doc-comments).
|
||||
|
||||
Example:
|
||||
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
self.attr = None #: This is a target of this mix-in.
|
||||
"""
|
||||
|
||||
def get_attribute_comment(self, parent: Any) -> Optional[List[str]]:
|
||||
try:
|
||||
for cls in inspect.getmro(parent):
|
||||
try:
|
||||
module = safe_getattr(cls, '__module__')
|
||||
qualname = safe_getattr(cls, '__qualname__')
|
||||
|
||||
analyzer = ModuleAnalyzer.for_module(module)
|
||||
analyzer.analyze()
|
||||
if qualname and self.objpath:
|
||||
key = (qualname, self.objpath[-1])
|
||||
if key in analyzer.attr_docs:
|
||||
return list(analyzer.attr_docs[key])
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
def is_uninitialized_instance_attribute(self, parent: Any) -> bool:
|
||||
"""Check the subject is an attribute defined in __init__()."""
|
||||
# An instance variable defined in __init__().
|
||||
if self.get_attribute_comment(parent):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
"""Check the exisitence of uninitizlied instance attribute when failed to import
|
||||
the attribute.
|
||||
"""
|
||||
try:
|
||||
return super().import_object(raiseerror=True) # type: ignore
|
||||
except ImportError as exc:
|
||||
try:
|
||||
ret = import_object(self.modname, self.objpath[:-1], 'class',
|
||||
attrgetter=self.get_attr, # type: ignore
|
||||
warningiserror=self.config.autodoc_warningiserror)
|
||||
parent = ret[3]
|
||||
if self.is_uninitialized_instance_attribute(parent):
|
||||
self.object = UNINITIALIZED_ATTR
|
||||
self.parent = parent
|
||||
return True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if raiseerror:
|
||||
raise
|
||||
else:
|
||||
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
|
||||
self.env.note_reread()
|
||||
return False
|
||||
|
||||
def should_suppress_value_header(self) -> bool:
|
||||
return (self.object is UNINITIALIZED_ATTR or
|
||||
super().should_suppress_value_header())
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
comment = self.get_attribute_comment(self.parent)
|
||||
if comment:
|
||||
return [comment]
|
||||
|
||||
return super().get_doc(ignore) # type: ignore
|
||||
|
||||
def add_content(self, more_content: Optional[StringList], no_docstring: bool = False
|
||||
) -> None:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
self.analyzer = None
|
||||
|
||||
super().add_content(more_content, no_docstring=no_docstring) # type: ignore
|
||||
|
||||
|
||||
class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore
|
||||
TypeVarMixin, DocstringStripSignatureMixin, ClassLevelDocumenter):
|
||||
TypeVarMixin, UninitializedInstanceAttributeMixin,
|
||||
NonDataDescriptorMixin, DocstringStripSignatureMixin,
|
||||
ClassLevelDocumenter):
|
||||
"""
|
||||
Specialized Documenter subclass for attributes.
|
||||
"""
|
||||
@@ -2131,35 +2266,36 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# An instance variable defined inside __init__().
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(self.modname)
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
if self.objpath:
|
||||
key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
|
||||
if key in attr_docs:
|
||||
return True
|
||||
|
||||
return False
|
||||
except PycodeError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def update_annotations(self, parent: Any) -> None:
|
||||
"""Update __annotations__ to support type_comment and so on."""
|
||||
try:
|
||||
annotations = inspect.getannotations(parent)
|
||||
|
||||
for cls in inspect.getmro(parent):
|
||||
try:
|
||||
module = safe_getattr(cls, '__module__')
|
||||
qualname = safe_getattr(cls, '__qualname__')
|
||||
|
||||
analyzer = ModuleAnalyzer.for_module(module)
|
||||
analyzer.analyze()
|
||||
for (classname, attrname), annotation in analyzer.annotations.items():
|
||||
if classname == qualname and attrname not in annotations:
|
||||
annotations[attrname] = annotation # type: ignore
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
try:
|
||||
ret = super().import_object(raiseerror=True)
|
||||
if inspect.isenumattribute(self.object):
|
||||
self.object = self.object.value
|
||||
if inspect.isattributedescriptor(self.object):
|
||||
self._datadescriptor = True
|
||||
else:
|
||||
# if it's not a data descriptor
|
||||
self._datadescriptor = False
|
||||
except ImportError as exc:
|
||||
if self.isinstanceattribute():
|
||||
self.object = INSTANCEATTR
|
||||
self._datadescriptor = False
|
||||
ret = True
|
||||
elif raiseerror:
|
||||
raise
|
||||
@@ -2168,6 +2304,9 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
|
||||
self.env.note_reread()
|
||||
ret = False
|
||||
|
||||
if self.parent:
|
||||
self.update_annotations(self.parent)
|
||||
|
||||
return ret
|
||||
|
||||
def get_real_modname(self) -> str:
|
||||
@@ -2187,27 +2326,19 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
|
||||
if self.objpath[-1] in annotations:
|
||||
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
|
||||
self.add_line(' :type: ' + objrepr, sourcename)
|
||||
else:
|
||||
key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
|
||||
if self.analyzer and key in self.analyzer.annotations:
|
||||
self.add_line(' :type: ' + self.analyzer.annotations[key],
|
||||
sourcename)
|
||||
|
||||
# data descriptors do not have useful values
|
||||
if not self._datadescriptor:
|
||||
try:
|
||||
if self.object is INSTANCEATTR or self.options.no_value:
|
||||
pass
|
||||
else:
|
||||
objrepr = object_description(self.object)
|
||||
self.add_line(' :value: ' + objrepr, sourcename)
|
||||
except ValueError:
|
||||
try:
|
||||
if (self.object is INSTANCEATTR or self.options.no_value or
|
||||
self.should_suppress_value_header()):
|
||||
pass
|
||||
else:
|
||||
objrepr = object_description(self.object)
|
||||
self.add_line(' :value: ' + objrepr, sourcename)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get_doc(self, ignore: int = None) -> List[List[str]]:
|
||||
if not self._datadescriptor:
|
||||
# if it's not a data descriptor, its docstring is very probably the
|
||||
# wrong thing to display
|
||||
if self.object is INSTANCEATTR:
|
||||
return []
|
||||
|
||||
try:
|
||||
|
||||
@@ -13,9 +13,10 @@ import traceback
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, List, NamedTuple, Optional, Tuple
|
||||
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
from sphinx.pycode import ModuleAnalyzer, PycodeError
|
||||
from sphinx.util import logging
|
||||
from sphinx.util.inspect import getannotations, getslots, isclass, isenumclass, safe_getattr
|
||||
from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
|
||||
safe_getattr)
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
@@ -165,12 +166,9 @@ class Attribute(NamedTuple):
|
||||
|
||||
|
||||
def _getmro(obj: Any) -> Tuple["Type", ...]:
|
||||
"""Get __mro__ from given *obj* safely."""
|
||||
__mro__ = safe_getattr(obj, '__mro__', None)
|
||||
if isinstance(__mro__, tuple):
|
||||
return __mro__
|
||||
else:
|
||||
return tuple()
|
||||
warnings.warn('sphinx.ext.autodoc.importer._getmro() is deprecated.',
|
||||
RemovedInSphinx40Warning)
|
||||
return getmro(obj)
|
||||
|
||||
|
||||
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
@@ -218,7 +216,7 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
continue
|
||||
|
||||
# annotation only member (ex. attr: int)
|
||||
for i, cls in enumerate(_getmro(subject)):
|
||||
for i, cls in enumerate(getmro(subject)):
|
||||
try:
|
||||
for name in getannotations(cls):
|
||||
name = unmangle(cls, name)
|
||||
@@ -235,3 +233,88 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
|
||||
return members
|
||||
|
||||
|
||||
class ClassAttribute:
|
||||
"""The attribute of the class."""
|
||||
|
||||
def __init__(self, cls: Any, name: str, value: Any, docstring: Optional[str] = None):
|
||||
self.class_ = cls
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.docstring = docstring
|
||||
|
||||
|
||||
def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
|
||||
) -> Dict[str, ClassAttribute]:
|
||||
"""Get members and attributes of target class."""
|
||||
from sphinx.ext.autodoc import INSTANCEATTR
|
||||
|
||||
# the members directly defined in the class
|
||||
obj_dict = attrgetter(subject, '__dict__', {})
|
||||
|
||||
members = {} # type: Dict[str, ClassAttribute]
|
||||
|
||||
# enum members
|
||||
if isenumclass(subject):
|
||||
for name, value in subject.__members__.items():
|
||||
if name not in members:
|
||||
members[name] = ClassAttribute(subject, name, value)
|
||||
|
||||
superclass = subject.__mro__[1]
|
||||
for name in obj_dict:
|
||||
if name not in superclass.__dict__:
|
||||
value = safe_getattr(subject, name)
|
||||
members[name] = ClassAttribute(subject, name, value)
|
||||
|
||||
# members in __slots__
|
||||
try:
|
||||
__slots__ = getslots(subject)
|
||||
if __slots__:
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
for name, docstring in __slots__.items():
|
||||
members[name] = ClassAttribute(subject, name, SLOTSATTR, docstring)
|
||||
except (AttributeError, TypeError, ValueError):
|
||||
pass
|
||||
|
||||
# other members
|
||||
for name in dir(subject):
|
||||
try:
|
||||
value = attrgetter(subject, name)
|
||||
unmangled = unmangle(subject, name)
|
||||
if unmangled and unmangled not in members:
|
||||
if name in obj_dict:
|
||||
members[unmangled] = ClassAttribute(subject, unmangled, value)
|
||||
else:
|
||||
members[unmangled] = ClassAttribute(None, unmangled, value)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
try:
|
||||
for cls in getmro(subject):
|
||||
# annotation only member (ex. attr: int)
|
||||
try:
|
||||
for name in getannotations(cls):
|
||||
name = unmangle(cls, name)
|
||||
if name and name not in members:
|
||||
members[name] = ClassAttribute(cls, name, INSTANCEATTR)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# append instance attributes (cf. self.attr1) if analyzer knows
|
||||
try:
|
||||
modname = safe_getattr(cls, '__module__')
|
||||
qualname = safe_getattr(cls, '__qualname__')
|
||||
analyzer = ModuleAnalyzer.for_module(modname)
|
||||
analyzer.analyze()
|
||||
for (ns, name), docstring in analyzer.attr_docs.items():
|
||||
if ns == qualname and name not in members:
|
||||
members[name] = ClassAttribute(cls, name, INSTANCEATTR,
|
||||
'\n'.join(docstring))
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return members
|
||||
|
||||
@@ -174,6 +174,8 @@ class GoogleDocstring:
|
||||
'notes': self._parse_notes_section,
|
||||
'other parameters': self._parse_other_parameters_section,
|
||||
'parameters': self._parse_parameters_section,
|
||||
'receive': self._parse_receives_section,
|
||||
'receives': self._parse_receives_section,
|
||||
'return': self._parse_returns_section,
|
||||
'returns': self._parse_returns_section,
|
||||
'raise': self._parse_raises_section,
|
||||
@@ -709,6 +711,15 @@ class GoogleDocstring:
|
||||
lines.append('')
|
||||
return lines
|
||||
|
||||
def _parse_receives_section(self, section: str) -> List[str]:
|
||||
if self._config.napoleon_use_param:
|
||||
# Allow to declare multiple parameters at once (ex: x, y: int)
|
||||
fields = self._consume_fields(multiple=True)
|
||||
return self._format_docutils_params(fields)
|
||||
else:
|
||||
fields = self._consume_fields()
|
||||
return self._format_fields(_('Receives'), fields)
|
||||
|
||||
def _parse_references_section(self, section: str) -> List[str]:
|
||||
use_admonition = self._config.napoleon_use_admonition_for_references
|
||||
return self._parse_generic_section(_('References'), use_admonition)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
|
||||
(sidebars != []) %}
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{# XXX necessary? #}
|
||||
{# URL root should never be #, then all links are fragments #}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
{%- if not embedded and docstitle %}
|
||||
{%- set titlesuffix = " — "|safe + docstitle|e %}
|
||||
@@ -88,7 +88,7 @@
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro script() %}
|
||||
<script id="documentation_options" data-url_root="{{ pathto('', 1) }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
|
||||
<script id="documentation_options" data-url_root="{{ url_root }}" src="{{ pathto('_static/documentation_options.js', 1) }}"></script>
|
||||
{%- for js in script_files %}
|
||||
{{ js_tag(js) }}
|
||||
{%- endfor %}
|
||||
|
||||
@@ -20,7 +20,7 @@ from sphinx.locale import __
|
||||
from sphinx.transforms import SphinxTransform
|
||||
from sphinx.util import epoch_to_rfc1123, logging, requests, rfc1123_to_epoch, sha1
|
||||
from sphinx.util.images import get_image_extension, guess_mimetype, parse_data_uri
|
||||
from sphinx.util.osutil import ensuredir, movefile
|
||||
from sphinx.util.osutil import ensuredir
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -99,7 +99,7 @@ class ImageDownloader(BaseImageConverter):
|
||||
# append a suffix if URI does not contain suffix
|
||||
ext = get_image_extension(mimetype)
|
||||
newpath = os.path.join(self.imagedir, dirname, basename + ext)
|
||||
movefile(path, newpath)
|
||||
os.replace(path, newpath)
|
||||
self.app.env.original_image_uri.pop(path)
|
||||
self.app.env.original_image_uri[newpath] = node['uri']
|
||||
path = newpath
|
||||
|
||||
@@ -271,6 +271,7 @@ class DocFieldTransformer:
|
||||
self.directive.domain,
|
||||
target,
|
||||
contnode=content[0],
|
||||
env=self.directive.state.document.settings.env
|
||||
)
|
||||
if _is_single_paragraph(field_body):
|
||||
paragraph = cast(nodes.paragraph, field_body[0])
|
||||
|
||||
@@ -20,7 +20,7 @@ import warnings
|
||||
from functools import partial, partialmethod
|
||||
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
|
||||
from io import StringIO
|
||||
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, cast
|
||||
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, cast
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
from sphinx.pycode.ast import ast # for py36-37
|
||||
@@ -36,6 +36,10 @@ else:
|
||||
MethodDescriptorType = type(str.join)
|
||||
WrapperDescriptorType = type(dict.__dict__['fromkeys'])
|
||||
|
||||
if False:
|
||||
# For type annotation
|
||||
from typing import Type # NOQA
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
memory_address_re = re.compile(r' at 0x[0-9a-f]{8,16}(?=>)', re.IGNORECASE)
|
||||
@@ -166,6 +170,18 @@ def getannotations(obj: Any) -> Mapping[str, Any]:
|
||||
return {}
|
||||
|
||||
|
||||
def getmro(obj: Any) -> Tuple["Type", ...]:
|
||||
"""Get __mro__ from given *obj* safely.
|
||||
|
||||
Raises AttributeError if given *obj* raises an error on accessing __mro__.
|
||||
"""
|
||||
__mro__ = safe_getattr(obj, '__mro__', None)
|
||||
if isinstance(__mro__, tuple):
|
||||
return __mro__
|
||||
else:
|
||||
return tuple()
|
||||
|
||||
|
||||
def getslots(obj: Any) -> Optional[Dict]:
|
||||
"""Get __slots__ attribute of the class as dict.
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ from io import StringIO
|
||||
from os import path
|
||||
from typing import Any, Generator, Iterator, List, Optional, Type
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx50Warning
|
||||
|
||||
try:
|
||||
# for ALT Linux (#6712)
|
||||
from sphinx.testing.path import path as Path
|
||||
@@ -83,6 +85,9 @@ def mtimes_of_files(dirnames: List[str], suffix: str) -> Iterator[float]:
|
||||
|
||||
def movefile(source: str, dest: str) -> None:
|
||||
"""Move a file, removing the destination if it exists."""
|
||||
warnings.warn('sphinx.util.osutil.movefile() is deprecated for removal. '
|
||||
'Please use os.replace() instead.',
|
||||
RemovedInSphinx50Warning, stacklevel=2)
|
||||
if os.path.exists(dest):
|
||||
try:
|
||||
os.unlink(dest)
|
||||
|
||||
@@ -7,6 +7,9 @@ myint = int
|
||||
#: docstring
|
||||
variable: myint
|
||||
|
||||
#: docstring
|
||||
variable2 = None # type: myint
|
||||
|
||||
|
||||
def sum(x: myint, y: myint) -> myint:
|
||||
"""docstring"""
|
||||
@@ -32,4 +35,7 @@ class Foo:
|
||||
"""docstring"""
|
||||
|
||||
#: docstring
|
||||
attr: myint
|
||||
attr1: myint
|
||||
|
||||
def __init__(self):
|
||||
self.attr2: myint = None #: docstring
|
||||
|
||||
10
tests/roots/test-ext-autodoc/target/instance_variable.py
Normal file
10
tests/roots/test-ext-autodoc/target/instance_variable.py
Normal file
@@ -0,0 +1,10 @@
|
||||
class Foo:
|
||||
def __init__(self):
|
||||
self.attr1 = None #: docstring foo
|
||||
self.attr2 = None #: docstring foo
|
||||
|
||||
|
||||
class Bar(Foo):
|
||||
def __init__(self):
|
||||
self.attr2 = None #: docstring bar
|
||||
self.attr3 = None #: docstring bar
|
||||
@@ -1,8 +1,12 @@
|
||||
class Foo:
|
||||
"""docstring"""
|
||||
|
||||
__slots__ = ['attr']
|
||||
|
||||
|
||||
class Bar:
|
||||
"""docstring"""
|
||||
|
||||
__slots__ = {'attr1': 'docstring of attr1',
|
||||
'attr2': 'docstring of attr2',
|
||||
'attr3': None}
|
||||
@@ -12,4 +16,6 @@ class Bar:
|
||||
|
||||
|
||||
class Baz:
|
||||
"""docstring"""
|
||||
|
||||
__slots__ = 'attr'
|
||||
|
||||
@@ -29,3 +29,6 @@ class Class:
|
||||
|
||||
class Derived(Class):
|
||||
attr7: int
|
||||
|
||||
|
||||
Alias = Derived
|
||||
|
||||
@@ -36,7 +36,11 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir):
|
||||
if not srcdir.exists():
|
||||
(rootdir / 'test-root').copytree(srcdir)
|
||||
except UnicodeEncodeError:
|
||||
# Now Python 3.7+ follows PEP-540 and uses utf-8 encoding for filesystem by default.
|
||||
# So this error handling will be no longer used (after dropping python 3.6 support).
|
||||
srcdir = basedir / 'all'
|
||||
if not srcdir.exists():
|
||||
(rootdir / 'test-root').copytree(srcdir)
|
||||
else:
|
||||
# add a doc with a non-ASCII file name to the source dir
|
||||
(srcdir / (test_name + '.txt')).write_text(dedent("""
|
||||
|
||||
@@ -791,6 +791,53 @@ def test_canonical(app):
|
||||
assert domain.objects['_io.StringIO'] == ('index', 'io.StringIO', 'class', True)
|
||||
|
||||
|
||||
def test_info_field_list(app):
|
||||
text = (".. py:module:: example\n"
|
||||
".. py:class:: Class\n"
|
||||
"\n"
|
||||
" :param str name: blah blah\n"
|
||||
" :param age: blah blah\n"
|
||||
" :type age: int\n")
|
||||
doctree = restructuredtext.parse(app, text)
|
||||
print(doctree)
|
||||
|
||||
assert_node(doctree, (nodes.target,
|
||||
addnodes.index,
|
||||
addnodes.index,
|
||||
[desc, ([desc_signature, ([desc_annotation, "class "],
|
||||
[desc_addname, "example."],
|
||||
[desc_name, "Class"])],
|
||||
[desc_content, nodes.field_list, nodes.field])]))
|
||||
assert_node(doctree[3][1][0][0],
|
||||
([nodes.field_name, "Parameters"],
|
||||
[nodes.field_body, nodes.bullet_list, ([nodes.list_item, nodes.paragraph],
|
||||
[nodes.list_item, nodes.paragraph])]))
|
||||
|
||||
# :param str name:
|
||||
assert_node(doctree[3][1][0][0][1][0][0][0],
|
||||
([addnodes.literal_strong, "name"],
|
||||
" (",
|
||||
[pending_xref, addnodes.literal_emphasis, "str"],
|
||||
")",
|
||||
" -- ",
|
||||
"blah blah"))
|
||||
assert_node(doctree[3][1][0][0][1][0][0][0][2], pending_xref,
|
||||
refdomain="py", reftype="class", reftarget="str",
|
||||
**{"py:module": "example", "py:class": "Class"})
|
||||
|
||||
# :param age: + :type age:
|
||||
assert_node(doctree[3][1][0][0][1][0][1][0],
|
||||
([addnodes.literal_strong, "age"],
|
||||
" (",
|
||||
[pending_xref, addnodes.literal_emphasis, "int"],
|
||||
")",
|
||||
" -- ",
|
||||
"blah blah"))
|
||||
assert_node(doctree[3][1][0][0][1][0][1][0][2], pending_xref,
|
||||
refdomain="py", reftype="class", reftarget="int",
|
||||
**{"py:module": "example", "py:class": "Class"})
|
||||
|
||||
|
||||
@pytest.mark.sphinx(freshenv=True)
|
||||
def test_module_index(app):
|
||||
text = (".. py:module:: docutils\n"
|
||||
|
||||
@@ -1171,6 +1171,8 @@ def test_slots(app):
|
||||
'.. py:class:: Bar()',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr1',
|
||||
' :module: target.slots',
|
||||
@@ -1191,6 +1193,8 @@ def test_slots(app):
|
||||
'.. py:class:: Baz()',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Baz.attr',
|
||||
' :module: target.slots',
|
||||
@@ -1199,6 +1203,8 @@ def test_slots(app):
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr',
|
||||
' :module: target.slots',
|
||||
@@ -1559,6 +1565,11 @@ def test_autodoc_typed_instance_variables(app):
|
||||
'.. py:module:: target.typed_vars',
|
||||
'',
|
||||
'',
|
||||
'.. py:attribute:: Alias',
|
||||
' :module: target.typed_vars',
|
||||
'',
|
||||
' alias of :class:`target.typed_vars.Derived`',
|
||||
'',
|
||||
'.. py:class:: Class()',
|
||||
' :module: target.typed_vars',
|
||||
'',
|
||||
@@ -1667,9 +1678,31 @@ def test_autodoc_typed_inherited_instance_variables(app):
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr3',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
' :value: 0',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr4',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
' attr4',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr5',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
' attr5',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr6',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
' attr6',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Derived.attr7',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
|
||||
@@ -57,6 +57,19 @@ def test_autoattribute_typed_variable(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_typed_variable_in_alias(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr2')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Alias.attr2',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_instance_variable(app):
|
||||
@@ -72,6 +85,21 @@ def test_autoattribute_instance_variable(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason='python 3.6+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_instance_variable_in_alias(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.typed_vars.Alias.attr4')
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:attribute:: Alias.attr4',
|
||||
' :module: target.typed_vars',
|
||||
' :type: int',
|
||||
'',
|
||||
' attr4',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_autoattribute_slots_variable_list(app):
|
||||
actual = do_autodoc(app, 'attribute', 'target.slots.Foo.attr')
|
||||
|
||||
@@ -51,6 +51,61 @@ def test_classes(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_instance_variable(app):
|
||||
options = {'members': True}
|
||||
actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Bar()',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr2',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
' docstring bar',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr3',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
' docstring bar',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_inherited_instance_variable(app):
|
||||
options = {'members': True,
|
||||
'inherited-members': True}
|
||||
actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Bar()',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr1',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
' docstring foo',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr2',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
' docstring bar',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr3',
|
||||
' :module: target.instance_variable',
|
||||
'',
|
||||
' docstring bar',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
def test_decorators(app):
|
||||
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
|
||||
assert list(actual) == [
|
||||
@@ -77,6 +132,32 @@ def test_decorators(app):
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_slots_attribute(app):
|
||||
options = {"members": None}
|
||||
actual = do_autodoc(app, 'class', 'target.slots.Bar', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:class:: Bar()',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr1',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring of attr1',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.attr2',
|
||||
' :module: target.slots',
|
||||
'',
|
||||
' docstring of instance attr2',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_show_inheritance_for_subclass_of_generic_type(app):
|
||||
|
||||
@@ -707,7 +707,14 @@ def test_autodoc_type_aliases(app):
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr',
|
||||
' .. py:attribute:: Foo.attr1',
|
||||
' :module: target.annotations',
|
||||
' :type: int',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr2',
|
||||
' :module: target.annotations',
|
||||
' :type: int',
|
||||
'',
|
||||
@@ -733,6 +740,14 @@ def test_autodoc_type_aliases(app):
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: variable2',
|
||||
' :module: target.annotations',
|
||||
' :type: int',
|
||||
' :value: None',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
# define aliases
|
||||
@@ -749,7 +764,14 @@ def test_autodoc_type_aliases(app):
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr',
|
||||
' .. py:attribute:: Foo.attr1',
|
||||
' :module: target.annotations',
|
||||
' :type: myint',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.attr2',
|
||||
' :module: target.annotations',
|
||||
' :type: myint',
|
||||
'',
|
||||
@@ -775,6 +797,14 @@ def test_autodoc_type_aliases(app):
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
'',
|
||||
'.. py:data:: variable2',
|
||||
' :module: target.annotations',
|
||||
' :type: myint',
|
||||
' :value: None',
|
||||
'',
|
||||
' docstring',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -303,6 +303,34 @@ class GoogleDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Receive:
|
||||
arg1 (list(int)): Description
|
||||
arg2 (list[int]): Description
|
||||
""",
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Receives: * **arg1** (*list(int)*) -- Description
|
||||
* **arg2** (*list[int]*) -- Description
|
||||
"""
|
||||
), (
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Receives:
|
||||
arg1 (list(int)): Description
|
||||
arg2 (list[int]): Description
|
||||
""",
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Receives: * **arg1** (*list(int)*) -- Description
|
||||
* **arg2** (*list[int]*) -- Description
|
||||
"""
|
||||
), (
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Yield:
|
||||
str:Extended
|
||||
description of yielded value
|
||||
@@ -1263,6 +1291,48 @@ class NumpyDocstringTest(BaseDocstringTest):
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Receive
|
||||
-------
|
||||
arg1:str
|
||||
Extended
|
||||
description of arg1
|
||||
arg2 : int
|
||||
Extended
|
||||
description of arg2
|
||||
""",
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Receives: * **arg1** (:class:`str`) -- Extended
|
||||
description of arg1
|
||||
* **arg2** (:class:`int`) -- Extended
|
||||
description of arg2
|
||||
"""
|
||||
), (
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Receives
|
||||
--------
|
||||
arg1:str
|
||||
Extended
|
||||
description of arg1
|
||||
arg2 : int
|
||||
Extended
|
||||
description of arg2
|
||||
""",
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
:Receives: * **arg1** (:class:`str`) -- Extended
|
||||
description of arg1
|
||||
* **arg2** (:class:`int`) -- Extended
|
||||
description of arg2
|
||||
"""
|
||||
), (
|
||||
"""
|
||||
Single line summary
|
||||
|
||||
Yield
|
||||
-----
|
||||
str
|
||||
|
||||
Reference in New Issue
Block a user