summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml6
-rw-r--r--CHANGES47
-rw-r--r--EXAMPLES2
-rw-r--r--README.rst4
-rw-r--r--doc/extdev/deprecated.rst11
-rw-r--r--doc/usage/configuration.rst18
-rw-r--r--doc/usage/extensions/autodoc.rst9
-rw-r--r--doc/usage/markdown.rst32
-rw-r--r--doc/usage/restructuredtext/domains.rst3
-rw-r--r--package-lock.json6
-rw-r--r--setup.py3
-rw-r--r--sphinx/__init__.py6
-rw-r--r--sphinx/application.py8
-rw-r--r--sphinx/builders/__init__.py7
-rw-r--r--sphinx/builders/linkcheck.py74
-rw-r--r--sphinx/config.py3
-rw-r--r--sphinx/domains/__init__.py16
-rw-r--r--sphinx/domains/c.py7
-rw-r--r--sphinx/domains/citation.py4
-rw-r--r--sphinx/domains/cpp.py40
-rw-r--r--sphinx/domains/javascript.py4
-rw-r--r--sphinx/domains/math.py4
-rw-r--r--sphinx/domains/python.py6
-rw-r--r--sphinx/domains/rst.py4
-rw-r--r--sphinx/domains/std.py28
-rw-r--r--sphinx/environment/__init__.py12
-rw-r--r--sphinx/environment/adapters/toctree.py6
-rw-r--r--sphinx/environment/collectors/__init__.py4
-rw-r--r--sphinx/ext/autodoc/__init__.py120
-rw-r--r--sphinx/ext/autodoc/directive.py2
-rw-r--r--sphinx/ext/autosummary/__init__.py6
-rw-r--r--sphinx/ext/autosummary/generate.py28
-rw-r--r--sphinx/jinja2glue.py13
-rw-r--r--sphinx/project.py4
-rw-r--r--sphinx/pycode/__init__.py14
-rw-r--r--sphinx/pycode/parser.py20
-rw-r--r--sphinx/search/__init__.py4
-rw-r--r--sphinx/themes/basic/static/basic.css_t2
-rw-r--r--sphinx/themes/basic/static/searchtools.js8
-rw-r--r--sphinx/transforms/__init__.py6
-rw-r--r--sphinx/transforms/i18n.py4
-rw-r--r--sphinx/transforms/post_transforms/__init__.py16
-rw-r--r--sphinx/util/__init__.py6
-rw-r--r--sphinx/util/docfields.py2
-rw-r--r--sphinx/util/docstrings.py23
-rw-r--r--sphinx/util/docutils.py8
-rw-r--r--sphinx/util/i18n.py5
-rw-r--r--sphinx/util/inspect.py87
-rw-r--r--sphinx/util/logging.py4
-rw-r--r--sphinx/util/nodes.py21
-rw-r--r--sphinx/util/parallel.py16
-rw-r--r--sphinx/util/rst.py10
-rw-r--r--sphinx/util/texescape.py2
-rw-r--r--sphinx/util/typing.py2
-rw-r--r--tests/roots/test-autosummary/dummy_module.py7
-rw-r--r--tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py (renamed from tests/roots/test-ext-autodoc/target/annotations.py)5
-rw-r--r--tests/roots/test-ext-autodoc/target/metadata.py2
-rw-r--r--tests/roots/test-ext-autodoc/target/module.py14
-rw-r--r--tests/roots/test-ext-autodoc/target/singledispatch.py1
-rw-r--r--tests/roots/test-ext-autodoc/target/singledispatchmethod.py1
-rw-r--r--tests/roots/test-nitpicky-warnings/conf.py1
-rw-r--r--tests/roots/test-nitpicky-warnings/index.rst7
-rw-r--r--tests/test_config.py74
-rw-r--r--tests/test_domain_std.py17
-rw-r--r--tests/test_ext_autodoc.py31
-rw-r--r--tests/test_ext_autodoc_autoclass.py47
-rw-r--r--tests/test_ext_autodoc_autofunction.py1
-rw-r--r--tests/test_ext_autodoc_automodule.py89
-rw-r--r--tests/test_ext_autodoc_configs.py57
-rw-r--r--tests/test_ext_autosummary.py2
-rw-r--r--tests/test_util_docstrings.py45
-rw-r--r--tests/test_util_inspect.py26
72 files changed, 926 insertions, 308 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f32468576..62a3d1139 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -23,9 +23,9 @@ jobs:
python: 3.9
docutils: du17
coverage: "--cov ./ --cov-append --cov-config setup.cfg"
- # - name: py310-dev
- # python: 3.10-dev
- # docutils: du16
+ - name: py310-dev
+ python: 3.10-dev
+ docutils: du17
env:
PYTEST_ADDOPTS: ${{ matrix.coverage }}
diff --git a/CHANGES b/CHANGES
index 8cb39f267..026215a95 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,50 @@
+Release 4.1.0 (in development)
+==============================
+
+Dependencies
+------------
+
+* Support jinja2-3.0
+
+Incompatible changes
+--------------------
+
+Deprecated
+----------
+
+* ``sphinx.util.docstrings.extract_metadata()``
+
+Features added
+--------------
+
+* #8107: autodoc: Add ``class-doc-from`` option to :rst:dir:`autoclass`
+ directive to control the content of the specific class like
+ :confval:`autoclass_content`
+* #8588: autodoc: :confval:`autodoc_type_aliases` now supports dotted name. It
+ allows you to define an alias for a class with module name like
+ ``foo.bar.BazClass``
+* #9175: autodoc: Special member is not documented in the module
+* #3257: autosummary: Support instance attributes for classes
+* #9129: html search: Show search summaries when html_copy_source = False
+* #9120: html theme: Eliminate prompt characters of code-block from copyable
+ text
+* #9176: i18n: Emit a debug message if message catalog file not found under
+ :confval:`locale_dirs`
+* #9097: Optimize the paralell build
+* #9131: Add :confval:`nitpick_ignore_regex` to ignore nitpicky warnings using
+ regular expressions
+
+
+Bugs fixed
+----------
+
+* #8872: autodoc: stacked singledispatches are wrongly rendered
+* #8597: autodoc: a docsting having metadata only should be treated as
+ undocumented
+
+Testing
+--------
+
Release 4.0.2 (in development)
==============================
diff --git a/EXAMPLES b/EXAMPLES
index 040637e96..598f41b4f 100644
--- a/EXAMPLES
+++ b/EXAMPLES
@@ -12,7 +12,6 @@ interesting examples.
Documentation using the alabaster theme
---------------------------------------
-* `AIOHTTP <https://docs.aiohttp.org/>`__
* `Alabaster <https://alabaster.readthedocs.io/>`__
* `Blinker <https://pythonhosted.org/blinker/>`__
* `Calibre <https://manual.calibre-ebook.com/>`__
@@ -311,6 +310,7 @@ Documentation using sphinx_bootstrap_theme
Documentation using a custom theme or integrated in a website
-------------------------------------------------------------
+* `AIOHTTP <https://docs.aiohttp.org/>`__
* `Apache Cassandra <https://cassandra.apache.org/doc/>`__
* `Astropy <http://docs.astropy.org/>`__
* `Bokeh <https://bokeh.pydata.org/>`__
diff --git a/README.rst b/README.rst
index 13bbab99d..c60e58780 100644
--- a/README.rst
+++ b/README.rst
@@ -10,10 +10,6 @@
:target: http://www.sphinx-doc.org/
:alt: Documentation Status
-.. image:: https://travis-ci.org/sphinx-doc/sphinx.svg?branch=master
- :target: https://travis-ci.org/sphinx-doc/sphinx
- :alt: Build Status (Travis CI)
-
.. image:: https://ci.appveyor.com/api/projects/status/github/sphinx-doc/sphinx?branch=master&svg=true
:target: https://ci.appveyor.com/project/sphinxdoc/sphinx
:alt: Build Status (AppVeyor)
diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 9e17b9fb4..952258a2b 100644
--- a/doc/extdev/deprecated.rst
+++ b/doc/extdev/deprecated.rst
@@ -22,6 +22,11 @@ The following is a list of deprecated interfaces.
- (will be) Removed
- Alternatives
+ * - ``sphinx.util.docstrings.extract_metadata()``
+ - 4.1
+ - 6.0
+ - ``sphinx.util.docstrings.separate_metadata()``
+
* - ``favicon`` variable in HTML templates
- 4.0
- TBD
@@ -35,12 +40,12 @@ The following is a list of deprecated interfaces.
* - ``sphinx.directives.patches.ListTable``
- 4.0
- 6.0
- - ``docutils.parsers.rst.diretives.tables.ListSVTable``
+ - ``docutils.parsers.rst.directives.tables.ListSVTable``
* - ``sphinx.directives.patches.RSTTable``
- 4.0
- 6.0
- - ``docutils.parsers.rst.diretives.tables.RSTTable``
+ - ``docutils.parsers.rst.directives.tables.RSTTable``
* - ``sphinx.ext.autodoc.directive.DocumenterBridge.filename_set``
- 4.0
@@ -80,7 +85,7 @@ The following is a list of deprecated interfaces.
* - ``sphinx.util.smartypants``
- 4.0
- 6.0
- - ``docutils.utils.smartyquotes``
+ - ``docutils.utils.smartquotes``
* - ``sphinx.util.typing.DirectiveOption``
- 4.0
diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst
index 7b7a0a1d4..0b2bd4e3e 100644
--- a/doc/usage/configuration.rst
+++ b/doc/usage/configuration.rst
@@ -419,6 +419,20 @@ General configuration
.. versionadded:: 1.1
+.. confval:: nitpick_ignore_regex
+
+ An extended version of :confval:`nitpick_ignore`, which instead interprets
+ the ``type`` and ``target`` strings as regular expressions. Note, that the
+ regular expression must match the whole string (as if the ``^`` and ``$``
+ markers were inserted).
+
+ For example, ``(r'py:.*', r'foo.*bar\.B.*')`` will ignore nitpicky warnings
+ for all python entities that start with ``'foo'`` and have ``'bar.B'`` in
+ them, such as ``('py:const', 'foo_package.bar.BAZ_VALUE')`` or
+ ``('py:class', 'food.bar.Barman')``.
+
+ .. versionadded:: 4.1
+
.. confval:: numfig
If true, figures, tables and code-blocks are automatically numbered if they
@@ -780,6 +794,10 @@ documentation on :ref:`intl` for details.
The default is ``['locales']``.
+ .. note:: The :option:`-v option for sphinx-build command <sphinx-build -v>`
+ is useful to check the locale_dirs config works as expected. It
+ emits debug messages if message catalog directory not found.
+
.. versionchanged:: 1.5
Use ``locales`` directory as a default value
diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst
index da0ff7c99..13a2b3010 100644
--- a/doc/usage/extensions/autodoc.rst
+++ b/doc/usage/extensions/autodoc.rst
@@ -343,6 +343,10 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
.. autoclass:: module.name::Noodle
+ * :rst:dir:`autoclass` also recognizes the ``class-doc-from`` option that
+ can be used to override the global value of :confval:`autoclass_content`.
+
+ .. versionadded:: 4.1
.. rst:directive:: autofunction
autodecorator
@@ -507,7 +511,7 @@ There are also config values that you can set:
The supported options are ``'members'``, ``'member-order'``,
``'undoc-members'``, ``'private-members'``, ``'special-members'``,
``'inherited-members'``, ``'show-inheritance'``, ``'ignore-module-all'``,
- ``'imported-members'`` and ``'exclude-members'``.
+ ``'imported-members'``, ``'exclude-members'`` and ``'class-doc-from'``.
.. versionadded:: 1.8
@@ -517,6 +521,9 @@ There are also config values that you can set:
.. versionchanged:: 2.1
Added ``'imported-members'``.
+ .. versionchanged:: 4.1
+ Added ``'class-doc-from'``.
+
.. confval:: autodoc_docstring_signature
Functions imported from C modules cannot be introspected, and therefore the
diff --git a/doc/usage/markdown.rst b/doc/usage/markdown.rst
index 3ab88ddad..ac24f8d62 100644
--- a/doc/usage/markdown.rst
+++ b/doc/usage/markdown.rst
@@ -8,13 +8,13 @@ Markdown
`Markdown`__ is a lightweight markup language with a simplistic plain text
formatting syntax. It exists in many syntactically different *flavors*. To
-support Markdown-based documentation, Sphinx can use `recommonmark`__.
-recommonmark is a Docutils bridge to `CommonMark-py`__, a Python package for
+support Markdown-based documentation, Sphinx can use `MyST-Parser`__.
+MyST-Parser is a Docutils bridge to `markdown-it-py`__, a Python package for
parsing the `CommonMark`__ Markdown flavor.
__ https://daringfireball.net/projects/markdown/
-__ https://recommonmark.readthedocs.io/en/latest/index.html
-__ https://github.com/rtfd/CommonMark-py
+__ https://myst-parser.readthedocs.io/en/latest/
+__ https://github.com/executablebooks/markdown-it-py
__ https://commonmark.org/
Configuration
@@ -22,23 +22,17 @@ Configuration
To configure your Sphinx project for Markdown support, proceed as follows:
-#. Install the Markdown parser *recommonmark*::
+#. Install the Markdown parser *MyST-Parser*::
- pip install --upgrade recommonmark
+ pip install --upgrade myst-parser
- .. note::
-
- The configuration as explained here requires recommonmark version
- 0.5.0 or later.
-
-#. Add *recommonmark* to the
+#. Add *myst_parser* to the
:confval:`list of configured extensions <extensions>`::
- extensions = ['recommonmark']
+ extensions = ['myst_parser']
- .. versionchanged:: 1.8
- Version 1.8 deprecates and version 3.0 removes the ``source_parsers``
- configuration variable that was used by older *recommonmark* versions.
+ .. note::
+ MyST-Parser requires Sphinx 2.1 or newer.
#. If you want to use Markdown files with extensions other than ``.md``, adjust
the :confval:`source_suffix` variable. The following example configures
@@ -51,8 +45,8 @@ To configure your Sphinx project for Markdown support, proceed as follows:
'.md': 'markdown',
}
-#. You can further configure *recommonmark* to allow custom syntax that
- standard *CommonMark* doesn't support. Read more in the `recommonmark
+#. You can further configure *MyST-Parser* to allow custom syntax that
+ standard *CommonMark* doesn't support. Read more in the `MyST-Parser
documentation`__.
-__ https://recommonmark.readthedocs.io/en/latest/auto_structify.html
+__ https://myst-parser.readthedocs.io/en/latest/using/syntax-optional.html
diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst
index 65a32b6c8..dfd347327 100644
--- a/doc/usage/restructuredtext/domains.rst
+++ b/doc/usage/restructuredtext/domains.rst
@@ -1680,6 +1680,9 @@ There is a set of directives allowing documenting command-line programs:
then ``:option:`rm -r``` would refer to the first option, while
``:option:`svn -r``` would refer to the second one.
+ If ``None`` is passed to the argument, the directive will reset the
+ current program name.
+
The program name may contain spaces (in case you want to document
subcommands like ``svn add`` and ``svn commit`` separately).
diff --git a/package-lock.json b/package-lock.json
index 087afcf3e..6fd89f9db 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -704,9 +704,9 @@
}
},
"lodash": {
- "version": "4.17.19",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
- "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"log4js": {
diff --git a/setup.py b/setup.py
index 7ce37f9ea..b669afc00 100644
--- a/setup.py
+++ b/setup.py
@@ -21,8 +21,7 @@ install_requires = [
'sphinxcontrib-htmlhelp',
'sphinxcontrib-serializinghtml',
'sphinxcontrib-qthelp',
- 'Jinja2>=2.3,<3.0',
- 'MarkupSafe<2.0',
+ 'Jinja2>=2.3',
'Pygments>=2.0',
'docutils>=0.14,<0.18',
'snowballstemmer>=1.1',
diff --git a/sphinx/__init__.py b/sphinx/__init__.py
index 016e84c27..a1de1402b 100644
--- a/sphinx/__init__.py
+++ b/sphinx/__init__.py
@@ -27,8 +27,8 @@ if 'PYTHONWARNINGS' not in os.environ:
warnings.filterwarnings('ignore', "'U' mode is deprecated",
DeprecationWarning, module='docutils.io')
-__version__ = '4.0.2+'
-__released__ = '4.0.2' # used when Sphinx builds its own docs
+__version__ = '4.1.0'
+__released__ = '4.1.0' # used when Sphinx builds its own docs
#: Version info for better programmatic use.
#:
@@ -38,7 +38,7 @@ __released__ = '4.0.2' # used when Sphinx builds its own docs
#:
#: .. versionadded:: 1.2
#: Before version 1.2, check the string ``sphinx.__version__``.
-version_info = (4, 0, 2, 'beta', 0)
+version_info = (4, 1, 0, 'final', 0)
package_dir = path.abspath(path.dirname(__file__))
diff --git a/sphinx/application.py b/sphinx/application.py
index 4735beffd..afbb0f981 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -141,9 +141,9 @@ class Sphinx:
self.phase = BuildPhase.INITIALIZATION
self.verbosity = verbosity
self.extensions: Dict[str, Extension] = {}
- self.builder: Builder = None
- self.env: BuildEnvironment = None
- self.project: Project = None
+ self.builder: Optional[Builder] = None
+ self.env: Optional[BuildEnvironment] = None
+ self.project: Optional[Project] = None
self.registry = SphinxComponentRegistry()
self.html_themes: Dict[str, str] = {}
@@ -174,7 +174,7 @@ class Sphinx:
if status is None:
self._status: IO = StringIO()
- self.quiet = True
+ self.quiet: bool = True
else:
self._status = status
self.quiet = False
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index bedc65b61..722f9a280 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -11,7 +11,8 @@
import pickle
import time
from os import path
-from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Sequence, Set, Tuple, Type, Union
+from typing import (TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple,
+ Type, Union)
from docutils import nodes
from docutils.nodes import Node
@@ -88,7 +89,7 @@ class Builder:
ensuredir(self.doctreedir)
self.app: Sphinx = app
- self.env: BuildEnvironment = None
+ self.env: Optional[BuildEnvironment] = None
self.events: EventManager = app.events
self.config: Config = app.config
self.tags: Tags = app.tags
@@ -225,7 +226,7 @@ class Builder:
self.compile_catalogs(set(repo.catalogs), message)
def compile_specific_catalogs(self, specified_files: List[str]) -> None:
- def to_domain(fpath: str) -> str:
+ def to_domain(fpath: str) -> Optional[str]:
docname = self.env.path2doc(path.abspath(fpath))
if docname:
return docname_to_domain(docname, self.config.gettext_compact)
diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py
index 05e12c173..a46b80c08 100644
--- a/sphinx/builders/linkcheck.py
+++ b/sphinx/builders/linkcheck.py
@@ -129,7 +129,7 @@ class CheckExternalLinksBuilder(DummyBuilder):
# create queues and worker threads
self._wqueue: PriorityQueue[CheckRequestType] = PriorityQueue()
- self._rqueue: Queue = Queue()
+ self._rqueue: Queue[CheckResult] = Queue()
@property
def anchors_ignore(self) -> List[Pattern]:
@@ -228,43 +228,39 @@ class CheckExternalLinksBuilder(DummyBuilder):
)
return self._wqueue
- def process_result(self, result: Tuple[str, str, int, str, str, int]) -> None:
- uri, docname, lineno, status, info, code = result
+ def process_result(self, result: CheckResult) -> None:
+ filename = self.env.doc2path(result.docname, None)
- filename = self.env.doc2path(docname, None)
- linkstat = dict(filename=filename, lineno=lineno,
- status=status, code=code, uri=uri,
- info=info)
- if status == 'unchecked':
- self.write_linkstat(linkstat)
+ linkstat = dict(filename=filename, lineno=result.lineno,
+ status=result.status, code=result.code, uri=result.uri,
+ info=result.message)
+ self.write_linkstat(linkstat)
+
+ if result.status == 'unchecked':
return
- if status == 'working' and info == 'old':
- self.write_linkstat(linkstat)
+ if result.status == 'working' and result.message == 'old':
return
- if lineno:
- logger.info('(%16s: line %4d) ', docname, lineno, nonl=True)
- if status == 'ignored':
- if info:
- logger.info(darkgray('-ignored- ') + uri + ': ' + info)
+ if result.lineno:
+ logger.info('(%16s: line %4d) ', result.docname, result.lineno, nonl=True)
+ if result.status == 'ignored':
+ if result.message:
+ logger.info(darkgray('-ignored- ') + result.uri + ': ' + result.message)
else:
- logger.info(darkgray('-ignored- ') + uri)
- self.write_linkstat(linkstat)
- elif status == 'local':
- logger.info(darkgray('-local- ') + uri)
- self.write_entry('local', docname, filename, lineno, uri)
- self.write_linkstat(linkstat)
- elif status == 'working':
- logger.info(darkgreen('ok ') + uri + info)
- self.write_linkstat(linkstat)
- elif status == 'broken':
+ logger.info(darkgray('-ignored- ') + result.uri)
+ elif result.status == 'local':
+ logger.info(darkgray('-local- ') + result.uri)
+ self.write_entry('local', result.docname, filename, result.lineno, result.uri)
+ elif result.status == 'working':
+ logger.info(darkgreen('ok ') + result.uri + result.message)
+ elif result.status == 'broken':
if self.app.quiet or self.app.warningiserror:
- logger.warning(__('broken link: %s (%s)'), uri, info,
- location=(filename, lineno))
+ logger.warning(__('broken link: %s (%s)'), result.uri, result.message,
+ location=(filename, result.lineno))
else:
- logger.info(red('broken ') + uri + red(' - ' + info))
- self.write_entry('broken', docname, filename, lineno, uri + ': ' + info)
- self.write_linkstat(linkstat)
- elif status == 'redirected':
+ logger.info(red('broken ') + result.uri + red(' - ' + result.message))
+ self.write_entry('broken', result.docname, filename, result.lineno,
+ result.uri + ': ' + result.message)
+ elif result.status == 'redirected':
try:
text, color = {
301: ('permanently', purple),
@@ -272,16 +268,16 @@ class CheckExternalLinksBuilder(DummyBuilder):
303: ('with See Other', purple),
307: ('temporarily', turquoise),
308: ('permanently', purple),
- }[code]
+ }[result.code]
except KeyError:
text, color = ('with unknown code', purple)
linkstat['text'] = text
- logger.info(color('redirect ') + uri + color(' - ' + text + ' to ' + info))
- self.write_entry('redirected ' + text, docname, filename,
- lineno, uri + ' to ' + info)
- self.write_linkstat(linkstat)
+ logger.info(color('redirect ') + result.uri +
+ color(' - ' + text + ' to ' + result.message))
+ self.write_entry('redirected ' + text, result.docname, filename,
+ result.lineno, result.uri + ' to ' + result.message)
else:
- raise ValueError("Unknown status %s." % status)
+ raise ValueError("Unknown status %s." % result.status)
def write_entry(self, what: str, docname: str, filename: str, line: int,
uri: str) -> None:
@@ -576,7 +572,7 @@ class HyperlinkAvailabilityCheckWorker(Thread):
if status == 'rate-limited':
logger.info(darkgray('-rate limited- ') + uri + darkgray(' | sleeping...'))
else:
- self.rqueue.put((uri, docname, lineno, status, info, code))
+ self.rqueue.put(CheckResult(uri, docname, lineno, status, info, code))
self.wqueue.task_done()
def limit_rate(self, response: Response) -> Optional[float]:
diff --git a/sphinx/config.py b/sphinx/config.py
index 418ecf4fb..1ba98d007 100644
--- a/sphinx/config.py
+++ b/sphinx/config.py
@@ -131,6 +131,7 @@ class Config:
'manpages_url': (None, 'env', []),
'nitpicky': (False, None, []),
'nitpick_ignore': ([], None, []),
+ 'nitpick_ignore_regex': ([], None, []),
'numfig': (False, 'env', []),
'numfig_secnum_depth': (1, 'env', []),
'numfig_format': ({}, 'env', []), # will be initialized in init_numfig_format()
@@ -309,7 +310,7 @@ class Config:
self.__dict__.update(state)
-def eval_config_file(filename: str, tags: Tags) -> Dict[str, Any]:
+def eval_config_file(filename: str, tags: Optional[Tags]) -> Dict[str, Any]:
"""Evaluate a config file."""
namespace: Dict[str, Any] = {}
namespace['__file__'] = filename
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py
index 8f3e6a955..dbfad258c 100644
--- a/sphinx/domains/__init__.py
+++ b/sphinx/domains/__init__.py
@@ -11,8 +11,8 @@
import copy
from abc import ABC, abstractmethod
-from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Tuple,
- Type, Union, cast)
+from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, List, NamedTuple, Optional,
+ Tuple, Type, Union, cast)
from docutils import nodes
from docutils.nodes import Element, Node, system_message
@@ -196,7 +196,7 @@ class Domain:
#: data value for a fresh environment
initial_data: Dict = {}
#: data value
- data: Dict = None
+ data: Dict
#: data version, bump this when the format of `self.data` changes
data_version = 0
@@ -251,7 +251,7 @@ class Domain:
for role in objtype.roles:
self._role2type.setdefault(role, []).append(name)
- def role(self, name: str) -> RoleFunction:
+ def role(self, name: str) -> Optional[RoleFunction]:
"""Return a role adapter function that always gives the registered
role its full name ('domain:name') as the first argument.
"""
@@ -269,7 +269,7 @@ class Domain:
self._role_cache[name] = role_adapter
return role_adapter
- def directive(self, name: str) -> Callable:
+ def directive(self, name: str) -> Optional[Callable]:
"""Return a directive adapter class that always gives the registered
directive its full name ('domain:name') as ``self.name``.
"""
@@ -318,7 +318,7 @@ class Domain:
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
"""Resolve the pending_xref *node* with the given *typ* and *target*.
This method should return a new node, to replace the xref node,
@@ -393,11 +393,11 @@ class Domain:
return type.lname
return _('%s %s') % (self.label, type.lname)
- def get_enumerable_node_type(self, node: Node) -> str:
+ def get_enumerable_node_type(self, node: Node) -> Optional[str]:
"""Get type of enumerable nodes (experimental)."""
enum_node_type, _ = self.enumerable_nodes.get(node.__class__, (None, None))
return enum_node_type
- def get_full_qualified_name(self, node: Element) -> str:
+ def get_full_qualified_name(self, node: Element) -> Optional[str]:
"""Return full qualified name for given node."""
return None
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index b0711f68e..ed908c875 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -9,7 +9,8 @@
"""
import re
-from typing import Any, Callable, Dict, Generator, Iterator, List, Tuple, TypeVar, Union, cast
+from typing import (Any, Callable, Dict, Generator, Iterator, List, Optional, Tuple, TypeVar,
+ Union, cast)
from docutils import nodes
from docutils.nodes import Element, Node, TextElement, system_message
@@ -3807,7 +3808,7 @@ class CDomain(Domain):
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
- contnode: Element) -> Tuple[Element, str]:
+ contnode: Element) -> Tuple[Optional[Element], Optional[str]]:
parser = DefinitionParser(target, location=node, config=env.config)
try:
name = parser.parse_xref_object()
@@ -3844,7 +3845,7 @@ class CDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
- contnode: Element) -> Element:
+ contnode: Element) -> Optional[Element]:
return self._resolve_xref_inner(env, fromdocname, builder, typ,
target, node, contnode)[0]
diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py
index 772d486af..0e859acb4 100644
--- a/sphinx/domains/citation.py
+++ b/sphinx/domains/citation.py
@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
-from typing import TYPE_CHECKING, Any, Dict, List, Set, Tuple, cast
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, cast
from docutils import nodes
from docutils.nodes import Element
@@ -88,7 +88,7 @@ class CitationDomain(Domain):
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
docname, labelid, lineno = self.citations.get(target, ('', '', 0))
if not docname:
return None
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index d57c6e257..6c551b4a7 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -2292,6 +2292,10 @@ class ASTDeclarator(ASTBase):
def name(self) -> ASTNestedName:
raise NotImplementedError(repr(self))
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ raise NotImplementedError(repr(self))
+
@property
def isPack(self) -> bool:
raise NotImplementedError(repr(self))
@@ -2339,6 +2343,10 @@ class ASTDeclaratorNameParamQual(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.declId
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.declId = name
+
@property
def isPack(self) -> bool:
return False
@@ -2420,6 +2428,10 @@ class ASTDeclaratorNameBitField(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.declId
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.declId = name
+
def get_param_id(self, version: int) -> str: # only the parameters (if any)
return ''
@@ -2466,6 +2478,10 @@ class ASTDeclaratorPtr(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.next.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
@property
def function_params(self) -> List[ASTFunctionParameter]:
return self.next.function_params
@@ -2565,6 +2581,10 @@ class ASTDeclaratorRef(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.next.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
@property
def isPack(self) -> bool:
return True
@@ -2629,6 +2649,10 @@ class ASTDeclaratorParamPack(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.next.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
@property
def function_params(self) -> List[ASTFunctionParameter]:
return self.next.function_params
@@ -2689,6 +2713,10 @@ class ASTDeclaratorMemPtr(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.next.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
@property
def function_params(self) -> List[ASTFunctionParameter]:
return self.next.function_params
@@ -2782,6 +2810,10 @@ class ASTDeclaratorParen(ASTDeclarator):
def name(self) -> ASTNestedName:
return self.inner.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.inner.name = name
+
@property
def function_params(self) -> List[ASTFunctionParameter]:
return self.inner.function_params
@@ -2913,6 +2945,10 @@ class ASTType(ASTBase):
def name(self) -> ASTNestedName:
return self.decl.name
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.decl.name = name
+
@property
def isPack(self) -> bool:
return self.decl.isPack
@@ -7558,7 +7594,7 @@ class CPPDomain(Domain):
def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref,
- contnode: Element) -> Tuple[Element, str]:
+ contnode: Element) -> Tuple[Optional[Element], Optional[str]]:
# add parens again for those that could be functions
if typ == 'any' or typ == 'func':
target += '()'
@@ -7707,7 +7743,7 @@ class CPPDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
return self._resolve_xref_inner(env, fromdocname, builder, typ,
target, node, contnode)[0]
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index 92d0e1d61..8511a1d6a 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
-from typing import Any, Dict, Iterator, List, Tuple, cast
+from typing import Any, Dict, Iterator, List, Optional, Tuple, cast
from docutils import nodes
from docutils.nodes import Element, Node
@@ -413,7 +413,7 @@ class JavaScriptDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
mod_name = node.get('js:module')
prefix = node.get('js:object')
searchorder = 1 if node.hasattr('refspecific') else 0
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
index 88db1ad0e..aa6f9422d 100644
--- a/sphinx/domains/math.py
+++ b/sphinx/domains/math.py
@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
-from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Tuple
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple
from docutils import nodes
from docutils.nodes import Element, Node, make_id, system_message
@@ -97,7 +97,7 @@ class MathDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: "Builder",
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
assert typ in ('eq', 'numref')
docname, number = self.equations.get(target, (None, None))
if docname:
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
index 069737bde..7d39d80ed 100644
--- a/sphinx/domains/python.py
+++ b/sphinx/domains/python.py
@@ -15,7 +15,7 @@ import sys
import typing
import warnings
from inspect import Parameter
-from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Tuple, Type, cast
+from typing import Any, Dict, Iterable, Iterator, List, NamedTuple, Optional, Tuple, Type, cast
from docutils import nodes
from docutils.nodes import Element, Node
@@ -1246,7 +1246,7 @@ class PythonDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
type: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
modname = node.get('py:module')
clsname = node.get('py:class')
searchmode = 1 if node.hasattr('refspecific') else 0
@@ -1344,7 +1344,7 @@ class PythonDomain(Domain):
else:
yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
- def get_full_qualified_name(self, node: Element) -> str:
+ def get_full_qualified_name(self, node: Element) -> Optional[str]:
modname = node.get('py:module')
clsname = node.get('py:class')
target = node.get('reftarget')
diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py
index d048c2dfb..539a610bb 100644
--- a/sphinx/domains/rst.py
+++ b/sphinx/domains/rst.py
@@ -9,7 +9,7 @@
"""
import re
-from typing import Any, Dict, Iterator, List, Tuple, cast
+from typing import Any, Dict, Iterator, List, Optional, Tuple, cast
from docutils.nodes import Element
from docutils.parsers.rst import directives
@@ -247,7 +247,7 @@ class ReSTDomain(Domain):
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element
- ) -> Element:
+ ) -> Optional[Element]:
objtypes = self.objtypes_for_role(typ)
for objtype in objtypes:
todocname, node_id = self.objects.get((objtype, target), (None, None))
diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py
index 5e10646f0..7660f84c9 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std.py
@@ -285,7 +285,7 @@ class OptionXRefRole(XRefRole):
def split_term_classifiers(line: str) -> List[Optional[str]]:
# split line into a term and classifiers. if no classifier, None is used..
- parts = re.split(' +: +', line) + [None]
+ parts: List[Optional[str]] = re.split(' +: +', line) + [None]
return parts
@@ -621,7 +621,7 @@ class StandardDomain(Domain):
}
# node_class -> (figtype, title_getter)
- enumerable_nodes: Dict[Type[Node], Tuple[str, Callable]] = {
+ enumerable_nodes: Dict[Type[Node], Tuple[str, Optional[Callable]]] = {
nodes.figure: ('figure', None),
nodes.table: ('table', None),
nodes.container: ('code-block', None),
@@ -812,7 +812,8 @@ class StandardDomain(Domain):
return newnode
def resolve_xref(self, env: "BuildEnvironment", fromdocname: str, builder: "Builder",
- typ: str, target: str, node: pending_xref, contnode: Element) -> Element:
+ typ: str, target: str, node: pending_xref, contnode: Element
+ ) -> Optional[Element]:
if typ == 'ref':
resolver = self._resolve_ref_xref
elif typ == 'numref':
@@ -832,7 +833,7 @@ class StandardDomain(Domain):
def _resolve_ref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str, node: pending_xref,
- contnode: Element) -> Element:
+ contnode: Element) -> Optional[Element]:
if node['refexplicit']:
# reference to anonymous label; the reference uses
# the supplied link caption
@@ -850,7 +851,7 @@ class StandardDomain(Domain):
def _resolve_numref_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
+ node: pending_xref, contnode: Element) -> Optional[Element]:
if target in self.labels:
docname, labelid, figname = self.labels.get(target, ('', '', ''))
else:
@@ -913,7 +914,7 @@ class StandardDomain(Domain):
def _resolve_keyword_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
+ node: pending_xref, contnode: Element) -> Optional[Element]:
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.labels.get(target, ('', '', ''))
if not docname:
@@ -923,7 +924,7 @@ class StandardDomain(Domain):
def _resolve_doc_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
+ node: pending_xref, contnode: Element) -> Optional[Element]:
# directly reference to document by source name; can be absolute or relative
refdoc = node.get('refdoc', fromdocname)
docname = docname_join(refdoc, node['reftarget'])
@@ -940,7 +941,7 @@ class StandardDomain(Domain):
def _resolve_option_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
+ node: pending_xref, contnode: Element) -> Optional[Element]:
progname = node.get('std:program')
target = target.strip()
docname, labelid = self.progoptions.get((progname, target), ('', ''))
@@ -977,7 +978,7 @@ class StandardDomain(Domain):
def _resolve_obj_xref(self, env: "BuildEnvironment", fromdocname: str,
builder: "Builder", typ: str, target: str,
- node: pending_xref, contnode: Element) -> Element:
+ node: pending_xref, contnode: Element) -> Optional[Element]:
objtypes = self.objtypes_for_role(typ) or []
for objtype in objtypes:
if (objtype, target) in self.objects:
@@ -1041,7 +1042,7 @@ class StandardDomain(Domain):
def is_enumerable_node(self, node: Node) -> bool:
return node.__class__ in self.enumerable_nodes
- def get_numfig_title(self, node: Node) -> str:
+ def get_numfig_title(self, node: Node) -> Optional[str]:
"""Get the title of enumerable nodes to refer them using its title"""
if self.is_enumerable_node(node):
elem = cast(Element, node)
@@ -1055,7 +1056,7 @@ class StandardDomain(Domain):
return None
- def get_enumerable_node_type(self, node: Node) -> str:
+ def get_enumerable_node_type(self, node: Node) -> Optional[str]:
"""Get type of enumerable nodes."""
def has_child(node: Element, cls: Type) -> bool:
return any(isinstance(child, cls) for child in node)
@@ -1094,7 +1095,7 @@ class StandardDomain(Domain):
# Maybe it is defined in orphaned document.
raise ValueError from exc
- def get_full_qualified_name(self, node: Element) -> str:
+ def get_full_qualified_name(self, node: Element) -> Optional[str]:
if node.get('reftype') == 'option':
progname = node.get('std:program')
command = ws_re.split(node.get('reftarget'))
@@ -1109,7 +1110,8 @@ class StandardDomain(Domain):
return None
-def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref) -> bool:
+def warn_missing_reference(app: "Sphinx", domain: Domain, node: pending_xref
+ ) -> Optional[bool]:
if (domain and domain.name != 'std') or node['reftype'] != 'ref':
return None
else:
diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py
index 0483dcdf9..2914a78af 100644
--- a/sphinx/environment/__init__.py
+++ b/sphinx/environment/__init__.py
@@ -14,8 +14,8 @@ from collections import defaultdict
from copy import copy
from datetime import datetime
from os import path
-from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Set, Tuple,
- Union)
+from typing import (TYPE_CHECKING, Any, Callable, Dict, Generator, Iterator, List, Optional,
+ Set, Tuple, Union)
from docutils import nodes
from docutils.nodes import Node
@@ -87,7 +87,7 @@ class BuildEnvironment:
transformations to resolve links to them.
"""
- domains: Dict[str, Domain] = None
+ domains: Dict[str, Domain]
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
@@ -266,7 +266,7 @@ class BuildEnvironment:
raise an exception if the user tries to use an environment with an
incompatible versioning method.
"""
- condition: Union[bool, Callable] = None
+ condition: Union[bool, Callable]
if callable(method):
condition = method
else:
@@ -309,7 +309,7 @@ class BuildEnvironment:
domain.merge_domaindata(docnames, other.domaindata[domainname])
self.events.emit('env-merge-info', self, docnames, other)
- def path2doc(self, filename: str) -> str:
+ def path2doc(self, filename: str) -> Optional[str]:
"""Return the docname for the filename if the file is document.
*filename* should be absolute or relative to the source directory.
@@ -577,7 +577,7 @@ class BuildEnvironment:
# allow custom references to be resolved
self.events.emit('doctree-resolved', doctree, docname)
- def collect_relations(self) -> Dict[str, List[str]]:
+ def collect_relations(self) -> Dict[str, List[Optional[str]]]:
traversed = set()
def traverse_toctree(parent: str, docname: str) -> Iterator[Tuple[str, str]]:
diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py
index 3ee2f9ff0..a62e951d7 100644
--- a/sphinx/environment/adapters/toctree.py
+++ b/sphinx/environment/adapters/toctree.py
@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
-from typing import TYPE_CHECKING, Any, Iterable, List, cast
+from typing import TYPE_CHECKING, Any, Iterable, List, Optional, cast
from docutils import nodes
from docutils.nodes import Element, Node
@@ -48,7 +48,7 @@ class TocTree:
def resolve(self, docname: str, builder: "Builder", toctree: addnodes.toctree,
prune: bool = True, maxdepth: int = 0, titles_only: bool = False,
- collapse: bool = False, includehidden: bool = False) -> Element:
+ collapse: bool = False, includehidden: bool = False) -> Optional[Element]:
"""Resolve a *toctree* node into individual bullet lists with titles
as items, returning None (if no containing titles are found) or
a new node.
@@ -313,7 +313,7 @@ class TocTree:
return toc
def get_toctree_for(self, docname: str, builder: "Builder", collapse: bool,
- **kwargs: Any) -> Element:
+ **kwargs: Any) -> Optional[Element]:
"""Return the global TOC nodetree."""
doctree = self.env.get_doctree(self.env.config.root_doc)
toctrees: List[Element] = []
diff --git a/sphinx/environment/collectors/__init__.py b/sphinx/environment/collectors/__init__.py
index e27091018..f4020431f 100644
--- a/sphinx/environment/collectors/__init__.py
+++ b/sphinx/environment/collectors/__init__.py
@@ -8,7 +8,7 @@
:license: BSD, see LICENSE for details.
"""
-from typing import TYPE_CHECKING, Dict, List, Set
+from typing import TYPE_CHECKING, Dict, List, Optional, Set
from docutils import nodes
@@ -27,7 +27,7 @@ class EnvironmentCollector:
entries and toctrees, etc.
"""
- listener_ids: Dict[str, int] = None
+ listener_ids: Optional[Dict[str, int]] = None
def enable(self, app: "Sphinx") -> None:
assert self.listener_ids is None
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index de94ff1c5..95aba45d8 100644
--- a/sphinx/ext/autodoc/__init__.py
+++ b/sphinx/ext/autodoc/__init__.py
@@ -30,7 +30,7 @@ from sphinx.ext.autodoc.mock import ismock, mock, undecorate
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect, logging
-from sphinx.util.docstrings import extract_metadata, prepare_docstring
+from sphinx.util.docstrings import prepare_docstring, separate_metadata
from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr,
stringify_signature)
from sphinx.util.typing import OptionSpec, get_type_hints, restify
@@ -129,6 +129,14 @@ def member_order_option(arg: Any) -> Optional[str]:
raise ValueError(__('invalid value for member-order option: %s') % arg)
+def class_doc_from_option(arg: Any) -> Optional[str]:
+ """Used to convert the :class-doc-from: option to autoclass directives."""
+ if arg in ('both', 'class', 'init'):
+ return arg
+ else:
+ raise ValueError(__('invalid value for class-doc-from option: %s') % arg)
+
+
SUPPRESS = object()
@@ -381,8 +389,8 @@ class Documenter:
# functions can contain a signature which is then used instead of
# an autogenerated one
try:
- explicit_modname, path, base, args, retann = \
- py_ext_sig_re.match(self.name).groups()
+ matched = py_ext_sig_re.match(self.name)
+ explicit_modname, path, base, args, retann = matched.groups()
except AttributeError:
logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name),
type='autodoc')
@@ -404,8 +412,8 @@ class Documenter:
self.args = args
self.retann = retann
- self.fullname = (self.modname or '') + \
- ('.' + '.'.join(self.objpath) if self.objpath else '')
+ self.fullname = ((self.modname or '') +
+ ('.' + '.'.join(self.objpath) if self.objpath else ''))
return True
def import_object(self, raiseerror: bool = False) -> bool:
@@ -701,6 +709,8 @@ class Documenter:
# if isattr is True, the member is documented as an attribute
if member is INSTANCEATTR:
isattr = True
+ elif (namespace, membername) in attr_docs:
+ isattr = True
else:
isattr = False
@@ -722,9 +732,9 @@ class Documenter:
# hack for ClassDocumenter to inject docstring via ObjectMember
doc = obj.docstring
+ doc, metadata = separate_metadata(doc)
has_doc = bool(doc)
- metadata = extract_metadata(doc)
if 'private' in metadata:
# consider a member private if docstring has "private" metadata
isprivate = True
@@ -761,7 +771,6 @@ class Documenter:
else:
# keep documented attributes
keep = True
- isattr = True
elif want_all and isprivate:
if has_doc or self.options.undoc_members:
if self.options.private_members is None:
@@ -816,8 +825,9 @@ class Documenter:
if self.objpath:
self.env.temp_data['autodoc:class'] = self.objpath[0]
- want_all = all_members or self.options.inherited_members or \
- self.options.members is ALL
+ want_all = (all_members or
+ self.options.inherited_members or
+ self.options.members is ALL)
# find out which members are documentable
members_check_module, members = self.get_object_members(want_all)
@@ -833,8 +843,7 @@ class Documenter:
classes.sort(key=lambda cls: cls.priority)
# give explicitly separated module name, so that members
# of inner classes can be documented
- full_mname = self.modname + '::' + \
- '.'.join(self.objpath + [mname])
+ full_mname = self.modname + '::' + '.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr))
@@ -1320,12 +1329,12 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
if typ is object:
pass # default implementation. skipped.
else:
- self.annotate_to_first_argument(func, typ)
-
- documenter = FunctionDocumenter(self.directive, '')
- documenter.object = func
- documenter.objpath = [None]
- sigs.append(documenter.format_signature())
+ dispatchfunc = self.annotate_to_first_argument(func, typ)
+ if dispatchfunc:
+ documenter = FunctionDocumenter(self.directive, '')
+ documenter.object = dispatchfunc
+ documenter.objpath = [None]
+ sigs.append(documenter.format_signature())
if overloaded:
actual = inspect.signature(self.object,
type_aliases=self.config.autodoc_type_aliases)
@@ -1350,28 +1359,34 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
return overload.replace(parameters=parameters)
- def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
+ def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]:
"""Annotate type hint to the first argument of function if needed."""
try:
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
except TypeError as exc:
logger.warning(__("Failed to get a function signature for %s: %s"),
self.fullname, exc)
- return
+ return None
except ValueError:
- return
+ return None
if len(sig.parameters) == 0:
- return
+ return None
+
+ def dummy():
+ pass
params = list(sig.parameters.values())
if params[0].annotation is Parameter.empty:
params[0] = params[0].replace(annotation=typ)
try:
- func.__signature__ = sig.replace(parameters=params) # type: ignore
+ dummy.__signature__ = sig.replace(parameters=params) # type: ignore
+ return dummy
except (AttributeError, TypeError):
# failed to update signature (ex. built-in or extension types)
- return
+ return None
+ else:
+ return None
class DecoratorDocumenter(FunctionDocumenter):
@@ -1417,6 +1432,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
'show-inheritance': bool_option, 'member-order': member_order_option,
'exclude-members': exclude_members_option,
'private-members': members_option, 'special-members': members_option,
+ 'class-doc-from': class_doc_from_option,
}
_signature_class: Any = None
@@ -1651,7 +1667,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
if lines is not None:
return lines
- content = self.config.autoclass_content
+ classdoc_from = self.options.get('class-doc-from', self.config.autoclass_content)
docstrings = []
attrdocstring = self.get_attr(self.object, '__doc__', None)
@@ -1660,7 +1676,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
# for classes, what the "docstring" is can be controlled via a
# config value; the default is only the class docstring
- if content in ('both', 'init'):
+ if classdoc_from in ('both', 'init'):
__init__ = self.get_attr(self.object, '__init__', None)
initdocstring = getdoc(__init__, self.get_attr,
self.config.autodoc_inherit_docstrings,
@@ -1682,7 +1698,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
initdocstring.strip() == object.__new__.__doc__)): # for !pypy
initdocstring = None
if initdocstring:
- if content == 'init':
+ if classdoc_from == 'init':
docstrings = [initdocstring]
else:
docstrings.append(initdocstring)
@@ -1918,7 +1934,7 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
return True
else:
doc = self.get_doc()
- metadata = extract_metadata('\n'.join(sum(doc, [])))
+ docstring, metadata = separate_metadata('\n'.join(sum(doc, [])))
if 'hide-value' in metadata:
return True
@@ -1952,8 +1968,8 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin,
pass
def get_real_modname(self) -> str:
- return self.get_attr(self.parent or self.object, '__module__', None) \
- or self.modname
+ real_modname = self.get_attr(self.parent or self.object, '__module__', None)
+ return real_modname or self.modname
def get_module_comment(self, attrname: str) -> Optional[List[str]]:
try:
@@ -2018,8 +2034,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
- return inspect.isroutine(member) and \
- not isinstance(parent, ModuleDocumenter)
+ return inspect.isroutine(member) and not isinstance(parent, ModuleDocumenter)
def import_object(self, raiseerror: bool = False) -> bool:
ret = super().import_object(raiseerror)
@@ -2109,13 +2124,13 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if typ is object:
pass # default implementation. skipped.
else:
- self.annotate_to_first_argument(func, typ)
-
- documenter = MethodDocumenter(self.directive, '')
- documenter.parent = self.parent
- documenter.object = func
- documenter.objpath = [None]
- sigs.append(documenter.format_signature())
+ dispatchmeth = self.annotate_to_first_argument(func, typ)
+ if dispatchmeth:
+ documenter = MethodDocumenter(self.directive, '')
+ documenter.parent = self.parent
+ documenter.object = dispatchmeth
+ documenter.objpath = [None]
+ sigs.append(documenter.format_signature())
if overloaded:
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
actual = inspect.signature(self.object, bound_method=False,
@@ -2149,27 +2164,34 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return overload.replace(parameters=parameters)
- def annotate_to_first_argument(self, func: Callable, typ: Type) -> None:
+ def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]:
"""Annotate type hint to the first argument of function if needed."""
try:
sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases)
except TypeError as exc:
logger.warning(__("Failed to get a method signature for %s: %s"),
self.fullname, exc)
- return
+ return None
except ValueError:
- return
+ return None
+
if len(sig.parameters) == 1:
- return
+ return None
+
+ def dummy():
+ pass
params = list(sig.parameters.values())
if params[1].annotation is Parameter.empty:
params[1] = params[1].replace(annotation=typ)
try:
- func.__signature__ = sig.replace(parameters=params) # type: ignore
+ dummy.__signature__ = sig.replace(parameters=params) # type: ignore
+ return dummy
except (AttributeError, TypeError):
# failed to update signature (ex. built-in or extension types)
- return
+ return None
+ else:
+ return None
class NonDataDescriptorMixin(DataDocumenterMixinBase):
@@ -2447,8 +2469,8 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
return ret
def get_real_modname(self) -> str:
- return self.get_attr(self.parent or self.object, '__module__', None) \
- or self.modname
+ real_modname = self.get_attr(self.parent or self.object, '__module__', None)
+ return real_modname or self.modname
def should_suppress_value_header(self) -> bool:
if super().should_suppress_value_header():
@@ -2456,7 +2478,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type:
else:
doc = self.get_doc()
if doc:
- metadata = extract_metadata('\n'.join(sum(doc, [])))
+ docstring, metadata = separate_metadata('\n'.join(sum(doc, [])))
if 'hide-value' in metadata:
return True
@@ -2549,8 +2571,8 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
pass
def get_real_modname(self) -> str:
- return self.get_attr(self.parent or self.object, '__module__', None) \
- or self.modname
+ real_modname = self.get_attr(self.parent or self.object, '__module__', None)
+ return real_modname or self.modname
def add_directive_header(self, sig: str) -> None:
super().add_directive_header(sig)
diff --git a/sphinx/ext/autodoc/directive.py b/sphinx/ext/autodoc/directive.py
index c58d0c411..a554adf68 100644
--- a/sphinx/ext/autodoc/directive.py
+++ b/sphinx/ext/autodoc/directive.py
@@ -30,7 +30,7 @@ logger = logging.getLogger(__name__)
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
'show-inheritance', 'private-members', 'special-members',
'ignore-module-all', 'exclude-members', 'member-order',
- 'imported-members']
+ 'imported-members', 'class-doc-from']
AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members',
'exclude-members']
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index cacff8c5c..be9f0cc65 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -662,8 +662,10 @@ def import_ivar_by_name(name: str, prefixes: List[str] = [None]) -> Tuple[str, A
name, attr = name.rsplit(".", 1)
real_name, obj, parent, modname = import_by_name(name, prefixes)
qualname = real_name.replace(modname + ".", "")
- analyzer = ModuleAnalyzer.for_module(modname)
- if (qualname, attr) in analyzer.find_attr_docs():
+ analyzer = ModuleAnalyzer.for_module(getattr(obj, '__module__', modname))
+ analyzer.analyze()
+ # check for presence in `annotations` to include dataclass attributes
+ if (qualname, attr) in analyzer.attr_docs or (qualname, attr) in analyzer.annotations:
return real_name + "." + attr, INSTANCEATTR, obj, modname
except (ImportError, ValueError, PycodeError):
pass
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index d1130d096..4f3493659 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -239,15 +239,33 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
name, exc, type='autosummary')
return False
- def get_members(obj: Any, types: Set[str], include_public: List[str] = [],
- imported: bool = True) -> Tuple[List[str], List[str]]:
- items: List[str] = []
- public: List[str] = []
+ def get_class_members(obj: Any) -> Dict[str, Any]:
+ members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr)
+ return {name: member.object for name, member in members.items()}
+
+ def get_module_members(obj: Any) -> Dict[str, Any]:
+ members = {}
for name in dir(obj):
try:
- value = safe_getattr(obj, name)
+ members[name] = safe_getattr(obj, name)
except AttributeError:
continue
+ return members
+
+ def get_all_members(obj: Any) -> Dict[str, Any]:
+ if doc.objtype == "module":
+ return get_module_members(obj)
+ elif doc.objtype == "class":
+ return get_class_members(obj)
+ return {}
+
+ def get_members(obj: Any, types: Set[str], include_public: List[str] = [],
+ imported: bool = True) -> Tuple[List[str], List[str]]:
+ items: List[str] = []
+ public: List[str] = []
+
+ all_members = get_all_members(obj)
+ for name, value in all_members.items():
documenter = get_documenter(app, value, obj)
if documenter.objtype in types:
# skip imported members if expected
diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py
index c239f5a4a..cd6e8066b 100644
--- a/sphinx/jinja2glue.py
+++ b/sphinx/jinja2glue.py
@@ -12,7 +12,7 @@ from os import path
from pprint import pformat
from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Tuple, Union
-from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound, contextfunction
+from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound
from jinja2.environment import Environment
from jinja2.sandbox import SandboxedEnvironment
from jinja2.utils import open_if_exists
@@ -22,6 +22,11 @@ from sphinx.theming import Theme
from sphinx.util import logging
from sphinx.util.osutil import mtimes_of_files
+try:
+ from jinja2.utils import pass_context # type: ignore # jinja2-3.0 or above
+except ImportError:
+ from jinja2 import contextfunction as pass_context
+
if TYPE_CHECKING:
from sphinx.builders import Builder
@@ -101,7 +106,7 @@ class idgen:
next = __next__ # Python 2/Jinja compatibility
-@contextfunction
+@pass_context
def warning(context: Dict, message: str, *args: Any, **kwargs: Any) -> str:
if 'pagename' in context:
filename = context.get('pagename') + context.get('file_suffix', '')
@@ -180,9 +185,9 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.environment.filters['toint'] = _toint
self.environment.filters['todim'] = _todim
self.environment.filters['slice_index'] = _slice_index
- self.environment.globals['debug'] = contextfunction(pformat)
+ self.environment.globals['debug'] = pass_context(pformat)
self.environment.globals['warning'] = warning
- self.environment.globals['accesskey'] = contextfunction(accesskey)
+ self.environment.globals['accesskey'] = pass_context(accesskey)
self.environment.globals['idgen'] = idgen
if use_i18n:
self.environment.install_gettext_translations(builder.app.translator)
diff --git a/sphinx/project.py b/sphinx/project.py
index bb2314c63..d4293cdc4 100644
--- a/sphinx/project.py
+++ b/sphinx/project.py
@@ -10,7 +10,7 @@
import os
from glob import glob
-from typing import Dict, List, Set
+from typing import Dict, List, Optional, Set
from sphinx.locale import __
from sphinx.util import get_matching_files, logging, path_stabilize
@@ -60,7 +60,7 @@ class Project:
return self.docnames
- def path2doc(self, filename: str) -> str:
+ def path2doc(self, filename: str) -> Optional[str]:
"""Return the docname for the filename if the file is document.
*filename* should be absolute or relative to the source directory.
diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py
index c55a4fe4a..8e79385e2 100644
--- a/sphinx/pycode/__init__.py
+++ b/sphinx/pycode/__init__.py
@@ -25,6 +25,13 @@ from sphinx.pycode.parser import Parser
class ModuleAnalyzer:
+ annotations: Dict[Tuple[str, str], str]
+ attr_docs: Dict[Tuple[str, str], List[str]]
+ finals: List[str]
+ overloads: Dict[str, List[Signature]]
+ tagorder: Dict[str, int]
+ tags: Dict[str, Tuple[str, int, int]]
+
# cache for analyzer objects -- caches both by module and file name
cache: Dict[Tuple[str, str], Any] = {}
@@ -134,13 +141,6 @@ class ModuleAnalyzer:
# cache the source code as well
self.code = source.read()
- # will be filled by analyze()
- self.annotations: Dict[Tuple[str, str], str] = None
- self.attr_docs: Dict[Tuple[str, str], List[str]] = None
- self.finals: List[str] = None
- self.overloads: Dict[str, List[Signature]] = None
- self.tagorder: Dict[str, int] = None
- self.tags: Dict[str, Tuple[str, int, int]] = None
self._analyzed = False
def parse(self) -> None:
diff --git a/sphinx/pycode/parser.py b/sphinx/pycode/parser.py
index fa249d8c5..a00b481ce 100644
--- a/sphinx/pycode/parser.py
+++ b/sphinx/pycode/parser.py
@@ -129,8 +129,8 @@ class TokenProcessor:
lines = iter(buffers)
self.buffers = buffers
self.tokens = tokenize.generate_tokens(lambda: next(lines))
- self.current: Token = None
- self.previous: Token = None
+ self.current: Optional[Token] = None
+ self.previous: Optional[Token] = None
def get_line(self, lineno: int) -> str:
"""Returns specified line."""
@@ -178,7 +178,7 @@ class AfterCommentParser(TokenProcessor):
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
- self.comment: str = None
+ self.comment: Optional[str] = None
def fetch_rvalue(self) -> List[Token]:
"""Fetch right-hand value of assignment."""
@@ -223,16 +223,16 @@ class VariableCommentPicker(ast.NodeVisitor):
self.encoding = encoding
self.context: List[str] = []
self.current_classes: List[str] = []
- self.current_function: ast.FunctionDef = None
+ self.current_function: Optional[ast.FunctionDef] = None
self.comments: Dict[Tuple[str, str], str] = OrderedDict()
self.annotations: Dict[Tuple[str, str], str] = {}
- self.previous: ast.AST = None
+ self.previous: Optional[ast.AST] = None
self.deforders: Dict[str, int] = {}
self.finals: List[str] = []
self.overloads: Dict[str, List[Signature]] = {}
- self.typing: str = None
- self.typing_final: str = None
- self.typing_overload: str = None
+ self.typing: Optional[str] = None
+ self.typing_final: Optional[str] = None
+ self.typing_overload: Optional[str] = None
super().__init__()
def get_qualname_for(self, name: str) -> Optional[List[str]]:
@@ -308,7 +308,7 @@ class VariableCommentPicker(ast.NodeVisitor):
return False
- def get_self(self) -> ast.arg:
+ def get_self(self) -> Optional[ast.arg]:
"""Returns the name of first argument if in function."""
if self.current_function and self.current_function.args.args:
return self.current_function.args.args[0]
@@ -466,7 +466,7 @@ class DefinitionFinder(TokenProcessor):
def __init__(self, lines: List[str]) -> None:
super().__init__(lines)
- self.decorator: Token = None
+ self.decorator: Optional[Token] = None
self.context: List[str] = []
self.indents: List = []
self.definitions: Dict[str, Tuple[str, int, int]] = {}
diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py
index 2a3420244..4b0567969 100644
--- a/sphinx/search/__init__.py
+++ b/sphinx/search/__init__.py
@@ -12,7 +12,7 @@ import pickle
import re
from importlib import import_module
from os import path
-from typing import IO, Any, Dict, Iterable, List, Set, Tuple, Type
+from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple, Type
from docutils import nodes
from docutils.nodes import Node
@@ -440,7 +440,7 @@ class IndexBuilder:
else:
return []
- def get_js_stemmer_rawcode(self) -> str:
+ def get_js_stemmer_rawcode(self) -> Optional[str]:
return None
def get_js_stemmer_code(self) -> str:
diff --git a/sphinx/themes/basic/static/basic.css_t b/sphinx/themes/basic/static/basic.css_t
index db43499ad..45815bac0 100644
--- a/sphinx/themes/basic/static/basic.css_t
+++ b/sphinx/themes/basic/static/basic.css_t
@@ -819,7 +819,7 @@ div.code-block-caption code {
table.highlighttable td.linenos,
span.linenos,
-div.doctest > div.highlight span.gp { /* gp: Generic.Prompt */
+div.highlight span.gp { /* gp: Generic.Prompt */
user-select: none;
-webkit-user-select: text; /* Safari fallback only */
-webkit-user-select: none; /* Chrome/Safari */
diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js
index e09f9263f..8eb14218b 100644
--- a/sphinx/themes/basic/static/searchtools.js
+++ b/sphinx/themes/basic/static/searchtools.js
@@ -276,7 +276,7 @@ var Search = {
setTimeout(function() {
displayNextItem();
}, 5);
- } else if (DOCUMENTATION_OPTIONS.HAS_SOURCE) {
+ } else {
$.ajax({url: requestUrl,
dataType: "text",
complete: function(jqxhr, textstatus) {
@@ -289,12 +289,6 @@ var Search = {
displayNextItem();
}, 5);
}});
- } else {
- // no source available, just display title
- Search.output.append(listItem);
- setTimeout(function() {
- displayNextItem();
- }, 5);
}
}
// search finished, update title and status message
diff --git a/sphinx/transforms/__init__.py b/sphinx/transforms/__init__.py
index 213742636..66882e2b2 100644
--- a/sphinx/transforms/__init__.py
+++ b/sphinx/transforms/__init__.py
@@ -10,7 +10,7 @@
import re
import warnings
-from typing import TYPE_CHECKING, Any, Dict, Generator, List, Tuple
+from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Tuple
from docutils import nodes
from docutils.nodes import Element, Node, Text
@@ -72,8 +72,8 @@ class SphinxTransformer(Transformer):
A transformer for Sphinx.
"""
- document: nodes.document = None
- env: "BuildEnvironment" = None
+ document: nodes.document
+ env: Optional["BuildEnvironment"] = None
def set_environment(self, env: "BuildEnvironment") -> None:
self.env = env
diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py
index 8f6d37d8d..2a5d6f121 100644
--- a/sphinx/transforms/i18n.py
+++ b/sphinx/transforms/i18n.py
@@ -10,7 +10,7 @@
from os import path
from textwrap import indent
-from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, TypeVar
+from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, TypeVar
from docutils import nodes
from docutils.io import StringInput
@@ -416,7 +416,7 @@ class Locale(SphinxTransform):
.format(old_xref_rawsources, new_xref_rawsources),
location=node)
- def get_ref_key(node: addnodes.pending_xref) -> Tuple[str, str, str]:
+ def get_ref_key(node: addnodes.pending_xref) -> Optional[Tuple[str, str, str]]:
case = node["refdomain"], node["reftype"]
if case == ('std', 'term'):
return None
diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py
index e2899d994..281407983 100644
--- a/sphinx/transforms/post_transforms/__init__.py
+++ b/sphinx/transforms/post_transforms/__init__.py
@@ -8,6 +8,7 @@
:license: BSD, see LICENSE for details.
"""
+import re
from typing import Any, Dict, List, Optional, Tuple, Type, cast
from docutils import nodes
@@ -171,14 +172,27 @@ class ReferencesResolver(SphinxPostTransform):
warn = node.get('refwarn')
if self.config.nitpicky:
warn = True
+ dtype = '%s:%s' % (domain.name, typ) if domain else typ
if self.config.nitpick_ignore:
- dtype = '%s:%s' % (domain.name, typ) if domain else typ
if (dtype, target) in self.config.nitpick_ignore:
warn = False
# for "std" types also try without domain name
if (not domain or domain.name == 'std') and \
(typ, target) in self.config.nitpick_ignore:
warn = False
+ if self.config.nitpick_ignore_regex:
+ def matches_ignore(entry_type: str, entry_target: str) -> bool:
+ for ignore_type, ignore_target in self.config.nitpick_ignore_regex:
+ if re.fullmatch(ignore_type, entry_type) and \
+ re.fullmatch(ignore_target, entry_target):
+ return True
+ return False
+ if matches_ignore(dtype, target):
+ warn = False
+ # for "std" types also try without domain name
+ if (not domain or domain.name == 'std') and \
+ matches_ignore(typ, target):
+ warn = False
if not warn:
return
diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py
index d420f4f77..f1a6f1af6 100644
--- a/sphinx/util/__init__.py
+++ b/sphinx/util/__init__.py
@@ -22,8 +22,8 @@ from datetime import datetime
from importlib import import_module
from os import path
from time import mktime, strptime
-from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Pattern,
- Set, Tuple, Type)
+from typing import (IO, TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional,
+ Pattern, Set, Tuple, Type)
from urllib.parse import parse_qsl, quote_plus, urlencode, urlsplit, urlunsplit
from sphinx.deprecation import RemovedInSphinx50Warning
@@ -408,7 +408,7 @@ def import_object(objname: str, source: str = None) -> Any:
raise ExtensionError('Could not import %s' % objname, exc) from exc
-def split_full_qualified_name(name: str) -> Tuple[str, str]:
+def split_full_qualified_name(name: str) -> Tuple[Optional[str], str]:
"""Split full qualified name to a pair of modname and qualname.
A qualname is an abbreviation for "Qualified name" introduced at PEP-3155
diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py
index f74f03265..3a3367ebe 100644
--- a/sphinx/util/docfields.py
+++ b/sphinx/util/docfields.py
@@ -209,7 +209,7 @@ class DocFieldTransformer:
Transforms field lists in "doc field" syntax into better-looking
equivalents, using the field type definitions given on a domain.
"""
- typemap: Dict[str, Tuple[Field, bool]] = None
+ typemap: Dict[str, Tuple[Field, bool]]
def __init__(self, directive: "ObjectDescription") -> None:
self.directive = directive
diff --git a/sphinx/util/docstrings.py b/sphinx/util/docstrings.py
index 46bb5b9b8..d81d7dd99 100644
--- a/sphinx/util/docstrings.py
+++ b/sphinx/util/docstrings.py
@@ -11,26 +11,28 @@
import re
import sys
import warnings
-from typing import Dict, List
+from typing import Dict, List, Tuple
from docutils.parsers.rst.states import Body
-from sphinx.deprecation import RemovedInSphinx50Warning
+from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning
field_list_item_re = re.compile(Body.patterns['field_marker'])
-def extract_metadata(s: str) -> Dict[str, str]:
- """Extract metadata from docstring."""
+def separate_metadata(s: str) -> Tuple[str, Dict[str, str]]:
+ """Separate docstring into metadata and others."""
in_other_element = False
metadata: Dict[str, str] = {}
+ lines = []
if not s:
- return metadata
+ return s, metadata
for line in prepare_docstring(s):
if line.strip() == '':
in_other_element = False
+ lines.append(line)
else:
matched = field_list_item_re.match(line)
if matched and not in_other_element:
@@ -38,9 +40,20 @@ def extract_metadata(s: str) -> Dict[str, str]:
if field_name.startswith('meta '):
name = field_name[5:].strip()
metadata[name] = line[matched.end():].strip()
+ else:
+ lines.append(line)
else:
in_other_element = True
+ lines.append(line)
+
+ return '\n'.join(lines), metadata
+
+
+def extract_metadata(s: str) -> Dict[str, str]:
+ warnings.warn("extract_metadata() is deprecated.",
+ RemovedInSphinx60Warning, stacklevel=2)
+ docstring, metadata = separate_metadata(s)
return metadata
diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py
index 44483bdd8..c2e12e152 100644
--- a/sphinx/util/docutils.py
+++ b/sphinx/util/docutils.py
@@ -176,8 +176,8 @@ class sphinx_domains:
"""
def __init__(self, env: "BuildEnvironment") -> None:
self.env = env
- self.directive_func: Callable = None
- self.roles_func: Callable = None
+ self.directive_func: Callable = lambda *args: (None, [])
+ self.roles_func: Callable = lambda *args: (None, [])
def __enter__(self) -> None:
self.enable()
@@ -372,7 +372,7 @@ class SphinxRole:
if name:
self.name = name.lower()
else:
- self.name = self.env.temp_data.get('default_role')
+ self.name = self.env.temp_data.get('default_role', '')
if not self.name:
self.name = self.env.config.default_role
if not self.name:
@@ -491,7 +491,7 @@ class SphinxTranslator(nodes.NodeVisitor):
# cache a vanilla instance of nodes.document
# Used in new_document() function
-__document_cache__: nodes.document = None
+__document_cache__: Optional[nodes.document] = None
def new_document(source_path: str, settings: Any = None) -> nodes.document:
diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py
index 1e469d135..f7298e2e8 100644
--- a/sphinx/util/i18n.py
+++ b/sphinx/util/i18n.py
@@ -91,8 +91,11 @@ class CatalogRepository:
for locale_dir in self._locale_dirs:
locale_dir = path.join(self.basedir, locale_dir)
- if path.exists(path.join(locale_dir, self.language, 'LC_MESSAGES')):
+ locale_path = path.join(locale_dir, self.language, 'LC_MESSAGES')
+ if path.exists(locale_path):
yield locale_dir
+ else:
+ logger.verbose(__('locale_dir %s does not exists'), locale_path)
@property
def pofiles(self) -> Generator[Tuple[str, str], None, None]:
diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py
index 7c9adb0bf..f216e8797 100644
--- a/sphinx/util/inspect.py
+++ b/sphinx/util/inspect.py
@@ -18,8 +18,10 @@ import types
import typing
import warnings
from functools import partial, partialmethod
+from importlib import import_module
from inspect import Parameter, isclass, ismethod, ismethoddescriptor, ismodule # NOQA
from io import StringIO
+from types import ModuleType
from typing import Any, Callable, Dict, Mapping, Optional, Sequence, Tuple, Type, cast
from sphinx.deprecation import RemovedInSphinx50Warning
@@ -501,6 +503,78 @@ class DefaultValue:
return self.value
+class TypeAliasForwardRef:
+ """Pseudo typing class for autodoc_type_aliases.
+
+ This avoids the error on evaluating the type inside `get_type_hints()`.
+ """
+ def __init__(self, name: str) -> None:
+ self.name = name
+
+ def __call__(self) -> None:
+ # Dummy method to imitate special typing classes
+ pass
+
+ def __eq__(self, other: Any) -> bool:
+ return self.name == other
+
+
+class TypeAliasModule:
+ """Pseudo module class for autodoc_type_aliases."""
+
+ def __init__(self, modname: str, mapping: Dict[str, str]) -> None:
+ self.__modname = modname
+ self.__mapping = mapping
+
+ self.__module: Optional[ModuleType] = None
+
+ def __getattr__(self, name: str) -> Any:
+ fullname = '.'.join(filter(None, [self.__modname, name]))
+ if fullname in self.__mapping:
+ # exactly matched
+ return TypeAliasForwardRef(self.__mapping[fullname])
+ else:
+ prefix = fullname + '.'
+ nested = {k: v for k, v in self.__mapping.items() if k.startswith(prefix)}
+ if nested:
+ # sub modules or classes found
+ return TypeAliasModule(fullname, nested)
+ else:
+ # no sub modules or classes found.
+ try:
+ # return the real submodule if exists
+ return import_module(fullname)
+ except ImportError:
+ # return the real class
+ if self.__module is None:
+ self.__module = import_module(self.__modname)
+
+ return getattr(self.__module, name)
+
+
+class TypeAliasNamespace(Dict[str, Any]):
+ """Pseudo namespace class for autodoc_type_aliases.
+
+ This enables to look up nested modules and classes like `mod1.mod2.Class`.
+ """
+
+ def __init__(self, mapping: Dict[str, str]) -> None:
+ self.__mapping = mapping
+
+ def __getitem__(self, key: str) -> Any:
+ if key in self.__mapping:
+ # exactly matched
+ return TypeAliasForwardRef(self.__mapping[key])
+ else:
+ prefix = key + '.'
+ nested = {k: v for k, v in self.__mapping.items() if k.startswith(prefix)}
+ if nested:
+ # sub modules or classes found
+ return TypeAliasModule(key, nested)
+ else:
+ raise KeyError
+
+
def _should_unwrap(subject: Callable) -> bool:
"""Check the function should be unwrapped on getting signature."""
__globals__ = getglobals(subject)
@@ -549,12 +623,19 @@ def signature(subject: Callable, bound_method: bool = False, follow_wrapped: boo
try:
# Resolve annotations using ``get_type_hints()`` and type_aliases.
- annotations = typing.get_type_hints(subject, None, type_aliases)
+ localns = TypeAliasNamespace(type_aliases)
+ annotations = typing.get_type_hints(subject, None, localns)
for i, param in enumerate(parameters):
if param.name in annotations:
- parameters[i] = param.replace(annotation=annotations[param.name])
+ annotation = annotations[param.name]
+ if isinstance(annotation, TypeAliasForwardRef):
+ annotation = annotation.name
+ parameters[i] = param.replace(annotation=annotation)
if 'return' in annotations:
- return_annotation = annotations['return']
+ if isinstance(annotations['return'], TypeAliasForwardRef):
+ return_annotation = annotations['return'].name
+ else:
+ return_annotation = annotations['return']
except Exception:
# ``get_type_hints()`` does not support some kind of objects like partial,
# ForwardRef and so on.
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 64b7d8fb4..e0f4f0e70 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -320,8 +320,8 @@ def prefixed_warnings(prefix: str) -> Generator[None, None, None]:
prefix_filter.prefix = previous
else:
# not prefixed yet
+ prefix_filter = MessagePrefixFilter(prefix)
try:
- prefix_filter = MessagePrefixFilter(prefix)
warning_handler.addFilter(prefix_filter)
yield
finally:
@@ -472,7 +472,7 @@ class SphinxLogRecordTranslator(logging.Filter):
* Make a instance of SphinxLogRecord
* docname to path if location given
"""
- LogRecordClass: Type[logging.LogRecord] = None
+ LogRecordClass: Type[logging.LogRecord]
def __init__(self, app: "Sphinx") -> None:
self.app = app
diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py
index 44eb5d303..36db149b9 100644
--- a/sphinx/util/nodes.py
+++ b/sphinx/util/nodes.py
@@ -10,7 +10,8 @@
import re
import unicodedata
-from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Set, Tuple, Type, Union, cast
+from typing import (TYPE_CHECKING, Any, Callable, Iterable, List, Optional, Set, Tuple, Type,
+ Union, cast)
from docutils import nodes
from docutils.nodes import Element, Node
@@ -170,7 +171,7 @@ def apply_source_workaround(node: Element) -> None:
))):
logger.debug('[i18n] PATCH: %r to have source and line: %s',
get_full_module_name(node), repr_domxml(node))
- node.source = get_node_source(node)
+ node.source = get_node_source(node) or ''
node.line = 0 # need fix docutils to get `node.line`
return
@@ -266,7 +267,7 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
if node.get('translatable'):
msg = '.. image:: %s' % node['uri']
else:
- msg = None
+ msg = ''
elif isinstance(node, META_TYPE_NODES):
msg = node.rawcontent
elif isinstance(node, nodes.pending) and is_pending_meta(node):
@@ -279,14 +280,14 @@ def extract_messages(doctree: Element) -> Iterable[Tuple[Element, str]]:
yield node, msg
-def get_node_source(node: Element) -> str:
+def get_node_source(node: Element) -> Optional[str]:
for pnode in traverse_parent(node):
if pnode.source:
return pnode.source
return None
-def get_node_line(node: Element) -> int:
+def get_node_line(node: Element) -> Optional[int]:
for pnode in traverse_parent(node):
if pnode.line:
return pnode.line
@@ -300,7 +301,7 @@ def traverse_parent(node: Element, cls: Any = None) -> Iterable[Element]:
node = node.parent
-def get_prev_node(node: Node) -> Node:
+def get_prev_node(node: Node) -> Optional[Node]:
pos = node.parent.index(node)
if pos > 0:
return node.parent[pos - 1]
@@ -360,10 +361,11 @@ indextypes = [
]
-def process_index_entry(entry: str, targetid: str) -> List[Tuple[str, str, str, str, str]]:
+def process_index_entry(entry: str, targetid: str
+ ) -> List[Tuple[str, str, str, str, Optional[str]]]:
from sphinx.domains.python import pairindextypes
- indexentries: List[Tuple[str, str, str, str, str]] = []
+ indexentries: List[Tuple[str, str, str, str, Optional[str]]] = []
entry = entry.strip()
oentry = entry
main = ''
@@ -531,7 +533,8 @@ def make_id(env: "BuildEnvironment", document: nodes.document,
return node_id
-def find_pending_xref_condition(node: addnodes.pending_xref, condition: str) -> Element:
+def find_pending_xref_condition(node: addnodes.pending_xref, condition: str
+ ) -> Optional[Element]:
"""Pick matched pending_xref_condition node up from the pending_xref."""
for subnode in node:
if (isinstance(subnode, addnodes.pending_xref_condition) and
diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py
index 7ad7f81e7..2a83d6297 100644
--- a/sphinx/util/parallel.py
+++ b/sphinx/util/parallel.py
@@ -14,7 +14,7 @@ import sys
import time
import traceback
from math import sqrt
-from typing import Any, Callable, Dict, List, Sequence
+from typing import Any, Callable, Dict, List, Optional, Sequence
try:
import multiprocessing
@@ -62,7 +62,7 @@ class ParallelTasks:
# (optional) function performed by each task on the result of main task
self._result_funcs: Dict[int, Callable] = {}
# task arguments
- self._args: Dict[int, List[Any]] = {}
+ self._args: Dict[int, Optional[List[Any]]] = {}
# list of subprocesses (both started and waiting)
self._procs: Dict[int, multiprocessing.Process] = {}
# list of receiving pipe connections of running subprocesses
@@ -105,7 +105,8 @@ class ParallelTasks:
def join(self) -> None:
try:
while self._pworking:
- self._join_one()
+ if not self._join_one():
+ time.sleep(0.02)
except Exception:
# shutdown other child processes on failure
self.terminate()
@@ -119,7 +120,8 @@ class ParallelTasks:
self._precvs.pop(tid)
self._pworking -= 1
- def _join_one(self) -> None:
+ def _join_one(self) -> bool:
+ joined_any = False
for tid, pipe in self._precvs.items():
if pipe.poll():
exc, logs, result = pipe.recv()
@@ -131,15 +133,17 @@ class ParallelTasks:
self._procs[tid].join()
self._precvs.pop(tid)
self._pworking -= 1
+ joined_any = True
break
- else:
- time.sleep(0.02)
+
while self._precvsWaiting and self._pworking < self.nproc:
newtid, newprecv = self._precvsWaiting.popitem()
self._precvs[newtid] = newprecv
self._procs[newtid].start()
self._pworking += 1
+ return joined_any
+
def make_chunks(arguments: Sequence[str], nproc: int, maxbatch: int = 10) -> List[Any]:
# determine how many documents to read in one go
diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py
index 82b3f6bda..8103a8b10 100644
--- a/sphinx/util/rst.py
+++ b/sphinx/util/rst.py
@@ -18,11 +18,17 @@ from docutils.parsers.rst import roles
from docutils.parsers.rst.languages import en as english
from docutils.statemachine import StringList
from docutils.utils import Reporter
-from jinja2 import Environment, environmentfilter
+from jinja2 import Environment
from sphinx.locale import __
from sphinx.util import docutils, logging
+try:
+ from jinja2.utils import pass_environment # type: ignore # jinja2-3.0 or above
+except ImportError:
+ from jinja2 import environmentfilter as pass_environment
+
+
logger = logging.getLogger(__name__)
docinfo_re = re.compile(':\\w+:.*?')
@@ -51,7 +57,7 @@ def textwidth(text: str, widechars: str = 'WF') -> int:
return sum(charwidth(c, widechars) for c in text)
-@environmentfilter
+@pass_environment
def heading(env: Environment, text: str, level: int = 1) -> str:
"""Create a heading for *level*."""
assert level <= 3
diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py
index 417a963a7..8dcc08a9b 100644
--- a/sphinx/util/texescape.py
+++ b/sphinx/util/texescape.py
@@ -29,6 +29,8 @@ tex_replacements = [
# map special Unicode characters to TeX commands
('✓', r'\(\checkmark\)'),
('✔', r'\(\pmb{\checkmark}\)'),
+ ('✕', r'\(\times\)'),
+ ('✖', r'\(\pmb{\times}\)'),
# used to separate -- in options
('', r'{}'),
# map some special Unicode characters to similar ASCII ones
diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py
index 957f8a332..fb8daa623 100644
--- a/sphinx/util/typing.py
+++ b/sphinx/util/typing.py
@@ -64,7 +64,7 @@ RoleFunction = Callable[[str, str, str, int, Inliner, Dict[str, Any], List[str]]
Tuple[List[nodes.Node], List[nodes.system_message]]]
# A option spec for directive
-OptionSpec = Dict[str, Callable[[Optional[str]], Any]]
+OptionSpec = Dict[str, Callable[[str], Any]]
# title getter functions for enumerable nodes (see sphinx.domains.std)
TitleGetter = Callable[[nodes.Node], str]
diff --git a/tests/roots/test-autosummary/dummy_module.py b/tests/roots/test-autosummary/dummy_module.py
index 93d482b59..4adc0313e 100644
--- a/tests/roots/test-autosummary/dummy_module.py
+++ b/tests/roots/test-autosummary/dummy_module.py
@@ -3,6 +3,7 @@
module_attr
C.class_attr
+ C.instance_attr
C.prop_attr1
C.prop_attr2
C.C2
@@ -51,6 +52,12 @@ class C:
#: value is integer.
class_attr = 42
+ def __init__(self):
+ #: This is an instance attribute
+ #:
+ #: value is a string
+ self.instance_attr = "42"
+
def _prop_attr_get(self):
"""
This is a function docstring
diff --git a/tests/roots/test-ext-autodoc/target/annotations.py b/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
index ef600e2af..d8a2fecef 100644
--- a/tests/roots/test-ext-autodoc/target/annotations.py
+++ b/tests/roots/test-ext-autodoc/target/autodoc_type_aliases.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import io
from typing import overload
myint = int
@@ -11,6 +12,10 @@ variable: myint
variable2 = None # type: myint
+def read(r: io.BytesIO) -> io.StringIO:
+ """docstring"""
+
+
def sum(x: myint, y: myint) -> myint:
"""docstring"""
return x + y
diff --git a/tests/roots/test-ext-autodoc/target/metadata.py b/tests/roots/test-ext-autodoc/target/metadata.py
new file mode 100644
index 000000000..7a4488f67
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/metadata.py
@@ -0,0 +1,2 @@
+def foo():
+ """:meta metadata-only-docstring:"""
diff --git a/tests/roots/test-ext-autodoc/target/module.py b/tests/roots/test-ext-autodoc/target/module.py
new file mode 100644
index 000000000..fe3b490a9
--- /dev/null
+++ b/tests/roots/test-ext-autodoc/target/module.py
@@ -0,0 +1,14 @@
+undocumented = 1
+
+#: docstring
+documented = 1
+
+undoc_annotated: int
+
+#: docstring
+annotated: int
+
+__special__ = 1
+
+#: docstring
+__documented_special__ = 1
diff --git a/tests/roots/test-ext-autodoc/target/singledispatch.py b/tests/roots/test-ext-autodoc/target/singledispatch.py
index 3fa81dcae..fca2b6683 100644
--- a/tests/roots/test-ext-autodoc/target/singledispatch.py
+++ b/tests/roots/test-ext-autodoc/target/singledispatch.py
@@ -15,6 +15,7 @@ def func(arg, kwarg=None):
@func.register(int)
+@func.register(float)
def _func_int(arg, kwarg=None):
"""A function for int."""
pass
diff --git a/tests/roots/test-ext-autodoc/target/singledispatchmethod.py b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py
index b5ccbb2f0..086c7fe66 100644
--- a/tests/roots/test-ext-autodoc/target/singledispatchmethod.py
+++ b/tests/roots/test-ext-autodoc/target/singledispatchmethod.py
@@ -10,6 +10,7 @@ class Foo:
pass
@meth.register(int)
+ @meth.register(float)
def _meth_int(self, arg, kwarg=None):
"""A method for int."""
pass
diff --git a/tests/roots/test-nitpicky-warnings/conf.py b/tests/roots/test-nitpicky-warnings/conf.py
new file mode 100644
index 000000000..2db221cc6
--- /dev/null
+++ b/tests/roots/test-nitpicky-warnings/conf.py
@@ -0,0 +1 @@
+nitpicky = True
diff --git a/tests/roots/test-nitpicky-warnings/index.rst b/tests/roots/test-nitpicky-warnings/index.rst
new file mode 100644
index 000000000..e73840d4f
--- /dev/null
+++ b/tests/roots/test-nitpicky-warnings/index.rst
@@ -0,0 +1,7 @@
+test-nitpicky-warnings
+======================
+
+:py:const:`prefix.anything.postfix`
+:py:class:`prefix.anything`
+:py:class:`anything.postfix`
+:js:class:`prefix.anything.postfix`
diff --git a/tests/test_config.py b/tests/test_config.py
index a48e7ce30..b9b4f612a 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -311,3 +311,77 @@ def test_check_enum_for_list_failed(logger):
config.init_values()
check_confval_types(None, config)
assert logger.warning.called
+
+
+nitpick_warnings = [
+ "WARNING: py:const reference target not found: prefix.anything.postfix",
+ "WARNING: py:class reference target not found: prefix.anything",
+ "WARNING: py:class reference target not found: anything.postfix",
+ "WARNING: js:class reference target not found: prefix.anything.postfix",
+]
+
+
+@pytest.mark.sphinx(testroot='nitpicky-warnings')
+def test_nitpick_base(app, status, warning):
+ app.builder.build_all()
+
+ warning = warning.getvalue().strip().split('\n')
+ assert len(warning) == len(nitpick_warnings)
+ for actual, expected in zip(warning, nitpick_warnings):
+ assert expected in actual
+
+
+@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
+ 'nitpick_ignore': [
+ ('py:const', 'prefix.anything.postfix'),
+ ('py:class', 'prefix.anything'),
+ ('py:class', 'anything.postfix'),
+ ('js:class', 'prefix.anything.postfix'),
+ ],
+})
+def test_nitpick_ignore(app, status, warning):
+ app.builder.build_all()
+ assert not len(warning.getvalue().strip())
+
+
+@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
+ 'nitpick_ignore_regex': [
+ (r'py:.*', r'.*postfix'),
+ (r'.*:class', r'prefix.*'),
+ ]
+})
+def test_nitpick_ignore_regex1(app, status, warning):
+ app.builder.build_all()
+ assert not len(warning.getvalue().strip())
+
+
+@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
+ 'nitpick_ignore_regex': [
+ (r'py:.*', r'prefix.*'),
+ (r'.*:class', r'.*postfix'),
+ ]
+})
+def test_nitpick_ignore_regex2(app, status, warning):
+ app.builder.build_all()
+ assert not len(warning.getvalue().strip())
+
+
+@pytest.mark.sphinx(testroot='nitpicky-warnings', confoverrides={
+ 'nitpick_ignore_regex': [
+ # None of these should match
+ (r'py:', r'.*'),
+ (r':class', r'.*'),
+ (r'', r'.*'),
+ (r'.*', r'anything'),
+ (r'.*', r'prefix'),
+ (r'.*', r'postfix'),
+ (r'.*', r''),
+ ]
+})
+def test_nitpick_ignore_regex_fullmatch(app, status, warning):
+ app.builder.build_all()
+
+ warning = warning.getvalue().strip().split('\n')
+ assert len(warning) == len(nitpick_warnings)
+ for actual, expected in zip(warning, nitpick_warnings):
+ assert expected in actual
diff --git a/tests/test_domain_std.py b/tests/test_domain_std.py
index 9d9e27bd0..011c82f6a 100644
--- a/tests/test_domain_std.py
+++ b/tests/test_domain_std.py
@@ -324,6 +324,23 @@ def test_cmdoption(app):
assert domain.progoptions[('ls', '-l')] == ('index', 'cmdoption-ls-l')
+def test_cmdoption_for_None(app):
+ text = (".. program:: ls\n"
+ ".. program:: None\n"
+ "\n"
+ ".. option:: -l\n")
+ domain = app.env.get_domain('std')
+ doctree = restructuredtext.parse(app, text)
+ assert_node(doctree, (addnodes.index,
+ [desc, ([desc_signature, ([desc_name, "-l"],
+ [desc_addname, ()])],
+ [desc_content, ()])]))
+ assert_node(doctree[0], addnodes.index,
+ entries=[('pair', 'command line option; -l', 'cmdoption-l', '', None)])
+ assert (None, '-l') in domain.progoptions
+ assert domain.progoptions[(None, '-l')] == ('index', 'cmdoption-l')
+
+
def test_multiple_cmdoptions(app):
text = (".. program:: cmd\n"
"\n"
diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py
index 2becf5de2..4c16886b3 100644
--- a/tests/test_ext_autodoc.py
+++ b/tests/test_ext_autodoc.py
@@ -736,6 +736,34 @@ def test_autodoc_undoc_members(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_autodoc_undoc_members_for_metadata_only(app):
+ # metadata only member is not displayed
+ options = {"members": None}
+ actual = do_autodoc(app, 'module', 'target.metadata', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.metadata',
+ '',
+ ]
+
+ # metadata only member is displayed when undoc-member given
+ options = {"members": None,
+ "undoc-members": None}
+ actual = do_autodoc(app, 'module', 'target.metadata', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.metadata',
+ '',
+ '',
+ '.. py:function:: foo()',
+ ' :module: target.metadata',
+ '',
+ ' :meta metadata-only-docstring:',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members(app):
options = {"members": None,
"inherited-members": None}
@@ -2080,6 +2108,7 @@ def test_singledispatch(app):
'',
'',
'.. py:function:: func(arg, kwarg=None)',
+ ' func(arg: float, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
@@ -2107,6 +2136,7 @@ def test_singledispatchmethod(app):
'',
'',
' .. py:method:: Foo.meth(arg, kwarg=None)',
+ ' Foo.meth(arg: float, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
@@ -2125,6 +2155,7 @@ def test_singledispatchmethod_automethod(app):
assert list(actual) == [
'',
'.. py:method:: Foo.meth(arg, kwarg=None)',
+ ' Foo.meth(arg: float, kwarg=None)',
' Foo.meth(arg: int, kwarg=None)',
' Foo.meth(arg: str, kwarg=None)',
' :module: target.singledispatchmethod',
diff --git a/tests/test_ext_autodoc_autoclass.py b/tests/test_ext_autodoc_autoclass.py
index d879f8e14..096dc9397 100644
--- a/tests/test_ext_autodoc_autoclass.py
+++ b/tests/test_ext_autodoc_autoclass.py
@@ -264,6 +264,53 @@ def test_show_inheritance_for_subclass_of_generic_type(app):
]
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_class_doc_from_class(app):
+ options = {"members": None,
+ "class-doc-from": "class"}
+ actual = do_autodoc(app, 'class', 'target.autoclass_content.C', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: C()',
+ ' :module: target.autoclass_content',
+ '',
+ ' A class having __init__, no __new__',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_class_doc_from_init(app):
+ options = {"members": None,
+ "class-doc-from": "init"}
+ actual = do_autodoc(app, 'class', 'target.autoclass_content.C', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: C()',
+ ' :module: target.autoclass_content',
+ '',
+ ' __init__ docstring',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_class_doc_from_both(app):
+ options = {"members": None,
+ "class-doc-from": "both"}
+ actual = do_autodoc(app, 'class', 'target.autoclass_content.C', options)
+ assert list(actual) == [
+ '',
+ '.. py:class:: C()',
+ ' :module: target.autoclass_content',
+ '',
+ ' A class having __init__, no __new__',
+ '',
+ ' __init__ docstring',
+ '',
+ ]
+
+
def test_class_alias(app):
def autodoc_process_docstring(*args):
"""A handler always raises an error.
diff --git a/tests/test_ext_autodoc_autofunction.py b/tests/test_ext_autodoc_autofunction.py
index 615091889..ca2429b5e 100644
--- a/tests/test_ext_autodoc_autofunction.py
+++ b/tests/test_ext_autodoc_autofunction.py
@@ -119,6 +119,7 @@ def test_singledispatch(app):
assert list(actual) == [
'',
'.. py:function:: func(arg, kwarg=None)',
+ ' func(arg: float, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py
index 3332704bb..59296a981 100644
--- a/tests/test_ext_autodoc_automodule.py
+++ b/tests/test_ext_autodoc_automodule.py
@@ -29,6 +29,95 @@ def test_empty_all(app):
]
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_automodule(app):
+ options = {'members': None}
+ actual = do_autodoc(app, 'module', 'target.module', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.module',
+ '',
+ '',
+ '.. py:data:: annotated',
+ ' :module: target.module',
+ ' :type: int',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:data:: documented',
+ ' :module: target.module',
+ ' :value: 1',
+ '',
+ ' docstring',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_automodule_undoc_members(app):
+ options = {'members': None,
+ 'undoc-members': None}
+ actual = do_autodoc(app, 'module', 'target.module', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.module',
+ '',
+ '',
+ '.. py:data:: annotated',
+ ' :module: target.module',
+ ' :type: int',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:data:: documented',
+ ' :module: target.module',
+ ' :value: 1',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:data:: undoc_annotated',
+ ' :module: target.module',
+ ' :type: int',
+ '',
+ ]
+
+
+@pytest.mark.sphinx('html', testroot='ext-autodoc')
+def test_automodule_special_members(app):
+ options = {'members': None,
+ 'special-members': None}
+ actual = do_autodoc(app, 'module', 'target.module', options)
+ assert list(actual) == [
+ '',
+ '.. py:module:: target.module',
+ '',
+ '',
+ '.. py:data:: __documented_special__',
+ ' :module: target.module',
+ ' :value: 1',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:data:: annotated',
+ ' :module: target.module',
+ ' :type: int',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:data:: documented',
+ ' :module: target.module',
+ ' :value: 1',
+ '',
+ ' docstring',
+ '',
+ ]
+
+
@pytest.mark.sphinx('html', testroot='ext-autodoc',
confoverrides={'autodoc_mock_imports': ['missing_module',
'missing_package1',
diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py
index bc8c01fbd..04d35e335 100644
--- a/tests/test_ext_autodoc_configs.py
+++ b/tests/test_ext_autodoc_configs.py
@@ -792,27 +792,27 @@ def test_autodoc_typehints_description_for_invalid_node(app):
def test_autodoc_type_aliases(app):
# default
options = {"members": None}
- actual = do_autodoc(app, 'module', 'target.annotations', options)
+ actual = do_autodoc(app, 'module', 'target.autodoc_type_aliases', options)
assert list(actual) == [
'',
- '.. py:module:: target.annotations',
+ '.. py:module:: target.autodoc_type_aliases',
'',
'',
'.. py:class:: Foo()',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
' .. py:attribute:: Foo.attr1',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: int',
'',
' docstring',
'',
'',
' .. py:attribute:: Foo.attr2',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: int',
'',
' docstring',
@@ -820,26 +820,32 @@ def test_autodoc_type_aliases(app):
'',
'.. py:function:: mult(x: int, y: int) -> int',
' mult(x: float, y: float) -> float',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:function:: read(r: _io.BytesIO) -> _io.StringIO',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: sum(x: int, y: int) -> int',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:data:: variable',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: int',
'',
' docstring',
'',
'',
'.. py:data:: variable2',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: int',
' :value: None',
'',
@@ -848,28 +854,29 @@ def test_autodoc_type_aliases(app):
]
# define aliases
- app.config.autodoc_type_aliases = {'myint': 'myint'}
- actual = do_autodoc(app, 'module', 'target.annotations', options)
+ app.config.autodoc_type_aliases = {'myint': 'myint',
+ 'io.StringIO': 'my.module.StringIO'}
+ actual = do_autodoc(app, 'module', 'target.autodoc_type_aliases', options)
assert list(actual) == [
'',
- '.. py:module:: target.annotations',
+ '.. py:module:: target.autodoc_type_aliases',
'',
'',
'.. py:class:: Foo()',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
' .. py:attribute:: Foo.attr1',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: myint',
'',
' docstring',
'',
'',
' .. py:attribute:: Foo.attr2',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: myint',
'',
' docstring',
@@ -877,26 +884,32 @@ def test_autodoc_type_aliases(app):
'',
'.. py:function:: mult(x: myint, y: myint) -> myint',
' mult(x: float, y: float) -> float',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
+ '',
+ ' docstring',
+ '',
+ '',
+ '.. py:function:: read(r: _io.BytesIO) -> my.module.StringIO',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:function:: sum(x: myint, y: myint) -> myint',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
'',
' docstring',
'',
'',
'.. py:data:: variable',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: myint',
'',
' docstring',
'',
'',
'.. py:data:: variable2',
- ' :module: target.annotations',
+ ' :module: target.autodoc_type_aliases',
' :type: myint',
' :value: None',
'',
@@ -911,10 +924,10 @@ def test_autodoc_type_aliases(app):
confoverrides={'autodoc_typehints': "description",
'autodoc_type_aliases': {'myint': 'myint'}})
def test_autodoc_typehints_description_and_type_aliases(app):
- (app.srcdir / 'annotations.rst').write_text('.. autofunction:: target.annotations.sum')
+ (app.srcdir / 'autodoc_type_aliases.rst').write_text('.. autofunction:: target.autodoc_type_aliases.sum')
app.build()
- context = (app.outdir / 'annotations.txt').read_text()
- assert ('target.annotations.sum(x, y)\n'
+ context = (app.outdir / 'autodoc_type_aliases.txt').read_text()
+ assert ('target.autodoc_type_aliases.sum(x, y)\n'
'\n'
' docstring\n'
'\n'
diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py
index 5d92b6afd..71868d492 100644
--- a/tests/test_ext_autosummary.py
+++ b/tests/test_ext_autosummary.py
@@ -161,6 +161,7 @@ def test_get_items_summary(make_app, app_params):
'emptyLine': "This is the real summary",
'module_attr': 'This is a module attribute',
'C.class_attr': 'This is a class attribute',
+ 'C.instance_attr': 'This is an instance attribute',
'C.prop_attr1': 'This is a function docstring',
'C.prop_attr2': 'This is a attribute docstring',
'C.C2': 'This is a nested inner class docstring',
@@ -329,6 +330,7 @@ def test_autosummary_generate(app, status, warning):
' ~Foo.CONSTANT3\n'
' ~Foo.CONSTANT4\n'
' ~Foo.baz\n'
+ ' ~Foo.value\n'
' \n' in Foo)
FooBar = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.Bar.rst').read_text()
diff --git a/tests/test_util_docstrings.py b/tests/test_util_docstrings.py
index 543feca2a..2d406b81c 100644
--- a/tests/test_util_docstrings.py
+++ b/tests/test_util_docstrings.py
@@ -8,31 +8,48 @@
:license: BSD, see LICENSE for details.
"""
-from sphinx.util.docstrings import extract_metadata, prepare_commentdoc, prepare_docstring
+from sphinx.util.docstrings import prepare_commentdoc, prepare_docstring, separate_metadata
-def test_extract_metadata():
- metadata = extract_metadata(":meta foo: bar\n"
- ":meta baz:\n")
+def test_separate_metadata():
+ # metadata only
+ text = (":meta foo: bar\n"
+ ":meta baz:\n")
+ docstring, metadata = separate_metadata(text)
+ assert docstring == ''
assert metadata == {'foo': 'bar', 'baz': ''}
+ # non metadata field list item
+ text = (":meta foo: bar\n"
+ ":param baz:\n")
+ docstring, metadata = separate_metadata(text)
+ assert docstring == ':param baz:\n'
+ assert metadata == {'foo': 'bar'}
+
# field_list like text following just after paragaph is not a field_list
- metadata = extract_metadata("blah blah blah\n"
- ":meta foo: bar\n"
- ":meta baz:\n")
+ text = ("blah blah blah\n"
+ ":meta foo: bar\n"
+ ":meta baz:\n")
+ docstring, metadata = separate_metadata(text)
+ assert docstring == text
assert metadata == {}
# field_list like text following after blank line is a field_list
- metadata = extract_metadata("blah blah blah\n"
- "\n"
- ":meta foo: bar\n"
- ":meta baz:\n")
+ text = ("blah blah blah\n"
+ "\n"
+ ":meta foo: bar\n"
+ ":meta baz:\n")
+ docstring, metadata = separate_metadata(text)
+ assert docstring == "blah blah blah\n\n"
assert metadata == {'foo': 'bar', 'baz': ''}
# non field_list item breaks field_list
- metadata = extract_metadata(":meta foo: bar\n"
- "blah blah blah\n"
- ":meta baz:\n")
+ text = (":meta foo: bar\n"
+ "blah blah blah\n"
+ ":meta baz:\n")
+ docstring, metadata = separate_metadata(text)
+ assert docstring == ("blah blah blah\n"
+ ":meta baz:\n")
assert metadata == {'foo': 'bar'}
diff --git a/tests/test_util_inspect.py b/tests/test_util_inspect.py
index 7b86c6ade..95e36ba14 100644
--- a/tests/test_util_inspect.py
+++ b/tests/test_util_inspect.py
@@ -19,7 +19,26 @@ import _testcapi
import pytest
from sphinx.util import inspect
-from sphinx.util.inspect import stringify_signature
+from sphinx.util.inspect import TypeAliasNamespace, stringify_signature
+
+
+def test_TypeAliasNamespace():
+ import logging.config
+ type_alias = TypeAliasNamespace({'logging.Filter': 'MyFilter',
+ 'logging.Handler': 'MyHandler',
+ 'logging.handlers.SyslogHandler': 'MySyslogHandler'})
+
+ assert type_alias['logging'].Filter == 'MyFilter'
+ assert type_alias['logging'].Handler == 'MyHandler'
+ assert type_alias['logging'].handlers.SyslogHandler == 'MySyslogHandler'
+ assert type_alias['logging'].Logger == logging.Logger
+ assert type_alias['logging'].config == logging.config
+
+ with pytest.raises(KeyError):
+ assert type_alias['log']
+
+ with pytest.raises(KeyError):
+ assert type_alias['unknown']
def test_signature():
@@ -183,10 +202,7 @@ def test_signature_annotations():
# Instance annotations
sig = inspect.signature(f11)
- if sys.version_info < (3, 10):
- assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
- else:
- assert stringify_signature(sig) == '(x: CustomAnnotation(), y: 123) -> None'
+ assert stringify_signature(sig) == '(x: CustomAnnotation, y: 123) -> None'
# tuple with more than two items
sig = inspect.signature(f12)