summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakeshi KOMIYA <i.tkomiya@gmail.com>2021-05-03 22:33:12 +0900
committerTakeshi KOMIYA <i.tkomiya@gmail.com>2021-05-03 22:33:12 +0900
commit3027a2f8675e6140f2d8b83d19bec4159a09af5c (patch)
treed4db4de45fe517f165425014dd1d90fc9bb6816e
parentf5e7b9b815977790f940ffcc374550bb70fc99d4 (diff)
parentf31af4b8158e6142d918366aa0026e40575af914 (diff)
downloadsphinx-git-3027a2f8675e6140f2d8b83d19bec4159a09af5c.tar.gz
Merge branch '4.x'
-rw-r--r--.github/workflows/main.yml2
-rw-r--r--CHANGES65
-rw-r--r--EXAMPLES2
-rw-r--r--doc/extdev/deprecated.rst5
-rw-r--r--doc/usage/extensions/autodoc.rst9
-rw-r--r--doc/usage/restructuredtext/domains.rst3
-rw-r--r--setup.py5
-rw-r--r--sphinx/builders/html/__init__.py6
-rw-r--r--sphinx/builders/linkcheck.py74
-rw-r--r--sphinx/ext/autodoc/__init__.py86
-rw-r--r--sphinx/ext/autodoc/directive.py2
-rw-r--r--sphinx/themes/basic/static/basic.css_t2
-rw-r--r--sphinx/util/docstrings.py23
-rw-r--r--sphinx/util/inspect.py87
-rw-r--r--sphinx/util/texescape.py2
-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/singledispatch.py1
-rw-r--r--tests/roots/test-ext-autodoc/target/singledispatchmethod.py1
-rw-r--r--tests/roots/test-root/conf.py2
-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_configs.py57
-rw-r--r--tests/test_ext_math.py16
-rw-r--r--tests/test_util_docstrings.py45
-rw-r--r--tests/test_util_inspect.py21
28 files changed, 467 insertions, 152 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f49117e1a..f32468576 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -21,7 +21,7 @@ jobs:
docutils: du16
- name: py39
python: 3.9
- docutils: du16
+ docutils: du17
coverage: "--cov ./ --cov-append --cov-config setup.cfg"
# - name: py310-dev
# python: 3.10-dev
diff --git a/CHANGES b/CHANGES
index 9e30c2497..c2522c6df 100644
--- a/CHANGES
+++ b/CHANGES
@@ -31,20 +31,34 @@ 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``
* #9129: html search: Show search summaries when html_copy_source = False
+* #9120: html theme: Eliminate prompt characters of code-block from copyable
+ text
* #9097: Optimize the paralell build
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.0 beta2 (in development)
+Release 4.0.0 beta3 (in development)
====================================
Dependencies
@@ -53,15 +67,37 @@ Dependencies
Incompatible changes
--------------------
-* #9023: Change the CSS classes on :rst:role:`cpp:expr` and
- :rst:role:`cpp:texpr`.
-
Deprecated
----------
Features added
--------------
+Bugs fixed
+----------
+
+Testing
+--------
+
+Release 4.0.0 beta2 (released Apr 29, 2021)
+===========================================
+
+Dependencies
+------------
+
+* Support docutils-0.17. Please notice it changes the output of HTML builder.
+ Some themes do not support it, and you need to update your custom CSS to
+ upgrade it.
+
+Incompatible changes
+--------------------
+
+* #9023: Change the CSS classes on :rst:role:`cpp:expr` and
+ :rst:role:`cpp:texpr`.
+
+Features added
+--------------
+
* #8818: autodoc: Super class having ``Any`` arguments causes nit-picky warning
* #9095: autodoc: TypeError is raised on processing broken metaclass
* #9110: autodoc: metadata of GenericAlias is not rendered as a reference in
@@ -81,9 +117,6 @@ Bugs fixed
* C, C++, fix ``KeyError`` when an ``alias`` directive is the first C/C++
directive in a file with another C/C++ directive later.
-Testing
---------
-
Release 4.0.0 beta1 (released Apr 12, 2021)
===========================================
@@ -222,24 +255,6 @@ Bugs fixed
Release 3.5.5 (in development)
==============================
-Dependencies
-------------
-
-Incompatible changes
---------------------
-
-Deprecated
-----------
-
-Features added
---------------
-
-Bugs fixed
-----------
-
-Testing
---------
-
Release 3.5.4 (released Apr 11, 2021)
=====================================
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/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst
index 9e17b9fb4..514a80541 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
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/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/setup.py b/setup.py
index 1c14ff1eb..7ce37f9ea 100644
--- a/setup.py
+++ b/setup.py
@@ -21,9 +21,10 @@ install_requires = [
'sphinxcontrib-htmlhelp',
'sphinxcontrib-serializinghtml',
'sphinxcontrib-qthelp',
- 'Jinja2>=2.3',
+ 'Jinja2>=2.3,<3.0',
+ 'MarkupSafe<2.0',
'Pygments>=2.0',
- 'docutils>=0.14,<0.17',
+ 'docutils>=0.14,<0.18',
'snowballstemmer>=1.1',
'babel>=1.3',
'alabaster>=0.7,<0.8',
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index a78d54a16..87b9c5c45 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -1166,7 +1166,11 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str,
else:
# str value (old styled)
attrs.append('src="%s"' % pathto(js, resource=True))
- return '<script %s>%s</script>' % (' '.join(attrs), body)
+
+ if attrs:
+ return '<script %s>%s</script>' % (' '.join(attrs), body)
+ else:
+ return '<script>%s</script>' % body
context['js_tag'] = js_tag
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/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py
index c92709deb..ff6475c94 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()
@@ -722,9 +730,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
@@ -1320,12 +1328,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 +1358,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 +1431,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 +1666,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 +1675,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 +1697,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 +1933,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
@@ -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):
@@ -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
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/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/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/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/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/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/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-root/conf.py b/tests/roots/test-root/conf.py
index 34cafa767..687445a70 100644
--- a/tests/roots/test-root/conf.py
+++ b/tests/roots/test-root/conf.py
@@ -42,7 +42,7 @@ latex_additional_files = ['svgimg.svg']
coverage_c_path = ['special/*.h']
coverage_c_regexes = {'function': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'}
-extlinks = {'issue': ('http://bugs.python.org/issue%s', 'issue '),
+extlinks = {'issue': ('http://bugs.python.org/issue%s', 'issue %s'),
'pyurl': ('http://python.org/%s', None)}
# modify tags from conf.py
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_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_math.py b/tests/test_ext_math.py
index bd124c8c6..ebe2c0f38 100644
--- a/tests/test_ext_math.py
+++ b/tests/test_ext_math.py
@@ -215,11 +215,23 @@ def test_math_compat(app, status, warning):
@pytest.mark.sphinx('html', testroot='ext-math',
confoverrides={'extensions': ['sphinx.ext.mathjax'],
- 'mathjax_config': {'extensions': ['tex2jax.js']}})
-def test_mathjax_config(app, status, warning):
+ 'mathjax3_config': {'extensions': ['tex2jax.js']}})
+def test_mathjax3_config(app, status, warning):
app.builder.build_all()
content = (app.outdir / 'index.html').read_text()
+ assert MATHJAX_URL in content
+ assert ('<script>window.MathJax = {"extensions": ["tex2jax.js"]}</script>' in content)
+
+
+@pytest.mark.sphinx('html', testroot='ext-math',
+ confoverrides={'extensions': ['sphinx.ext.mathjax'],
+ 'mathjax2_config': {'extensions': ['tex2jax.js']}})
+def test_mathjax2_config(app, status, warning):
+ app.builder.build_all()
+
+ content = (app.outdir / 'index.html').read_text()
+ assert MATHJAX_URL in content
assert ('<script type="text/x-mathjax-config">'
'MathJax.Hub.Config({"extensions": ["tex2jax.js"]})'
'</script>' in content)
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..fbf243ba1 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():