From b0875d63fc0e57db6b976127015e121059edd698 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Sat, 25 Feb 2017 20:07:16 -0800 Subject: [PATCH 01/12] Fix Python domain nesting Moved #3465 here, to address this in `stable` instead. This fixes a problem with the Python domain object nesting. Because only one object name was stored in `ref_context`, and reset to `None` in `after_content`, nesting broke if you put anything after a nested class: ```rst .. py:class:: Parent .. py:method:: foo() This wouldn't resolve: :py:meth:`bar` .. py:class:: Child In the `after_content` method, the object is reset to `None`, so anything after this in the same nesting is considered to be top level instead. .. py:method:: bar() This is top level, as the domain thinks the surrounding object is `None` ``` This depends on #3519 and can be rebased after that is merged into stable Fixes #3065 Refs #3067 --- sphinx/domains/python.py | 70 ++++++++++++++++++++++++++++++---------- tests/test_domain_py.py | 7 ++-- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index ba38f4702..34240bd89 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -138,6 +138,9 @@ class PyTypedField(PyXrefMixin, TypedField): class PyObject(ObjectDescription): """ Description of a general Python object. + + :cvar allow_nesting: Class is an object that allows for nested namespaces + :vartype allow_nesting: bool """ option_spec = { 'noindex': directives.flag, @@ -164,6 +167,8 @@ class PyObject(ObjectDescription): names=('rtype',), bodyrolename='obj'), ] + allow_nesting = False + def get_signature_prefix(self, sig): """May return a prefix to put before the object name in the signature. @@ -285,12 +290,54 @@ class PyObject(ObjectDescription): fullname, '', None)) def before_content(self): - # needed for automatic qualification of members (reset in subclasses) - self.clsname_set = False + # type: () -> None + """Handle object nesting before content + + If this class is a nestable object, such as a class object, build up a + representation of the nesting heirarchy so that de-nesting multiple + levels works correctly. + + If this class isn't a nestable object, just set the current class name + using the object prefix, if any. This class name will be removed with + :py:meth:`after_content`, and is not added to the list of nested + classes. + """ + prefix = None + if self.names: + (cls_name, cls_name_prefix) = self.names.pop() + prefix = cls_name_prefix.strip('.') if cls_name_prefix else None + if self.allow_nesting: + prefix = cls_name + if prefix: + self.env.ref_context['py:class'] = prefix + if self.allow_nesting: + try: + self.env.ref_context['py:classes'].append(prefix) + except (AttributeError, KeyError): + self.env.ref_context['py:classes'] = [prefix] def after_content(self): - if self.clsname_set: - self.env.ref_context.pop('py:class', None) + # type: () -> None + """Handle object de-nesting after content + + If this class is a nestable object, removing the last nested class prefix + ends further nesting in the object. + + If this class is not a nestable object, the list of classes should not + be altered as we didn't affect the nesting levels in + :py:meth:`before_content`. + """ + if self.allow_nesting: + try: + self.env.ref_context['py:classes'].pop() + except (KeyError, IndexError): + self.env.ref_context['py:classes'] = [] + try: + cls_name = self.env.ref_context.get('py:classes', [])[-1] + except IndexError: + cls_name = None + finally: + self.env.ref_context['py:class'] = cls_name class PyModulelevel(PyObject): @@ -319,6 +366,8 @@ class PyClasslike(PyObject): Description of a class-like object (classes, interfaces, exceptions). """ + allow_nesting = True + def get_signature_prefix(self, sig): return self.objtype + ' ' @@ -332,12 +381,6 @@ class PyClasslike(PyObject): else: return '' - def before_content(self): - PyObject.before_content(self) - if self.names: - self.env.ref_context['py:class'] = self.names[0][0] - self.clsname_set = True - class PyClassmember(PyObject): """ @@ -410,13 +453,6 @@ class PyClassmember(PyObject): else: return '' - def before_content(self): - PyObject.before_content(self) - lastname = self.names and self.names[-1][1] - if lastname and not self.env.ref_context.get('py:class'): - self.env.ref_context['py:class'] = lastname.strip('.') - self.clsname_set = True - class PyDecoratorMixin(object): """ diff --git a/tests/test_domain_py.py b/tests/test_domain_py.py index f290bfff3..1f497d69d 100644 --- a/tests/test_domain_py.py +++ b/tests/test_domain_py.py @@ -82,8 +82,8 @@ def test_domain_py_xrefs(app, status, warning): u'subchild_2', u'meth') assert_refnode(refnodes[8], None, u'NestedParentA.NestedChildA', u'NestedParentA.child_1', u'meth') - assert_refnode(refnodes[9], None, None, u'NestedChildA.subchild_1', - u'meth') + assert_refnode(refnodes[9], None, u'NestedParentA', + u'NestedChildA.subchild_1', u'meth') assert_refnode(refnodes[10], None, u'NestedParentB', u'child_1', u'meth') assert_refnode(refnodes[11], None, u'NestedParentB', u'NestedParentB', u'class') @@ -125,6 +125,7 @@ def test_domain_py_objects(app, status, warning): assert objects['module_a.submodule.ModTopLevel'] == ('module', 'class') assert objects['module_a.submodule.ModTopLevel.mod_child_1'] == ('module', 'method') assert objects['module_a.submodule.ModTopLevel.mod_child_2'] == ('module', 'method') + assert 'ModTopLevel.ModNoModule' not in objects assert objects['ModNoModule'] == ('module', 'class') assert objects['module_b.submodule.ModTopLevel'] == ('module', 'class') @@ -136,7 +137,7 @@ def test_domain_py_objects(app, status, warning): assert objects['NestedParentA.NestedChildA'] == ('roles', 'class') assert objects['NestedParentA.NestedChildA.subchild_1'] == ('roles', 'method') assert objects['NestedParentA.NestedChildA.subchild_2'] == ('roles', 'method') - assert objects['child_2'] == ('roles', 'method') + assert objects['NestedParentA.child_2'] == ('roles', 'method') assert objects['NestedParentB'] == ('roles', 'class') assert objects['NestedParentB.child_1'] == ('roles', 'method') From 6e0467ca776486b6c7088225de20256953f40a44 Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Tue, 14 Mar 2017 20:30:58 +0300 Subject: [PATCH 02/12] test_markup: Make verify functions test both HTML and LaTeX outputs Previously it would return after performing the HTML check, without running the LaTeX one. --- tests/test_markup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_markup.py b/tests/test_markup.py index 1d5407a27..7609e5430 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -97,9 +97,9 @@ def verify_re_latex(app, parse): def verify_re(verify_re_html, verify_re_latex): def verify_re_(rst, html_expected, latex_expected): if html_expected: - return verify_re_html(rst, html_expected) + verify_re_html(rst, html_expected) if latex_expected: - return verify_re_latex(rst, latex_expected) + verify_re_latex(rst, latex_expected) return verify_re_ @@ -107,9 +107,9 @@ def verify_re(verify_re_html, verify_re_latex): def verify(verify_re_html, verify_re_latex): def verify_(rst, html_expected, latex_expected): if html_expected: - return verify_re_html(rst, re.escape(html_expected) + '$') + verify_re_html(rst, re.escape(html_expected) + '$') if latex_expected: - return verify_re_latex(rst, re.escape(latex_expected) + '$') + verify_re_latex(rst, re.escape(latex_expected) + '$') return verify_ From b7efbfe615589a7f0a8cbe61248463a59a563112 Mon Sep 17 00:00:00 2001 From: Rob Ruana Date: Wed, 23 Nov 2016 10:45:39 -0800 Subject: [PATCH 03/12] Fix #3174: [Napoleon] Defers autodoc-skip-member to other extensions if Napoleon doesn't care if the member is skipped --- doc/ext/autodoc.rst | 5 +++++ sphinx/ext/napoleon/__init__.py | 2 +- tests/test_ext_napoleon.py | 10 +++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 63c959869..50004e575 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -446,6 +446,11 @@ member should be included in the documentation by using the following event: documentation. The member is excluded if a handler returns ``True``. It is included if the handler returns ``False``. + If more than one enabled extension handles the ``autodoc-skip-member`` + event, autodoc will use the first non-``None`` value returned by a handler. + Handlers should return ``None`` to fall back to the skipping behavior of + autodoc and other enabled extensions. + :param app: the Sphinx application object :param what: the type of the object which the docstring belongs to (one of ``"module"``, ``"class"``, ``"exception"``, ``"function"``, ``"method"``, diff --git a/sphinx/ext/napoleon/__init__.py b/sphinx/ext/napoleon/__init__.py index 651355c57..b3b9fc1b3 100644 --- a/sphinx/ext/napoleon/__init__.py +++ b/sphinx/ext/napoleon/__init__.py @@ -453,4 +453,4 @@ def _skip_member(app, what, name, obj, skip, options): (is_private and inc_private) or (is_init and inc_init)): return False - return skip + return None diff --git a/tests/test_ext_napoleon.py b/tests/test_ext_napoleon.py index 21d095a79..5f68ba7c0 100644 --- a/tests/test_ext_napoleon.py +++ b/tests/test_ext_napoleon.py @@ -123,19 +123,19 @@ class SetupTest(TestCase): class SkipMemberTest(TestCase): - def assertSkip(self, what, member, obj, expect_skip, config_name): - skip = 'default skip' + def assertSkip(self, what, member, obj, expect_default_skip, config_name): + skip = True app = mock.Mock() app.config = Config() setattr(app.config, config_name, True) - if expect_skip: - self.assertEqual(skip, _skip_member(app, what, member, obj, skip, + if expect_default_skip: + self.assertEqual(None, _skip_member(app, what, member, obj, skip, mock.Mock())) else: self.assertFalse(_skip_member(app, what, member, obj, skip, mock.Mock())) setattr(app.config, config_name, False) - self.assertEqual(skip, _skip_member(app, what, member, obj, skip, + self.assertEqual(None, _skip_member(app, what, member, obj, skip, mock.Mock())) def test_namedtuple(self): From 1717a95a7728b0f4efdd5cda0bd3b38c9b1a37bf Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Wed, 15 Mar 2017 11:29:50 +0300 Subject: [PATCH 04/12] Fix a test failure which was uncovered by the previous change In PR #3527 the LaTeX output for quotes has changed, but the test was not updated. --- tests/test_markup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_markup.py b/tests/test_markup.py index 7609e5430..11776cf44 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -179,7 +179,7 @@ def get_verifier(verify, verify_re): 'verify', '"John"', '

“John”

', - "``John''", + r'\sphinxquotedblleft{}John\sphinxquotedblright{}', ), ( # ... but not in literal text From f314245aa5edcbf12e97fe1c9266acbca869169d Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 14 Mar 2017 18:56:18 +0100 Subject: [PATCH 05/12] Allow running Sphinx without ssl --- sphinx/util/requests.py | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/sphinx/util/requests.py b/sphinx/util/requests.py index 674e00900..5c3cdb419 100644 --- a/sphinx/util/requests.py +++ b/sphinx/util/requests.py @@ -36,30 +36,34 @@ except ImportError: # for requests < 2.4.0 InsecureRequestWarning = None -# try to load requests[security] +# try to load requests[security] (but only if SSL is available) try: - pkg_resources.require(['requests[security]']) -except (pkg_resources.DistributionNotFound, - pkg_resources.VersionConflict): import ssl - if not getattr(ssl, 'HAS_SNI', False): - # don't complain on each url processed about the SSL issue - requests.packages.urllib3.disable_warnings( - requests.packages.urllib3.exceptions.InsecurePlatformWarning) +except ImportError: + pass +else: + try: + pkg_resources.require(['requests[security]']) + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + if not getattr(ssl, 'HAS_SNI', False): + # don't complain on each url processed about the SSL issue + requests.packages.urllib3.disable_warnings( + requests.packages.urllib3.exceptions.InsecurePlatformWarning) + warnings.warn( + 'Some links may return broken results due to being unable to ' + 'check the Server Name Indication (SNI) in the returned SSL cert ' + 'against the hostname in the url requested. Recommended to ' + 'install "requests[security]" as a dependency or upgrade to ' + 'a python version with SNI support (Python 3 and Python 2.7.9+).' + ) + except pkg_resources.UnknownExtra: warnings.warn( 'Some links may return broken results due to being unable to ' 'check the Server Name Indication (SNI) in the returned SSL cert ' 'against the hostname in the url requested. Recommended to ' - 'install "requests[security]" as a dependency or upgrade to ' - 'a python version with SNI support (Python 3 and Python 2.7.9+).' + 'install requests-2.4.1+.' ) -except pkg_resources.UnknownExtra: - warnings.warn( - 'Some links may return broken results due to being unable to ' - 'check the Server Name Indication (SNI) in the returned SSL cert ' - 'against the hostname in the url requested. Recommended to ' - 'install requests-2.4.1+.' - ) useragent_header = [('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0')] From 279588931023c14cf6b198ab5bb7432725f216a0 Mon Sep 17 00:00:00 2001 From: Anthony Johnson Date: Wed, 15 Mar 2017 23:07:54 -0700 Subject: [PATCH 06/12] Review feedback, cleanup, and docs --- sphinx/domains/python.py | 49 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 34240bd89..c90836fa0 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -293,28 +293,32 @@ class PyObject(ObjectDescription): # type: () -> None """Handle object nesting before content - If this class is a nestable object, such as a class object, build up a - representation of the nesting heirarchy so that de-nesting multiple - levels works correctly. + :py:class:`PyObject` represents Python language constructs. For + constructs that are nestable, such as a Python classes, this method will + build up a stack of the nesting heirarchy so that it can be later + de-nested correctly, in :py:meth:`after_content`. - If this class isn't a nestable object, just set the current class name - using the object prefix, if any. This class name will be removed with - :py:meth:`after_content`, and is not added to the list of nested - classes. + For constructs that aren't nestable, the stack is bypassed, and instead + only the most recent object is tracked. This object prefix name will be + removed with :py:meth:`after_content`. """ - prefix = None if self.names: - (cls_name, cls_name_prefix) = self.names.pop() - prefix = cls_name_prefix.strip('.') if cls_name_prefix else None + # fullname and name_prefix come from the `handle_signature` method. + # fullname represents the full object name that is constructed using + # object nesting and explicit prefixes. `name_prefix` is the + # explicit prefix given in a signature + (fullname, name_prefix) = self.names[-1] if self.allow_nesting: - prefix = cls_name + prefix = fullname + elif name_prefix: + prefix = name_prefix.strip('.') + else: + prefix = None if prefix: self.env.ref_context['py:class'] = prefix if self.allow_nesting: - try: - self.env.ref_context['py:classes'].append(prefix) - except (AttributeError, KeyError): - self.env.ref_context['py:classes'] = [prefix] + classes = self.env.ref_context.setdefault('py:classes', []) + classes.append(prefix) def after_content(self): # type: () -> None @@ -327,17 +331,14 @@ class PyObject(ObjectDescription): be altered as we didn't affect the nesting levels in :py:meth:`before_content`. """ + classes = self.env.ref_context.setdefault('py:classes', []) if self.allow_nesting: try: - self.env.ref_context['py:classes'].pop() - except (KeyError, IndexError): - self.env.ref_context['py:classes'] = [] - try: - cls_name = self.env.ref_context.get('py:classes', [])[-1] - except IndexError: - cls_name = None - finally: - self.env.ref_context['py:class'] = cls_name + classes.pop() + except IndexError: + pass + self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0 + else None) class PyModulelevel(PyObject): From 39bce2f90a078273d328ffbf6fbef48e576d1bd1 Mon Sep 17 00:00:00 2001 From: jfbu Date: Thu, 16 Mar 2017 23:01:12 +0100 Subject: [PATCH 07/12] Remove documentation of ``\sphinxquotedblleft`` from next minor release Indeed, the macros will perhaps be removed already at 1.6, (PR #3562), hence it is better not to document them at 1.5.4. --- doc/latex.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/doc/latex.rst b/doc/latex.rst index d42878e85..ebc14765e 100644 --- a/doc/latex.rst +++ b/doc/latex.rst @@ -438,14 +438,6 @@ Let us now list some macros from the package file the new macros are wrappers of the formerly hard-coded ``\texttt``, ``\emph``, ... The default definitions can be found in :file:`sphinx.sty`. -- macros for directional double quotes: pairs of straight double quote ``"`` - in reST source are converted into LaTeX mark-up - ``\sphinxquotedblleft{}`` and ``\sphinxquotedblright{}`` which default to - `````\ ````` and ``''`` (i.e. the TeX mark-up for directional double - quotes via font ligaturing mechanism.) - - .. versionadded:: 1.5.4 - Formerly, produced TeX was directly with `````\ ````` and ``''``. - paragraph level environments: for each admonition type ````, the used environment is named ``sphinx``. They may be ``\renewenvironment`` 'd individually, and must then be defined with one argument (it is the heading From a9243276cc888976042516bea5dc8be86af71ba7 Mon Sep 17 00:00:00 2001 From: Tormod Landet Date: Thu, 16 Mar 2017 09:35:11 +0100 Subject: [PATCH 08/12] Mention style-checks and type-checks in devguide --- CONTRIBUTING.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d64f27702..30dee2774 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -127,6 +127,11 @@ These are the basic steps needed to start developing on Sphinx. cd doc make clean html latexpdf + * Run code style checks and type checks (type checks require mypy):: + + make style-check + make type-check + * Run the unit tests under different Python environments using :program:`tox`:: From 2b929dc27eec7724d8a19b818fea81cca5b6c9ee Mon Sep 17 00:00:00 2001 From: jfbu Date: Sat, 18 Mar 2017 09:25:48 +0100 Subject: [PATCH 09/12] Fix syntax coloring in CONTRIBUTING.rst --- CONTRIBUTING.rst | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 30dee2774..3ea46f674 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,3 +1,5 @@ +.. highlight:: console + Sphinx Developer's Guide ======================== @@ -127,10 +129,11 @@ These are the basic steps needed to start developing on Sphinx. cd doc make clean html latexpdf - * Run code style checks and type checks (type checks require mypy):: + * Run code style checks and type checks (type checks require ``mypy`` and are + currently done for commits to ``master`` only):: - make style-check - make type-check + make style-check + make type-check * Run the unit tests under different Python environments using :program:`tox`:: @@ -279,9 +282,7 @@ Debugging Tips `modified snowballcode generator `_. Generated `JSX `_ files are in `this repository `_. - You can get the resulting JavaScript files using the following command: - - .. code-block:: bash + You can get the resulting JavaScript files using the following command:: $ npm install $ node_modules/.bin/grunt build # -> dest/*.global.js From a0e1eb654410ceb42194edaca27ef3623d6acb22 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 18 Mar 2017 17:38:50 +0900 Subject: [PATCH 10/12] Update CHANGES for PR #3520 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index 57b2f364d..4b1a31979 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugs fixed rendered as code * #2665, #2607: Link names in C++ docfields, and make it possible for other domains. * #3542: C++, fix parsing error of non-type template argument with template. +* #3065, #3520: python domain fails to recognize nested class Testing -------- From ada9aa6b9432517bdf042832eba1bf6b4d66ad32 Mon Sep 17 00:00:00 2001 From: jfbu Date: Sat, 18 Mar 2017 09:45:19 +0100 Subject: [PATCH 11/12] Markup in CONTRIBUTING.rst --- CONTRIBUTING.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3ea46f674..e3a68b58e 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -277,7 +277,7 @@ Debugging Tips * Set the debugging options in the `Docutils configuration file `_. -* JavaScript stemming algorithms in `sphinx/search/*.py` (except `en.py`) are +* JavaScript stemming algorithms in ``sphinx/search/*.py`` (except ``en.py``) are generated by this `modified snowballcode generator `_. Generated `JSX `_ files are From 2dae11aa6800af78574d27d0bcc1f121b483c00a Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sat, 18 Mar 2017 18:17:59 +0900 Subject: [PATCH 12/12] Fix #3552: linkcheck raises UnboundLocalError --- CHANGES | 1 + sphinx/builders/linkcheck.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4b1a31979..328298ee5 100644 --- a/CHANGES +++ b/CHANGES @@ -67,6 +67,7 @@ Bugs fixed * #3450:   is appeared in EPUB docs * #3418: Search button is misaligned in nature and pyramid theme * #3421: Could not translate a caption of tables +* #3552: linkcheck raises UnboundLocalError Release 1.5.2 (released Jan 22, 2017) ===================================== diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 563f658b6..74ce76b64 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -167,7 +167,9 @@ class CheckExternalLinksBuilder(Builder): # history contains any redirects, get last if response.history: code = response.history[-1].status_code - return 'redirected', new_url, code + return 'redirected', new_url, code + else: + return 'redirected', new_url, 0 def check(): # check for various conditions without bothering the network