diff options
-rw-r--r-- | CHANGES | 21 | ||||
-rw-r--r-- | doc/usage/configuration.rst | 2 | ||||
-rw-r--r-- | setup.py | 1 | ||||
-rw-r--r-- | sphinx/application.py | 11 | ||||
-rw-r--r-- | sphinx/builders/latex/__init__.py | 9 | ||||
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 215 | ||||
-rw-r--r-- | sphinx/ext/napoleon/docstring.py | 2 | ||||
-rw-r--r-- | sphinx/registry.py | 9 | ||||
-rw-r--r-- | sphinx/templates/latex/latex.tex_t | 8 | ||||
-rw-r--r-- | sphinx/util/inspect.py | 2 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/sort_by_all.py | 25 | ||||
-rw-r--r-- | tests/roots/test-ext-autodoc/target/typehints.py | 21 | ||||
-rw-r--r-- | tests/test_ext_autodoc.py | 133 | ||||
-rw-r--r-- | tests/test_ext_autodoc_configs.py | 49 | ||||
-rw-r--r-- | tests/test_ext_autosummary.py | 2 | ||||
-rw-r--r-- | tests/test_ext_napoleon_docstring.py | 21 | ||||
-rw-r--r-- | tests/test_project.py | 4 | ||||
-rw-r--r-- | tests/test_util_inspect.py | 14 |
18 files changed, 433 insertions, 116 deletions
@@ -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 @@ -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): |