From d7cab2ebfa768c7b04fbbf131c15462e3a3147ec Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Wed, 4 Mar 2020 00:53:48 +0900 Subject: [PATCH 1/9] test: Add testcase for special case of PythonDomain.find_obj() --- tests/roots/test-domain-py/module.rst | 8 ++++++++ tests/test_domain_py.py | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/roots/test-domain-py/module.rst b/tests/roots/test-domain-py/module.rst index c01032b26..dce3fa5ac 100644 --- a/tests/roots/test-domain-py/module.rst +++ b/tests/roots/test-domain-py/module.rst @@ -51,3 +51,11 @@ module .. py:attribute:: attr2 :type: :doc:`index` + +.. py:module:: exceptions + +.. py:exception:: Exception + +.. py:module:: object + +.. py:function:: sum() diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 218ded510..8d6d43b48 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -206,6 +206,18 @@ def test_domain_py_find_obj(app, status, warning): [('NestedParentA.NestedChildA.subchild_1', ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) + # special case: exceptions + assert (find_obj('exceptions', None, 'Exception', 'exc') == + [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) + assert (find_obj(None, None, 'Exception', 'exc') == + [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) + + # special case: object + assert (find_obj('object', None, 'sum', 'func') == + [('object.sum', ('module', 'object-sum', 'function'))]) + assert (find_obj(None, None, 'sum', 'func') == + [('object.sum', ('module', 'object-sum', 'function'))]) + def test_get_full_qualified_name(): env = Mock(domaindata={}) From 961b4d1545eb5174617d8c3251006cc19f420088 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 3 Feb 2018 13:44:17 +0900 Subject: [PATCH 2/9] Close #2815: autodoc: Support singledispatch functions --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 60 ++++++++++++++++++- sphinx/ext/autosummary/generate.py | 2 + sphinx/util/inspect.py | 11 ++++ .../test-ext-autodoc/target/singledispatch.py | 19 ++++++ tests/test_autodoc.py | 19 ++++++ 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/singledispatch.py diff --git a/CHANGES b/CHANGES index 15c4f376c..65a69f6b8 100644 --- a/CHANGES +++ b/CHANGES @@ -58,6 +58,7 @@ Features added * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list * #7165: autodoc: Support Annotated type (PEP-593) +* #2815: autodoc: Support singledispatch functions * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 7abd6c879..f6d581cfc 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -14,7 +14,8 @@ import importlib import re import warnings from types import ModuleType -from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Union +from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union +from unittest.mock import patch from docutils.statemachine import StringList @@ -1056,6 +1057,62 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ self.add_line(' :async:', sourcename) +class SingledispatchFunctionDocumenter(FunctionDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed functions. + """ + objtype = 'singledispatch_function' + directivetype = 'function' + member_order = 30 + + # before FunctionDocumenter + priority = FunctionDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + return (super().can_document_member(member, membername, isattr, parent) and + inspect.is_singledispatch_function(member)) + + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + + # intercept generated directive headers + # TODO: It is very hacky to use mock to intercept header generation + with patch.object(self, 'add_line') as add_line: + super().add_directive_header(sig) + + # output first line of header + self.add_line(*add_line.call_args_list[0][0]) + + # inserts signature of singledispatch'ed functions + for typ, func in self.object.registry.items(): + if typ is object: + pass # default implementation. skipped. + else: + self.annotate_to_first_argument(func, typ) + + documenter = FunctionDocumenter(self.directive, '') + documenter.object = func + self.add_line(' %s%s' % (self.format_name(), + documenter.format_signature()), + sourcename) + + # output remains of directive header + for call in add_line.call_args_list[1:]: + self.add_line(*call[0]) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: + """Annotate type hint to the first argument of function if needed.""" + sig = inspect.signature(func) + if len(sig.parameters) == 0: + return + + name = list(sig.parameters)[0] + if name not in func.__annotations__: + func.__annotations__[name] = typ + + class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. @@ -1612,6 +1669,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(FunctionDocumenter) + app.add_autodocumenter(SingledispatchFunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index ef623e2bd..8bea3b4a3 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -72,12 +72,14 @@ def setup_documenters(app: Any) -> None: FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, DataDeclarationDocumenter, + SingledispatchFunctionDocumenter, ) documenters = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter, DataDeclarationDocumenter, + SingledispatchFunctionDocumenter, ] # type: List[Type[Documenter]] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 281ef4493..58f922cae 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -224,6 +224,17 @@ def isattributedescriptor(obj: Any) -> bool: return False +def is_singledispatch_function(obj: Any) -> bool: + """Check if the object is singledispatch function.""" + if (inspect.isfunction(obj) and + hasattr(obj, 'dispatch') and + hasattr(obj, 'register') and + obj.dispatch.__module__ == 'functools'): + return True + else: + return False + + def isfunction(obj: Any) -> bool: """Check if the object is function.""" return inspect.isfunction(unwrap(obj)) diff --git a/tests/roots/test-ext-autodoc/target/singledispatch.py b/tests/roots/test-ext-autodoc/target/singledispatch.py new file mode 100644 index 000000000..c33d001b1 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/singledispatch.py @@ -0,0 +1,19 @@ +from functools import singledispatch + + +@singledispatch +def func(arg, kwarg=None): + """A function for general use.""" + pass + + +@func.register(int) +def _func_int(arg, kwarg=None): + """A function for int.""" + pass + + +@func.register(str) +def _func_str(arg, kwarg=None): + """A function for str.""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index b001de804..dcbdc6d3a 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1563,3 +1563,22 @@ def test_autodoc_for_egged_code(app): ' :module: sample', '' ] + + +@pytest.mark.usefixtures('setup_test') +def test_singledispatch(): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatch', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatch', + '', + '', + '.. py:function:: func(arg, kwarg=None)', + ' func(arg: int, kwarg=None)', + ' func(arg: str, kwarg=None)', + ' :module: target.singledispatch', + '', + ' A function for general use.', + ' ' + ] From 8f7cc26b208ae9e477a95c8daf5e690e49945ad5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 1 Mar 2020 22:15:10 +0900 Subject: [PATCH 3/9] autodoc: Support singledispatch methods --- CHANGES | 2 +- sphinx/ext/autodoc/__init__.py | 61 +++++++++++++++++++ sphinx/util/inspect.py | 9 +++ .../target/singledispatchmethod.py | 20 ++++++ tests/test_autodoc.py | 27 ++++++++ 5 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/roots/test-ext-autodoc/target/singledispatchmethod.py diff --git a/CHANGES b/CHANGES index 65a69f6b8..3263f9fb5 100644 --- a/CHANGES +++ b/CHANGES @@ -58,7 +58,7 @@ Features added * #6830: autodoc: consider a member private if docstring contains ``:meta private:`` in info-field-list * #7165: autodoc: Support Annotated type (PEP-593) -* #2815: autodoc: Support singledispatch functions +* #2815: autodoc: Support singledispatch functions and methods * #6558: glossary: emit a warning for duplicated glossary entry * #3106: domain: Register hyperlink target for index page automatically * #6558: std domain: emit a warning for duplicated generic objects diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index f6d581cfc..97995a410 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1457,6 +1457,66 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: pass +class SingledispatchMethodDocumenter(MethodDocumenter): + """ + Specialized Documenter subclass for singledispatch'ed methods. + """ + objtype = 'singledispatch_method' + directivetype = 'method' + member_order = 50 + + # before MethodDocumenter + priority = MethodDocumenter.priority + 1 + + @classmethod + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + ) -> bool: + if super().can_document_member(member, membername, isattr, parent) and parent.object: + meth = parent.object.__dict__.get(membername) + return inspect.is_singledispatch_method(meth) + else: + return False + + def add_directive_header(self, sig: str) -> None: + sourcename = self.get_sourcename() + + # intercept generated directive headers + # TODO: It is very hacky to use mock to intercept header generation + with patch.object(self, 'add_line') as add_line: + super().add_directive_header(sig) + + # output first line of header + self.add_line(*add_line.call_args_list[0][0]) + + # inserts signature of singledispatch'ed functions + meth = self.parent.__dict__.get(self.objpath[-1]) + for typ, func in meth.dispatcher.registry.items(): + if typ is object: + pass # default implementation. skipped. + else: + self.annotate_to_first_argument(func, typ) + + documenter = MethodDocumenter(self.directive, '') + documenter.object = func + self.add_line(' %s%s' % (self.format_name(), + documenter.format_signature()), + sourcename) + + # output remains of directive header + for call in add_line.call_args_list[1:]: + self.add_line(*call[0]) + + def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: + """Annotate type hint to the first argument of function if needed.""" + sig = inspect.signature(func, bound_method=True) + if len(sig.parameters) == 0: + return + + name = list(sig.parameters)[0] + if name not in func.__annotations__: + func.__annotations__[name] = typ + + class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for attributes. @@ -1672,6 +1732,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(SingledispatchFunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) + app.add_autodocumenter(SingledispatchMethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 58f922cae..d22df7656 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -235,6 +235,15 @@ def is_singledispatch_function(obj: Any) -> bool: return False +def is_singledispatch_method(obj: Any) -> bool: + """Check if the object is singledispatch method.""" + try: + from functools import singledispatchmethod # type: ignore + return isinstance(obj, singledispatchmethod) + except ImportError: # py35-37 + return False + + def isfunction(obj: Any) -> bool: """Check if the object is function.""" return inspect.isfunction(unwrap(obj)) diff --git a/tests/roots/test-ext-autodoc/target/singledispatchmethod.py b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py new file mode 100644 index 000000000..b5ccbb2f0 --- /dev/null +++ b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py @@ -0,0 +1,20 @@ +from functools import singledispatchmethod + + +class Foo: + """docstring""" + + @singledispatchmethod + def meth(self, arg, kwarg=None): + """A method for general use.""" + pass + + @meth.register(int) + def _meth_int(self, arg, kwarg=None): + """A method for int.""" + pass + + @meth.register(str) + def _meth_str(self, arg, kwarg=None): + """A method for str.""" + pass diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index dcbdc6d3a..0510fff86 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -1582,3 +1582,30 @@ def test_singledispatch(): ' A function for general use.', ' ' ] + + +@pytest.mark.skipif(sys.version_info < (3, 8), + reason='singledispatchmethod is available since python3.8') +@pytest.mark.usefixtures('setup_test') +def test_singledispatchmethod(): + options = {"members": None} + actual = do_autodoc(app, 'module', 'target.singledispatchmethod', options) + assert list(actual) == [ + '', + '.. py:module:: target.singledispatchmethod', + '', + '', + '.. py:class:: Foo', + ' :module: target.singledispatchmethod', + '', + ' docstring', + ' ', + ' ', + ' .. py:method:: Foo.meth(arg, kwarg=None)', + ' Foo.meth(arg: int, kwarg=None)', + ' Foo.meth(arg: str, kwarg=None)', + ' :module: target.singledispatchmethod', + ' ', + ' A method for general use.', + ' ' + ] From ea329866b0dabe67f40d0cf2ded22a5d5bd701f4 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 18:01:16 +0900 Subject: [PATCH 4/9] Update CHANGES for #6903 --- CHANGES | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 15c4f376c..e21623cc4 100644 --- a/CHANGES +++ b/CHANGES @@ -23,16 +23,13 @@ Incompatible changes * Due to the scoping changes for :rst:dir:`productionlist` some uses of :rst:role:`token` must be modified to include the scope which was previously ignored. -* #6903: js domain: Internal data structure has changed. Both objects and - modules have node_id for cross reference +* #6903: Internal data structure of Python, reST and standard domains have + changed. The node_id is added to the index of objects and modules. Now they + contains a pair of docname and node_id for cross reference. * #7210: js domain: Non intended behavior is removed such as ``parseInt_`` links to ``.. js:function:: parseInt`` -* #6903: rst domain: Internal data structure has changed. Now objects have - node_id for cross reference * #7229: rst domain: Non intended behavior is removed such as ``numref_`` links to ``.. rst:role:: numref`` -* #6903: py domain: Internal data structure has changed. Both objects and - modules have node_id for cross reference * #6903: py domain: Non intended behavior is removed such as ``say_hello_`` links to ``.. py:function:: say_hello()`` From 8b5686d87c2b1729cd6f84810400ca7bdf800ebc Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 18:49:14 +0900 Subject: [PATCH 5/9] Fix #7267: autodoc: errormsg for invalid directive options has wrong location --- CHANGES | 1 + sphinx/ext/autodoc/directive.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 15c4f376c..a9f109061 100644 --- a/CHANGES +++ b/CHANGES @@ -91,6 +91,7 @@ Bugs fixed * C++, suppress warnings for directly dependent typenames in cross references generated automatically in signatures. * #5637: autodoc: Incorrect handling of nested class names on show-inheritance +* #7267: autodoc: error message for invalid directive options has wrong location * #5637: inheritance_diagram: Incorrect handling of nested class names * #7139: ``code-block:: guess`` does not work diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py index b44bd75b3..9302a954a 100644 --- a/sphinx/ext/autodoc/directive.py +++ b/sphinx/ext/autodoc/directive.py @@ -137,7 +137,7 @@ class AutodocDirective(SphinxDirective): except (KeyError, ValueError, TypeError) as exc: # an option is either unknown or has a wrong type logger.error('An option to %s is either unknown or has an invalid value: %s' % - (self.name, exc), location=(source, lineno)) + (self.name, exc), location=(self.env.docname, lineno)) return [] # generate the output From dd455fe3d379054a704c97b32c14a992306680b5 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:05:26 +0900 Subject: [PATCH 6/9] test: Add testcases for decorators --- tests/test_domain_py.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 218ded510..27819af6b 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -585,6 +585,36 @@ def test_pyattribute(app): assert domain.objects['Class.attr'] == ('index', 'class-attr', 'attribute') +def test_pydecorator_signature(app): + text = ".. py:decorator:: deco" + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_addname, "@"], + [desc_name, "deco"])], + desc_content)])) + assert_node(doctree[1], addnodes.desc, desctype="function", + domain="py", objtype="function", noindex=False) + + assert 'deco' in domain.objects + assert domain.objects['deco'] == ('index', 'deco', 'function') + + +def test_pydecoratormethod_signature(app): + text = ".. py:decoratormethod:: deco" + domain = app.env.get_domain('py') + doctree = restructuredtext.parse(app, text) + assert_node(doctree, (addnodes.index, + [desc, ([desc_signature, ([desc_addname, "@"], + [desc_name, "deco"])], + desc_content)])) + assert_node(doctree[1], addnodes.desc, desctype="method", + domain="py", objtype="method", noindex=False) + + assert 'deco' in domain.objects + assert domain.objects['deco'] == ('index', 'deco', 'method') + + @pytest.mark.sphinx(freshenv=True) def test_module_index(app): text = (".. py:module:: docutils\n" From 3cadc825593718a150db95d20caa6a2218237735 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:12:34 +0900 Subject: [PATCH 7/9] py domain: refactor PyDecoratorFunction and PyDecoratorMethod --- sphinx/domains/python.py | 52 +++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index e667f91dc..ea8d6bb70 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -489,6 +489,23 @@ class PyFunction(PyObject): return _('%s() (built-in function)') % name +class PyDecoratorFunction(PyFunction): + """Description of a decorator.""" + + def run(self) -> List[Node]: + # a decorator function is a function after all + self.name = 'py:function' + return super().run() + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + ret = super().handle_signature(sig, signode) + signode.insert(0, addnodes.desc_addname('@', '@')) + return ret + + def needs_arglist(self) -> bool: + return False + + class PyVariable(PyObject): """Description of a variable.""" @@ -700,6 +717,22 @@ class PyStaticMethod(PyMethod): return super().run() +class PyDecoratorMethod(PyMethod): + """Description of a decoratormethod.""" + + def run(self) -> List[Node]: + self.name = 'py:method' + return super().run() + + def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + ret = super().handle_signature(sig, signode) + signode.insert(0, addnodes.desc_addname('@', '@')) + return ret + + def needs_arglist(self) -> bool: + return False + + class PyAttribute(PyObject): """Description of an attribute.""" @@ -750,25 +783,6 @@ class PyDecoratorMixin: return False -class PyDecoratorFunction(PyDecoratorMixin, PyModulelevel): - """ - Directive to mark functions meant to be used as decorators. - """ - def run(self) -> List[Node]: - # a decorator function is a function after all - self.name = 'py:function' - return super().run() - - -class PyDecoratorMethod(PyDecoratorMixin, PyClassmember): - """ - Directive to mark methods meant to be used as decorators. - """ - def run(self) -> List[Node]: - self.name = 'py:method' - return super().run() - - class PyModule(SphinxDirective): """ Directive to mark description of a new module. From d49bec1c6706bff5a0a4adb7446e0e9a33b39813 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 23:13:06 +0900 Subject: [PATCH 8/9] py domain: Deprecate PyDecoratorMixin --- CHANGES | 1 + doc/extdev/deprecated.rst | 5 +++++ sphinx/domains/python.py | 11 ++++++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 15c4f376c..ef26337ab 100644 --- a/CHANGES +++ b/CHANGES @@ -42,6 +42,7 @@ Deprecated * ``desc_signature['first']`` * ``sphinx.directives.DescDirective`` * ``sphinx.domains.std.StandardDomain.add_object()`` +* ``sphinx.domains.python.PyDecoratorMixin`` * ``sphinx.parsers.Parser.app`` * ``sphinx.testing.path.Path.text()`` * ``sphinx.testing.path.Path.bytes()`` diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index e98652ed2..cf914a7cd 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -41,6 +41,11 @@ The following is a list of deprecated interfaces. - 5.0 - ``sphinx.domains.std.StandardDomain.note_object()`` + * - ``sphinx.domains.python.PyDecoratorMixin`` + - 3.0 + - 5.0 + - N/A + * - ``sphinx.parsers.Parser.app`` - 3.0 - 5.0 diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index ea8d6bb70..eed7eaaa6 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -25,7 +25,7 @@ from sphinx import addnodes from sphinx.addnodes import pending_xref, desc_signature from sphinx.application import Sphinx from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx40Warning +from sphinx.deprecation import RemovedInSphinx40Warning, RemovedInSphinx50Warning from sphinx.directives import ObjectDescription from sphinx.domains import Domain, ObjType, Index, IndexEntry from sphinx.environment import BuildEnvironment @@ -775,6 +775,15 @@ class PyDecoratorMixin: Mixin for decorator directives. """ def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]: + for cls in self.__class__.__mro__: + if cls.__name__ != 'DirectiveAdapter': + warnings.warn('PyDecoratorMixin is deprecated. ' + 'Please check the implementation of %s' % cls, + RemovedInSphinx50Warning) + break + else: + warnings.warn('PyDecoratorMixin is deprecated', RemovedInSphinx50Warning) + ret = super().handle_signature(sig, signode) # type: ignore signode.insert(0, addnodes.desc_addname('@', '@')) return ret From 8e1934b6a131c239fb27f685714864c5d14d3625 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 7 Mar 2020 17:46:26 +0900 Subject: [PATCH 9/9] Close #7246: Drop special cross reference helper for exceptions, functions and methods --- CHANGES | 2 ++ sphinx/domains/python.py | 8 -------- tests/test_domain_py.py | 12 ------------ 3 files changed, 2 insertions(+), 20 deletions(-) diff --git a/CHANGES b/CHANGES index c5f26b37c..268b6d835 100644 --- a/CHANGES +++ b/CHANGES @@ -32,6 +32,8 @@ Incompatible changes to ``.. rst:role:: numref`` * #6903: py domain: Non intended behavior is removed such as ``say_hello_`` links to ``.. py:function:: say_hello()`` +* #7246: py domain: Drop special cross reference helper for exceptions, + functions and methods Deprecated ---------- diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index e667f91dc..4fd7dab08 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -1110,14 +1110,6 @@ class PythonDomain(Domain): elif modname and classname and \ modname + '.' + classname + '.' + name in self.objects: newname = modname + '.' + classname + '.' + name - # special case: builtin exceptions have module "exceptions" set - elif type == 'exc' and '.' not in name and \ - 'exceptions.' + name in self.objects: - newname = 'exceptions.' + name - # special case: object methods - elif type in ('func', 'meth') and '.' not in name and \ - 'object.' + name in self.objects: - newname = 'object.' + name if newname is not None: matches.append((newname, self.objects[newname])) return matches diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index 8d6d43b48..218ded510 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -206,18 +206,6 @@ def test_domain_py_find_obj(app, status, warning): [('NestedParentA.NestedChildA.subchild_1', ('roles', 'nestedparenta-nestedchilda-subchild-1', 'method'))]) - # special case: exceptions - assert (find_obj('exceptions', None, 'Exception', 'exc') == - [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) - assert (find_obj(None, None, 'Exception', 'exc') == - [('exceptions.Exception', ('module', 'exceptions-exception', 'exception'))]) - - # special case: object - assert (find_obj('object', None, 'sum', 'func') == - [('object.sum', ('module', 'object-sum', 'function'))]) - assert (find_obj(None, None, 'sum', 'func') == - [('object.sum', ('module', 'object-sum', 'function'))]) - def test_get_full_qualified_name(): env = Mock(domaindata={})