Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sphinx-doc/sphinx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES21
-rw-r--r--doc/usage/configuration.rst2
-rw-r--r--setup.py1
-rw-r--r--sphinx/application.py11
-rw-r--r--sphinx/builders/latex/__init__.py9
-rw-r--r--sphinx/ext/autodoc/__init__.py215
-rw-r--r--sphinx/ext/napoleon/docstring.py2
-rw-r--r--sphinx/registry.py9
-rw-r--r--sphinx/templates/latex/latex.tex_t8
-rw-r--r--sphinx/util/inspect.py2
-rw-r--r--tests/roots/test-ext-autodoc/target/sort_by_all.py25
-rw-r--r--tests/roots/test-ext-autodoc/target/typehints.py21
-rw-r--r--tests/test_ext_autodoc.py133
-rw-r--r--tests/test_ext_autodoc_configs.py49
-rw-r--r--tests/test_ext_autosummary.py2
-rw-r--r--tests/test_ext_napoleon_docstring.py21
-rw-r--r--tests/test_project.py4
-rw-r--r--tests/test_util_inspect.py14
18 files changed, 433 insertions, 116 deletions
diff --git a/CHANGES b/CHANGES
index df999d296..166d8fb8a 100644
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,8 @@ Features added
* #7487: autodoc: Allow to generate docs for singledispatch functions by
py:autofunction
* #7143: autodoc: Support final classes and methods
+* #7384: autodoc: Support signatures defined by ``__new__()``, metaclasses and
+ builtin base classes
* #7466: autosummary: headings in generated documents are not translated
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
caption to the toctree
@@ -75,6 +77,7 @@ Features added
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
* #7596: py domain: Change a type annotation for variables to a hyperlink
* #7582: napoleon: a type for attribute are represented like type annotation
+* #7734: napoleon: overescaped trailing underscore on attribute
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions
@@ -100,8 +103,11 @@ Bugs fixed
* #7676: autodoc: typo in the default value of autodoc_member_order
* #7676: autodoc: wrong value for :member-order: option is ignored silently
* #7676: autodoc: member-order="bysource" does not work for C module
+* #3673: autodoc: member-order="bysource" does not work for a module having
+ __all__
* #7668: autodoc: wrong retann value is passed to a handler of
autodoc-proccess-signature
+* #7711: autodoc: fails with ValueError when processing numpy objects
* #7551: autosummary: a nested class is indexed as non-nested class
* #7661: autosummary: autosummary directive emits warnings twices if failed to
import the target module
@@ -117,6 +123,7 @@ Bugs fixed
supporting images
* #7610: incorrectly renders consecutive backslashes for docutils-0.16
* #7646: handle errors on event handlers
+* #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents
* #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels
* C++, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``::A::B``.
@@ -126,7 +133,7 @@ Bugs fixed
Testing
--------
-Release 3.0.4 (in development)
+Release 3.0.5 (in development)
==============================
Dependencies
@@ -144,15 +151,21 @@ Features added
Bugs fixed
----------
+Testing
+--------
+
+Release 3.0.4 (released May 27, 2020)
+=====================================
+
+Bugs fixed
+----------
+
* #7567: autodoc: parametrized types are shown twice for generic types
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
* #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons
* #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT``
-Testing
---------
-
Release 3.0.3 (released Apr 26, 2020)
=====================================
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 1b6ea212f..bc483fa1c 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -575,7 +575,7 @@ General configuration
A dictionary of options that modify how the lexer specified by
:confval:`highlight_language` generates highlighted source code. These are
lexer-specific; for the options understood by each, see the
- `Pygments documentation <https://pygments.org/docs/lexers.html>`_.
+ `Pygments documentation <https://pygments.org/docs/lexers>`_.
.. versionadded:: 1.3
diff --git a/setup.py b/setup.py
index a427d5493..5e822fe9b 100644
--- a/setup.py
+++ b/setup.py
@@ -202,6 +202,7 @@ setup(
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Framework :: Setuptools Plugin',
diff --git a/sphinx/application.py b/sphinx/application.py
index b02fb4d60..bd23c86e7 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -968,12 +968,14 @@ class Sphinx:
self.add_css_file(filename, **attributes)
- def add_latex_package(self, packagename: str, options: str = None) -> None:
+ def add_latex_package(self, packagename: str, options: str = None,
+ after_hyperref: bool = False) -> None:
r"""Register a package to include in the LaTeX source code.
Add *packagename* to the list of packages that LaTeX source code will
include. If you provide *options*, it will be taken to `\usepackage`
- declaration.
+ declaration. If you set *after_hyperref* truthy, the package will be
+ loaded after ``hyperref`` package.
.. code-block:: python
@@ -983,8 +985,11 @@ class Sphinx:
# => \usepackage[foo,bar]{mypackage}
.. versionadded:: 1.3
+ .. versionadded:: 3.1
+
+ *after_hyperref* option.
"""
- self.registry.add_latex_package(packagename, options)
+ self.registry.add_latex_package(packagename, options, after_hyperref)
def add_lexer(self, alias: str, lexer: Union[Lexer, "Type[Lexer]"]) -> None:
"""Register a new lexer for source code.
diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py
index 225975c20..88c471675 100644
--- a/sphinx/builders/latex/__init__.py
+++ b/sphinx/builders/latex/__init__.py
@@ -129,6 +129,7 @@ class LaTeXBuilder(Builder):
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages
+ self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
texescape.init()
self.init_context()
@@ -180,6 +181,7 @@ class LaTeXBuilder(Builder):
# Apply extension settings to context
self.context['packages'] = self.usepackages
+ self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
# Apply user settings to context
self.context.update(self.config.latex_elements)
@@ -501,6 +503,12 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
config.latex_theme_options.pop(key)
+def install_pakcages_for_ja(app: Sphinx) -> None:
+ """Install packages for Japanese."""
+ if app.config.language == 'ja':
+ app.add_latex_package('pxjahyper', after_hyperref=True)
+
+
def default_latex_engine(config: Config) -> str:
""" Better default latex_engine settings for specific languages. """
if config.language == 'ja':
@@ -548,6 +556,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(LaTeXBuilder)
app.connect('config-inited', validate_config_values, priority=800)
app.connect('config-inited', validate_latex_theme_options, priority=800)
+ app.connect('builder-inited', install_pakcages_for_ja)
app.add_config_value('latex_engine', default_latex_engine, None,
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex'))
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index f9c775f7d..f8e4be999 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -13,7 +13,7 @@
import importlib
import re
import warnings
-from inspect import Parameter
+from inspect import Parameter, Signature
from types import ModuleType
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
@@ -392,6 +392,17 @@ class Documenter:
# directives of course)
return '.'.join(self.objpath) or self.modname
+ def _call_format_args(self, **kwargs: Any) -> str:
+ if kwargs:
+ try:
+ return self.format_args(**kwargs)
+ except TypeError:
+ # avoid chaining exceptions, by putting nothing here
+ pass
+
+ # retry without arguments for old documenters
+ return self.format_args()
+
def format_signature(self, **kwargs: Any) -> str:
"""Format the signature (arguments and return annotation) of the object.
@@ -405,12 +416,7 @@ class Documenter:
# try to introspect the signature
try:
retann = None
- try:
- args = self.format_args(**kwargs)
- except TypeError:
- # retry without arguments for old documenters
- args = self.format_args()
-
+ args = self._call_format_args(**kwargs)
if args:
matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args)
if matched:
@@ -714,12 +720,26 @@ class Documenter:
'.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr))
- member_order = self.options.member_order or \
- self.env.config.autodoc_member_order
- if member_order == 'groupwise':
+
+ member_order = self.options.member_order or self.env.config.autodoc_member_order
+ memberdocumenters = self.sort_members(memberdocumenters, member_order)
+
+ for documenter, isattr in memberdocumenters:
+ documenter.generate(
+ all_members=True, real_modname=self.real_modname,
+ check_module=members_check_module and not isattr)
+
+ # reset current objects
+ self.env.temp_data['autodoc:module'] = None
+ self.env.temp_data['autodoc:class'] = None
+
+ def sort_members(self, documenters: List[Tuple["Documenter", bool]],
+ order: str) -> List[Tuple["Documenter", bool]]:
+ """Sort the given member list."""
+ if order == 'groupwise':
# sort by group; alphabetically within groups
- memberdocumenters.sort(key=lambda e: (e[0].member_order, e[0].name))
- elif member_order == 'bysource':
+ documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
+ elif order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
@@ -727,25 +747,16 @@ class Documenter:
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
- memberdocumenters.sort(key=keyfunc)
+ documenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
- elif member_order == 'alphabetical':
- memberdocumenters.sort(key=lambda e: e[0].name)
- else:
- raise ValueError("Illegal member order {}".format(member_order))
+ else: # alphabetical
+ documenters.sort(key=lambda e: e[0].name)
- for documenter, isattr in memberdocumenters:
- documenter.generate(
- all_members=True, real_modname=self.real_modname,
- check_module=members_check_module and not isattr)
-
- # reset current objects
- self.env.temp_data['autodoc:module'] = None
- self.env.temp_data['autodoc:class'] = None
+ return documenters
def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
@@ -852,6 +863,7 @@ class ModuleDocumenter(Documenter):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
+ self.__all__ = None
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@@ -874,6 +886,30 @@ class ModuleDocumenter(Documenter):
type='autodoc')
return ret
+ def import_object(self) -> Any:
+ def is_valid_module_all(__all__: Any) -> bool:
+ """Check the given *__all__* is valid for a module."""
+ if (isinstance(__all__, (list, tuple)) and
+ all(isinstance(e, str) for e in __all__)):
+ return True
+ else:
+ return False
+
+ ret = super().import_object()
+
+ if not self.options.ignore_module_all:
+ __all__ = getattr(self.object, '__all__', None)
+ if is_valid_module_all(__all__):
+ # valid __all__ found. copy it to self.__all__
+ self.__all__ = __all__
+ elif __all__:
+ # invalid __all__ found.
+ logger.warning(__('__all__ should be a list of strings, not %r '
+ '(in module %s) -- ignoring __all__') %
+ (__all__, self.fullname), type='autodoc')
+
+ return ret
+
def add_directive_header(self, sig: str) -> None:
Documenter.add_directive_header(self, sig)
@@ -889,24 +925,12 @@ class ModuleDocumenter(Documenter):
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
if want_all:
- if (self.options.ignore_module_all or not
- hasattr(self.object, '__all__')):
+ if self.__all__:
+ memberlist = self.__all__
+ else:
# for implicit module members, check __module__ to avoid
# documenting imported objects
return True, get_module_members(self.object)
- else:
- memberlist = self.object.__all__
- # Sometimes __all__ is broken...
- if not isinstance(memberlist, (list, tuple)) or not \
- all(isinstance(entry, str) for entry in memberlist):
- logger.warning(
- __('__all__ should be a list of strings, not %r '
- '(in module %s) -- ignoring __all__') %
- (memberlist, self.fullname),
- type='autodoc'
- )
- # fall back to all members
- return True, get_module_members(self.object)
else:
memberlist = self.options.members or []
ret = []
@@ -922,6 +946,25 @@ class ModuleDocumenter(Documenter):
)
return False, ret
+ def sort_members(self, documenters: List[Tuple["Documenter", bool]],
+ order: str) -> List[Tuple["Documenter", bool]]:
+ if order == 'bysource' and self.__all__:
+ # Sort alphabetically first (for members not listed on the __all__)
+ documenters.sort(key=lambda e: e[0].name)
+
+ # Sort by __all__
+ def keyfunc(entry: Tuple[Documenter, bool]) -> int:
+ name = entry[0].name.split('::')[1]
+ if name in self.__all__:
+ return self.__all__.index(name)
+ else:
+ return len(self.__all__)
+ documenters.sort(key=keyfunc)
+
+ return documenters
+ else:
+ return super().sort_members(documenters, order)
+
class ModuleLevelDocumenter(Documenter):
"""
@@ -1168,6 +1211,14 @@ class DecoratorDocumenter(FunctionDocumenter):
return None
+# Types which have confusing metaclass signatures it would be best not to show.
+# These are listed by name, rather than storing the objects themselves, to avoid
+# needing to import the modules.
+_METACLASS_CALL_BLACKLIST = [
+ 'enum.EnumMeta.__call__',
+]
+
+
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for classes.
@@ -1202,27 +1253,83 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
+ def _get_signature(self) -> Optional[Signature]:
+ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
+ """ Get the `attr` function or method from `obj`, if it is user-defined. """
+ if inspect.is_builtin_class_method(obj, attr):
+ return None
+ attr = self.get_attr(obj, attr, None)
+ if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
+ return None
+ return attr
+
+ # This sequence is copied from inspect._signature_from_callable.
+ # ValueError means that no signature could be found, so we keep going.
+
+ # First, let's see if it has an overloaded __call__ defined
+ # in its metaclass
+ call = get_user_defined_function_or_method(type(self.object), '__call__')
+
+ if call is not None:
+ if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST:
+ call = None
+
+ if call is not None:
+ self.env.app.emit('autodoc-before-process-signature', call, True)
+ try:
+ return inspect.signature(call, bound_method=True)
+ except ValueError:
+ pass
+
+ # Now we check if the 'obj' class has a '__new__' method
+ new = get_user_defined_function_or_method(self.object, '__new__')
+ if new is not None:
+ self.env.app.emit('autodoc-before-process-signature', new, True)
+ try:
+ return inspect.signature(new, bound_method=True)
+ except ValueError:
+ pass
+
+ # Finally, we should have at least __init__ implemented
+ init = get_user_defined_function_or_method(self.object, '__init__')
+ if init is not None:
+ self.env.app.emit('autodoc-before-process-signature', init, True)
+ try:
+ return inspect.signature(init, bound_method=True)
+ except ValueError:
+ pass
+
+ # None of the attributes are user-defined, so fall back to let inspect
+ # handle it.
+ # We don't know the exact method that inspect.signature will read
+ # the signature from, so just pass the object itself to our hook.
+ self.env.app.emit('autodoc-before-process-signature', self.object, False)
+ try:
+ return inspect.signature(self.object, bound_method=False)
+ except ValueError:
+ pass
+
+ # Still no signature: happens e.g. for old-style classes
+ # with __init__ in C and no `__text_signature__`.
+ return None
+
def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
- # for classes, the relevant signature is the __init__ method's
- initmeth = self.get_attr(self.object, '__init__', None)
- # classes without __init__ method, default __init__ or
- # __init__ written in C?
- if initmeth is None or \
- inspect.is_builtin_class_method(self.object, '__init__') or \
- not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
- return None
try:
- self.env.app.emit('autodoc-before-process-signature', initmeth, True)
- sig = inspect.signature(initmeth, bound_method=True)
- return stringify_signature(sig, show_return_annotation=False, **kwargs)
- except TypeError:
- # still not possible: happens e.g. for old-style classes
- # with __init__ in C
+ sig = self._get_signature()
+ except TypeError as exc:
+ # __signature__ attribute contained junk
+ logger.warning(__("Failed to get a constructor signature for %s: %s"),
+ self.fullname, exc)
+ return None
+
+ if sig is None:
return None
+ return stringify_signature(sig, show_return_annotation=False, **kwargs)
+
def format_signature(self, **kwargs: Any) -> str:
if self.doc_as_attr:
return ''
diff --git a/sphinx/ext/napoleon/docstring.py b/sphinx/ext/napoleon/docstring.py
index 11409e6f6..32edd7f8f 100644
--- a/sphinx/ext/napoleon/docstring.py
+++ b/sphinx/ext/napoleon/docstring.py
@@ -318,7 +318,7 @@ class GoogleDocstring:
return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name: str) -> str:
- if name.endswith('_'):
+ if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False):
name = name[:-1] + r'\_'
if name[:2] == '**':
diff --git a/sphinx/registry.py b/sphinx/registry.py
index 200f59d42..cad74559c 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -98,6 +98,8 @@ class SphinxComponentRegistry:
#: LaTeX packages; list of package names and its options
self.latex_packages = [] # type: List[Tuple[str, str]]
+ self.latex_packages_after_hyperref = [] # type: List[Tuple[str, str]]
+
#: post transforms; list of transforms
self.post_transforms = [] # type: List[Type[Transform]]
@@ -363,9 +365,12 @@ class SphinxComponentRegistry:
logger.debug('[app] adding js_file: %r, %r', filename, attributes)
self.js_files.append((filename, attributes))
- def add_latex_package(self, name: str, options: str) -> None:
+ def add_latex_package(self, name: str, options: str, after_hyperref: bool = False) -> None:
logger.debug('[app] adding latex package: %r', name)
- self.latex_packages.append((name, options))
+ if after_hyperref:
+ self.latex_packages_after_hyperref.append((name, options))
+ else:
+ self.latex_packages.append((name, options))
def add_enumerable_node(self, node: "Type[Node]", figtype: str,
title_getter: TitleGetter = None, override: bool = False) -> None:
diff --git a/sphinx/templates/latex/latex.tex_t b/sphinx/templates/latex/latex.tex_t
index a0a5a26b1..5082254e7 100644
--- a/sphinx/templates/latex/latex.tex_t
+++ b/sphinx/templates/latex/latex.tex_t
@@ -46,6 +46,14 @@
<%- endfor %>
<%= hyperref %>
+<%- for name, option in packages_after_hyperref %>
+<%- if option %>
+\usepackage[<%= option %>]{<%= name %>}
+<%- else %>
+\usepackage{<%= name %>}
+<%- endif %>
+<%- endfor %>
+
<%= contentsname %>
\usepackage{sphinxmessages}
<%= tocdepth %>
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 6ba698eed..15f0d66e2 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -726,7 +726,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
# This tries to obtain the docstring from super classes.
for basecls in getattr(cls, '__mro__', []):
meth = safe_getattr(basecls, name, None)
- if meth:
+ if meth is not None:
doc = inspect.getdoc(meth)
if doc:
break
diff --git a/tests/roots/test-ext-autodoc/target/sort_by_all.py b/tests/roots/test-ext-autodoc/target/sort_by_all.py
new file mode 100644
index 000000000..03def4715
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/sort_by_all.py
@@ -0,0 +1,25 @@
+__all__ = ['baz', 'foo', 'Bar']
+
+
+def foo():
+ pass
+
+
+class Bar:
+ pass
+
+
+def baz():
+ pass
+
+
+def qux():
+ pass
+
+
+class Quux:
+ pass
+
+
+def foobar():
+ pass
diff --git a/tests/roots/test-ext-autodoc/target/typehints.py b/tests/roots/test-ext-autodoc/target/typehints.py
index 4503d41e4..1a70eca67 100644
--- a/tests/roots/test-ext-autodoc/target/typehints.py
+++ b/tests/roots/test-ext-autodoc/target/typehints.py
@@ -37,6 +37,26 @@ def tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]:
pass
+class NewAnnotation:
+ def __new__(cls, i: int) -> 'NewAnnotation':
+ pass
+
+
+class NewComment:
+ def __new__(cls, i):
+ # type: (int) -> NewComment
+ pass
+
+
+class _MetaclassWithCall(type):
+ def __call__(cls, a: int):
+ pass
+
+
+class SignatureFromMetaclass(metaclass=_MetaclassWithCall):
+ pass
+
+
def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
pass
@@ -48,4 +68,3 @@ def missing_attr(c,
):
# type: (...) -> str
return a + (b or "")
-
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index 9dbc311b8..c1799778c 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -169,21 +169,64 @@ def test_format_signature(app):
pass
class E:
- pass
- # no signature for classes without __init__
+ def __init__(self):
+ pass
+
+ # an empty init and no init are the same
for C in (D, E):
- assert formatsig('class', 'D', C, None, None) == ''
+ assert formatsig('class', 'D', C, None, None) == '()'
+
+
+ class SomeMeta(type):
+ def __call__(cls, a, b=None):
+ return type.__call__(cls, a, b)
+ # these three are all equivalent
class F:
def __init__(self, a, b=None):
pass
+ class FNew:
+ def __new__(cls, a, b=None):
+ return super().__new__(cls)
+
+ class FMeta(metaclass=SomeMeta):
+ pass
+
+ # and subclasses should always inherit
class G(F):
pass
- for C in (F, G):
+
+ class GNew(FNew):
+ pass
+
+ class GMeta(FMeta):
+ pass
+
+ # subclasses inherit
+ for C in (F, FNew, FMeta, G, GNew, GMeta):
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
+
+ class ListSubclass(list):
+ pass
+
+ # only supported if the python implementation decides to document it
+ if getattr(list, '__text_signature__', None) is not None:
+ assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
+ else:
+ assert formatsig('class', 'C', ListSubclass, None, None) == ''
+
+
+ class ExceptionSubclass(Exception):
+ pass
+
+ # Exception has no __text_signature__ at least in Python 3.8
+ if getattr(Exception, '__text_signature__', None) is None:
+ assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
+
+
# __init__ have signature at first line of docstring
directive.env.config.autoclass_content = 'both'
@@ -497,14 +540,14 @@ def test_autodoc_members(app):
# default (no-members)
actual = do_autodoc(app, 'class', 'target.inheritance.Base')
assert list(filter(lambda l: '::' in l, actual)) == [
- '.. py:class:: Base',
+ '.. py:class:: Base()',
]
# default ALL-members
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
- '.. py:class:: Base',
+ '.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)'
@@ -514,7 +557,7 @@ def test_autodoc_members(app):
options = {"members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
- '.. py:class:: Base',
+ '.. py:class:: Base()',
' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)'
]
@@ -526,7 +569,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
- '.. py:class:: Base',
+ '.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()'
]
@@ -535,7 +578,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
- '.. py:class:: Base',
+ '.. py:class:: Base()',
]
@@ -679,10 +722,10 @@ def test_autodoc_ignore_module_all(app):
assert list(filter(lambda l: 'class::' in l, actual)) == [
'.. py:class:: Class(arg)',
'.. py:class:: CustomDict',
- '.. py:class:: InnerChild',
+ '.. py:class:: InnerChild()',
'.. py:class:: InstAttCls()',
- '.. py:class:: Outer',
- ' .. py:class:: Outer.Inner',
+ '.. py:class:: Outer()',
+ ' .. py:class:: Outer.Inner()',
'.. py:class:: StrRepr'
]
@@ -703,7 +746,7 @@ def test_autodoc_noindex(app):
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(actual) == [
'',
- '.. py:class:: Base',
+ '.. py:class:: Base()',
' :noindex:',
' :module: target.inheritance',
''
@@ -730,13 +773,13 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer', options)
assert list(actual) == [
'',
- '.. py:class:: Outer',
+ '.. py:class:: Outer()',
' :module: target',
'',
' Foo',
'',
'',
- ' .. py:class:: Outer.Inner',
+ ' .. py:class:: Outer.Inner()',
' :module: target',
'',
' Foo',
@@ -757,7 +800,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
assert list(actual) == [
'',
- '.. py:class:: Outer.Inner',
+ '.. py:class:: Outer.Inner()',
' :module: target',
'',
' Foo',
@@ -774,7 +817,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.InnerChild', options)
assert list(actual) == [
'',
- '.. py:class:: InnerChild',
+ '.. py:class:: InnerChild()',
' :module: target', '',
' Bases: :class:`target.Outer.Inner`',
'',
@@ -818,7 +861,7 @@ def test_autodoc_descriptor(app):
actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
assert list(actual) == [
'',
- '.. py:class:: Class',
+ '.. py:class:: Class()',
' :module: target.descriptor',
'',
'',
@@ -915,6 +958,40 @@ def test_autodoc_member_order(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_module_member_order(app):
+ # case member-order='bysource'
+ options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
+ 'member-order': 'bysource',
+ "undoc-members": True}
+ actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
+ assert list(filter(lambda l: '::' in l, actual)) == [
+ '.. py:module:: target.sort_by_all',
+ '.. py:function:: baz()',
+ '.. py:function:: foo()',
+ '.. py:class:: Bar()',
+ '.. py:class:: Quux()',
+ '.. py:function:: foobar()',
+ '.. py:function:: qux()',
+ ]
+
+ # case member-order='bysource' and ignore-module-all
+ options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
+ 'member-order': 'bysource',
+ "undoc-members": True,
+ "ignore-module-all": True}
+ actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
+ assert list(filter(lambda l: '::' in l, actual)) == [
+ '.. py:module:: target.sort_by_all',
+ '.. py:function:: foo()',
+ '.. py:class:: Bar()',
+ '.. py:function:: baz()',
+ '.. py:function:: qux()',
+ '.. py:class:: Quux()',
+ '.. py:function:: foobar()',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_scope(app):
app.env.temp_data['autodoc:module'] = 'target'
actual = do_autodoc(app, 'attribute', 'Class.mdocattr')
@@ -952,7 +1029,7 @@ def test_class_attributes(app):
actual = do_autodoc(app, 'class', 'target.AttCls', options)
assert list(actual) == [
'',
- '.. py:class:: AttCls',
+ '.. py:class:: AttCls()',
' :module: target',
'',
'',
@@ -1072,7 +1149,7 @@ def test_slots(app):
' :module: target.slots',
'',
'',
- '.. py:class:: Foo',
+ '.. py:class:: Foo()',
' :module: target.slots',
'',
'',
@@ -1088,7 +1165,7 @@ def test_enum_class(app):
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
assert list(actual) == [
'',
- '.. py:class:: EnumCls',
+ '.. py:class:: EnumCls(value)',
' :module: target.enum',
'',
' this is enum class',
@@ -1205,7 +1282,7 @@ def test_abstractmethods(app):
'.. py:module:: target.abstractmethods',
'',
'',
- '.. py:class:: Base',
+ '.. py:class:: Base()',
' :module: target.abstractmethods',
'',
'',
@@ -1322,7 +1399,7 @@ def test_coroutine(app):
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
assert list(actual) == [
'',
- '.. py:class:: AsyncClass',
+ '.. py:class:: AsyncClass()',
' :module: target.coroutine',
'',
'',
@@ -1364,7 +1441,7 @@ def test_coroutine(app):
def test_partialmethod(app):
expected = [
'',
- '.. py:class:: Cell',
+ '.. py:class:: Cell()',
' :module: target.partialmethod',
'',
' An example for partialmethod.',
@@ -1394,7 +1471,7 @@ def test_partialmethod(app):
def test_partialmethod_undoc_members(app):
expected = [
'',
- '.. py:class:: Cell',
+ '.. py:class:: Cell()',
' :module: target.partialmethod',
'',
' An example for partialmethod.',
@@ -1581,7 +1658,7 @@ def test_singledispatchmethod(app):
'.. py:module:: target.singledispatchmethod',
'',
'',
- '.. py:class:: Foo',
+ '.. py:class:: Foo()',
' :module: target.singledispatchmethod',
'',
' docstring',
@@ -1626,7 +1703,7 @@ def test_cython(app):
'.. py:module:: target.cython',
'',
'',
- '.. py:class:: Class',
+ '.. py:class:: Class()',
' :module: target.cython',
'',
' Docstring.',
@@ -1657,7 +1734,7 @@ def test_final(app):
'.. py:module:: target.final',
'',
'',
- '.. py:class:: Class',
+ '.. py:class:: Class()',
' :module: target.final',
' :final:',
'',
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index 6821c6264..22558885b 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -9,6 +9,7 @@
"""
import platform
+import sys
import pytest
@@ -27,7 +28,7 @@ def test_autoclass_content_class(app):
'.. py:module:: target.autoclass_content',
'',
'',
- '.. py:class:: A',
+ '.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@@ -45,13 +46,13 @@ def test_autoclass_content_class(app):
' A class having __init__, no __new__',
'',
'',
- '.. py:class:: D',
+ '.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
- '.. py:class:: E',
+ '.. py:class:: E()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__',
@@ -87,7 +88,7 @@ def test_autoclass_content_init(app):
'.. py:module:: target.autoclass_content',
'',
'',
- '.. py:class:: A',
+ '.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@@ -105,13 +106,13 @@ def test_autoclass_content_init(app):
' __init__ docstring',
'',
'',
- '.. py:class:: D',
+ '.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
- '.. py:class:: E',
+ '.. py:class:: E()',
' :module: target.autoclass_content',
'',
' __new__ docstring',
@@ -147,7 +148,7 @@ def test_autoclass_content_both(app):
'.. py:module:: target.autoclass_content',
'',
'',
- '.. py:class:: A',
+ '.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@@ -167,13 +168,13 @@ def test_autoclass_content_both(app):
' __init__ docstring',
'',
'',
- '.. py:class:: D',
+ '.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
- '.. py:class:: E',
+ '.. py:class:: E()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__',
@@ -237,7 +238,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
- '.. py:class:: DocstringSig',
+ '.. py:class:: DocstringSig()',
' :module: target',
'',
'',
@@ -279,7 +280,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
- '.. py:class:: DocstringSig',
+ '.. py:class:: DocstringSig()',
' :module: target',
'',
'',
@@ -435,7 +436,7 @@ def test_mocked_module_imports(app, warning):
'.. py:module:: target.need_mocks',
'',
'',
- '.. py:class:: TestAutodoc',
+ '.. py:class:: TestAutodoc()',
' :module: target.need_mocks',
'',
' TestAutodoc docstring.',
@@ -493,6 +494,18 @@ def test_autodoc_typehints_signature(app):
' :module: target.typehints',
'',
'',
+ '.. py:class:: NewAnnotation(i: int)',
+ ' :module: target.typehints',
+ '',
+ '',
+ '.. py:class:: NewComment(i: int)',
+ ' :module: target.typehints',
+ '',
+ '',
+ '.. py:class:: SignatureFromMetaclass(a: int)',
+ ' :module: target.typehints',
+ '',
+ '',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
' :module: target.typehints',
@@ -547,6 +560,18 @@ def test_autodoc_typehints_none(app):
' :module: target.typehints',
'',
'',
+ '.. py:class:: NewAnnotation(i)',
+ ' :module: target.typehints',
+ '',
+ '',
+ '.. py:class:: NewComment(i)',
+ ' :module: target.typehints',
+ '',
+ '',
+ '.. py:class:: SignatureFromMetaclass(a)',
+ ' :module: target.typehints',
+ '',
+ '',
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
' :module: target.typehints',
'',
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index 281ba141e..a65826141 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -292,7 +292,7 @@ def test_autosummary_generate(app, status, warning):
assert len(doctree[3][0][0][2]) == 5
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
- assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar\n\n'
+ assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
diff --git a/tests/test_ext_napoleon_docstring.py b/tests/test_ext_napoleon_docstring.py
index 3027a4cb2..738fd6532 100644
--- a/tests/test_ext_napoleon_docstring.py
+++ b/tests/test_ext_napoleon_docstring.py
@@ -1399,11 +1399,32 @@ arg_ : type
"""
expected = """
+:ivar arg_: some description
+:vartype arg_: type
+"""
+
+ config = Config(napoleon_use_ivar=True)
+ app = mock.Mock()
+ actual = str(NumpyDocstring(docstring, config, app, "class"))
+
+ self.assertEqual(expected, actual)
+
+ def test_underscore_in_attribute_strip_signature_backslash(self):
+ docstring = """
+Attributes
+----------
+
+arg_ : type
+ some description
+"""
+
+ expected = """
:ivar arg\\_: some description
:vartype arg\\_: type
"""
config = Config(napoleon_use_ivar=True)
+ config.strip_signature_backslash = True
app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "class"))
diff --git a/tests/test_project.py b/tests/test_project.py
index a721d39fe..50b06f7b8 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -58,8 +58,8 @@ def test_project_path2doc(app):
assert project.path2doc('index.foo') is None # unknown extension
assert project.path2doc('index.foo.rst') == 'index.foo'
assert project.path2doc('index') is None
- assert project.path2doc('/path/to/index.rst') == PathComparer('/path/to/index')
- assert project.path2doc(app.srcdir / '/to/index.rst') == PathComparer('/to/index')
+ assert project.path2doc('path/to/index.rst') == 'path/to/index'
+ assert project.path2doc(app.srcdir / 'to/index.rst') == 'to/index'
@pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic')
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
index 4da61df47..f16feb698 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -29,12 +29,14 @@ def test_signature():
with pytest.raises(TypeError):
inspect.signature('')
- # builitin classes
- with pytest.raises(ValueError):
- inspect.signature(int)
-
- with pytest.raises(ValueError):
- inspect.signature(str)
+ # 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):
+ inspect.signature(list)
# normal function
def func(a, b, c=1, d=2, *e, **f):