Merge branch '2.0'

This commit is contained in:
jfbu 2019-04-23 14:48:17 +02:00
commit 65ac90773d
14 changed files with 253 additions and 86 deletions

View File

@ -67,6 +67,9 @@ Deprecated
* ``sphinx.ext.autodoc.importer.MockLoader``
* ``sphinx.ext.autodoc.importer.mock()``
* ``sphinx.ext.autosummary.autolink_role()``
* ``sphinx.ext.imgmath.DOC_BODY``
* ``sphinx.ext.imgmath.DOC_BODY_PREVIEW``
* ``sphinx.ext.imgmath.DOC_HEAD``
* ``sphinx.transforms.CitationReferences``
* ``sphinx.transforms.SmartQuotesSkipper``
* ``sphinx.util.docfields.DocFieldTransformer.preprocess_fieldtypes()``
@ -94,9 +97,13 @@ Features added
variables (refs: #6232, #6303)
* #6287: autodoc: Unable to document bound instance methods exported as module
functions
* #6289: autodoc: :confval:`autodoc_default_options` now supports
``imported-members`` option
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
imported members on autosummary
* #6271: ``make clean`` is catastrophically broken if building into '.'
* Add ``:classmethod:`` and ``:staticmethod:`` options to :rst:dir:`py:method`
directive
Bugs fixed
----------

View File

@ -177,6 +177,21 @@ The following is a list of deprecated interfaces.
- 4.0
- ``sphinx.ext.autosummary.AutoLink``
* - ``sphinx.ext.imgmath.DOC_BODY``
- 2.1
- 4.0
- N/A
* - ``sphinx.ext.imgmath.DOC_BODY_PREVIEW``
- 2.1
- 4.0
- N/A
* - ``sphinx.ext.imgmath.DOC_HEAD``
- 2.1
- 4.0
- N/A
* - ``sphinx.transforms.CitationReferences``
- 2.1
- 4.0

View File

@ -387,14 +387,17 @@ 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'`` and
``'exclude-members'``.
``'inherited-members'``, ``'show-inheritance'``, ``'ignore-module-all'``,
``'imported-members'`` and ``'exclude-members'``.
.. versionadded:: 1.8
.. versionchanged:: 2.0
Accepts ``True`` as a value.
.. versionchanged:: 2.1
Added ``'imported-members'``.
.. confval:: autodoc_docstring_signature
Functions imported from C modules cannot be introspected, and therefore the

View File

@ -216,6 +216,13 @@ The following directives are provided for module and class contents:
described for ``function``. See also :ref:`signatures` and
:ref:`info-field-lists`.
The ``classmethod`` option and ``staticmethod`` option can be given (with
no value) to indicate the method is a class method (or a static method).
.. versionchanged:: 2.1
``:classmethod:`` and ``:staticmethod:`` options added.
.. rst:directive:: .. py:staticmethod:: name(parameters)
Like :rst:dir:`py:method`, but indicates that the method is a static method.

View File

@ -562,10 +562,25 @@ class PyClassmember(PyObject):
class PyMethod(PyObject):
"""Description of a method."""
option_spec = PyObject.option_spec.copy()
option_spec.update({
'classmethod': directives.flag,
'staticmethod': directives.flag,
})
def needs_arglist(self):
# type: () -> bool
return True
def get_signature_prefix(self, sig):
# type: (str) -> str
if 'staticmethod' in self.options:
return 'static '
elif 'classmethod' in self.options:
return 'classmethod '
else:
return ''
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
name, cls = name_cls
@ -579,53 +594,38 @@ class PyMethod(PyObject):
else:
return '%s()' % name
return _('%s() (%s method)') % (methname, clsname)
if 'staticmethod' in self.options:
return _('%s() (%s static method)') % (methname, clsname)
elif 'classmethod' in self.options:
return _('%s() (%s class method)') % (methname, clsname)
else:
return _('%s() (%s method)') % (methname, clsname)
class PyClassMethod(PyMethod):
"""Description of a classmethod."""
def get_signature_prefix(self, sig):
# type: (str) -> str
return 'classmethod '
option_spec = PyObject.option_spec.copy()
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
name, cls = name_cls
try:
clsname, methname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
clsname = '.'.join([modname, clsname])
except ValueError:
if modname:
return _('%s() (in module %s)') % (name, modname)
else:
return '%s()' % name
def run(self):
# type: () -> List[nodes.Node]
self.name = 'py:method'
self.options['classmethod'] = True
return _('%s() (%s class method)') % (methname, clsname)
return super().run()
class PyStaticMethod(PyMethod):
"""Description of a staticmethod."""
def get_signature_prefix(self, sig):
# type: (str) -> str
return 'static '
option_spec = PyObject.option_spec.copy()
def get_index_text(self, modname, name_cls):
# type: (str, Tuple[str, str]) -> str
name, cls = name_cls
try:
clsname, methname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
clsname = '.'.join([modname, clsname])
except ValueError:
if modname:
return _('%s() (in module %s)') % (name, modname)
else:
return '%s()' % name
def run(self):
# type: () -> List[nodes.Node]
self.name = 'py:method'
self.options['staticmethod'] = True
return _('%s() (%s static method)') % (methname, clsname)
return super().run()
class PyAttribute(PyObject):

View File

@ -57,10 +57,6 @@ class ReSTMarkup(ObjectDescription):
def get_index_text(self, objectname, name):
# type: (str, str) -> str
if self.objtype == 'directive':
return _('%s (directive)') % name
elif self.objtype == 'role':
return _('%s (role)') % name
return ''
@ -98,6 +94,10 @@ class ReSTDirective(ReSTMarkup):
signode += addnodes.desc_addname(args, args)
return name
def get_index_text(self, objectname, name):
# type: (str, str) -> str
return _('%s (directive)') % name
class ReSTRole(ReSTMarkup):
"""
@ -108,6 +108,10 @@ class ReSTRole(ReSTMarkup):
signode += addnodes.desc_name(':%s:' % sig, ':%s:' % sig)
return sig
def get_index_text(self, objectname, name):
# type: (str, str) -> str
return _('%s (role)') % name
class ReSTDomain(Domain):
"""ReStructuredText domain."""

View File

@ -1269,6 +1269,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
Specialized Documenter subclass for methods (normal, static and class).
"""
objtype = 'method'
directivetype = 'method'
member_order = 50
priority = 1 # must be more than FunctionDocumenter
@ -1289,16 +1290,11 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
if obj is None:
obj = self.object
if inspect.isclassmethod(obj):
self.directivetype = 'classmethod'
if (inspect.isclassmethod(obj) or
inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name)):
# document class and static members before ordinary ones
self.member_order = self.member_order - 1
elif inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
self.directivetype = 'staticmethod'
# document class and static members before ordinary ones
self.member_order = self.member_order - 1
else:
self.directivetype = 'method'
return ret
def format_args(self):
@ -1314,6 +1310,17 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
args = args.replace('\\', '\\\\')
return args
def add_directive_header(self, sig):
# type: (str) -> None
super().add_directive_header(sig)
sourcename = self.get_sourcename()
obj = self.parent.__dict__.get(self.object_name, self.object)
if inspect.isclassmethod(obj):
self.add_line(' :classmethod:', sourcename)
elif inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name):
self.add_line(' :staticmethod:', sourcename)
def document_members(self, all_members=False):
# type: (bool) -> None
pass

View File

@ -30,7 +30,8 @@ logger = logging.getLogger(__name__)
# common option names for autodoc directives
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
'show-inheritance', 'private-members', 'special-members',
'ignore-module-all', 'exclude-members', 'member-order']
'ignore-module-all', 'exclude-members', 'member-order',
'imported-members']
class DummyOptionSpec(dict):

View File

@ -21,12 +21,15 @@ from subprocess import CalledProcessError, PIPE
from docutils import nodes
import sphinx
from sphinx import package_dir
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.errors import SphinxError
from sphinx.locale import _, __
from sphinx.util import logging
from sphinx.util.math import get_node_equation_number, wrap_displaymath
from sphinx.util.osutil import ensuredir
from sphinx.util.png import read_png_depth, write_png_depth
from sphinx.util.template import LaTeXRenderer
if False:
# For type annotation
@ -38,6 +41,8 @@ if False:
logger = logging.getLogger(__name__)
templates_path = path.join(package_dir, 'templates', 'imgmath')
class MathExtError(SphinxError):
category = 'Math extension error'
@ -87,19 +92,27 @@ DOC_BODY_PREVIEW = r'''
depth_re = re.compile(br'\[\d+ depth=(-?\d+)\]')
def generate_latex_macro(math, config):
# type: (str, Config) -> str
def generate_latex_macro(math, config, confdir=''):
# type: (str, Config, str) -> str
"""Generate LaTeX macro."""
fontsize = config.imgmath_font_size
baselineskip = int(round(fontsize * 1.2))
variables = {
'fontsize': config.imgmath_font_size,
'baselineskip': int(round(config.imgmath_font_size * 1.2)),
'preamble': config.imgmath_latex_preamble,
'math': math
}
latex = DOC_HEAD + config.imgmath_latex_preamble
if config.imgmath_use_preview:
latex += DOC_BODY_PREVIEW % (fontsize, baselineskip, math)
template_name = 'preview.tex_t'
else:
latex += DOC_BODY % (fontsize, baselineskip, math)
template_name = 'template.tex_t'
return latex
for template_dir in config.templates_path:
template = path.join(confdir, template_dir, template_name)
if path.exists(template):
return LaTeXRenderer().render(template, variables)
return LaTeXRenderer(templates_path).render(template_name, variables)
def ensure_tempdir(builder):
@ -220,7 +233,7 @@ def render_math(self, math):
if image_format not in SUPPORT_FORMAT:
raise MathExtError('imgmath_image_format must be either "png" or "svg"')
latex = generate_latex_macro(math, self.builder.config)
latex = generate_latex_macro(math, self.builder.config, self.builder.confdir)
filename = "%s.%s" % (sha1(latex.encode()).hexdigest(), image_format)
relfn = posixpath.join(self.builder.imgpath, 'math', filename)
@ -332,6 +345,15 @@ def html_visit_displaymath(self, node):
raise nodes.SkipNode
deprecated_alias('sphinx.ext.imgmath',
{
'DOC_BODY': DOC_BODY,
'DOC_BODY_PREVIEW': DOC_BODY_PREVIEW,
'DOC_HEAD': DOC_HEAD,
},
RemovedInSphinx40Warning)
def setup(app):
# type: (Sphinx) -> Dict[str, Any]
app.add_html_math_renderer('imgmath',

View File

@ -0,0 +1,18 @@
\documentclass[12pt]{article}
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{anyfontsize}
\usepackage{bm}
\pagestyle{empty}
<%= preamble %>
\usepackage[active]{preview}
\begin{document}
\begin{preview}
\fontsize{<%= fontsize %>}{<%= baselineskip %}}\selectfont <%= math %>
\end{preview}
\end{document}

View File

@ -0,0 +1,14 @@
\documentclass[12pt]{article}
\usepackage[utf8x]{inputenc}
\usepackage{amsmath}
\usepackage{amsthm}
\usepackage{amssymb}
\usepackage{amsfonts}
\usepackage{anyfontsize}
\usepackage{bm}
\pagestyle{empty}
<%= preamble %>
\begin{document}
\fontsize{<%= fontsize %>}{<%= baselineskip %>}\selectfont <%= math %>
\end{document}

View File

@ -67,9 +67,10 @@ class SphinxRenderer(FileRenderer):
class LaTeXRenderer(SphinxRenderer):
def __init__(self):
# type: () -> None
template_path = os.path.join(package_dir, 'templates', 'latex')
def __init__(self, template_path=None):
# type: (str) -> None
if template_path is None:
template_path = os.path.join(package_dir, 'templates', 'latex')
super().__init__(template_path)
# use texescape as escape filter

View File

@ -702,9 +702,9 @@ def test_autodoc_members(app):
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:classmethod:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedmeth()',
' .. py:staticmethod:: Base.inheritedstaticmeth(cls)'
' .. py:method:: Base.inheritedstaticmeth(cls)'
]
# default specific-members
@ -713,7 +713,7 @@ def test_autodoc_members(app):
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:method:: Base.inheritedmeth()',
' .. py:staticmethod:: Base.inheritedstaticmeth(cls)'
' .. py:method:: Base.inheritedstaticmeth(cls)'
]
@ -724,7 +724,7 @@ def test_autodoc_exclude_members(app):
actual = do_autodoc(app, 'class', 'target.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
' .. py:classmethod:: Base.inheritedclassmeth()'
' .. py:method:: Base.inheritedclassmeth()'
]
# members vs exclude-members
@ -752,9 +752,9 @@ def test_autodoc_undoc_members(app):
' .. py:attribute:: Class.inst_attr_string',
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
' .. py:attribute:: Class.udocattr',
@ -769,11 +769,11 @@ def test_autodoc_inherited_members(app):
actual = do_autodoc(app, 'class', 'target.Class', options)
assert list(filter(lambda l: 'method::' in l, actual)) == [
' .. py:method:: Class.excludemeth()',
' .. py:classmethod:: Class.inheritedclassmeth()',
' .. py:method:: Class.inheritedclassmeth()',
' .. py:method:: Class.inheritedmeth()',
' .. py:staticmethod:: Class.inheritedstaticmeth(cls)',
' .. py:method:: Class.inheritedstaticmeth(cls)',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.skipmeth()'
]
@ -832,9 +832,9 @@ def test_autodoc_special_members(app):
' .. py:attribute:: Class.inst_attr_string',
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
' .. py:attribute:: Class.udocattr',
@ -952,6 +952,34 @@ def test_autodoc_inner_class(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_classmethod(app):
actual = do_autodoc(app, 'method', 'target.Base.inheritedclassmeth')
assert list(actual) == [
'',
'.. py:method:: Base.inheritedclassmeth()',
' :module: target',
' :classmethod:',
'',
' Inherited class method.',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_staticmethod(app):
actual = do_autodoc(app, 'method', 'target.Base.inheritedstaticmeth')
assert list(actual) == [
'',
'.. py:method:: Base.inheritedstaticmeth(cls)',
' :module: target',
' :staticmethod:',
'',
' Inherited static method.',
' '
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_descriptor(app):
actual = do_autodoc(app, 'attribute', 'target.Class.descr')
@ -1001,8 +1029,8 @@ def test_autodoc_member_order(app):
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr',
' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.inst_attr_inline',
' .. py:attribute:: Class.inst_attr_comment',
' .. py:attribute:: Class.inst_attr_string',
@ -1019,8 +1047,8 @@ def test_autodoc_member_order(app):
'.. py:class:: Class(arg)',
' .. py:method:: Class.excludemeth()',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.skipmeth()',
' .. py:method:: Class.undocmeth()',
' .. py:attribute:: Class._private_inst_attr',
@ -1053,9 +1081,9 @@ def test_autodoc_member_order(app):
' .. py:attribute:: Class.inst_attr_string',
' .. py:attribute:: Class.mdocattr',
' .. py:method:: Class.meth()',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.prop',
' .. py:classmethod:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)',
' .. py:attribute:: Class.skipattr',
' .. py:method:: Class.skipmeth()',
' .. py:attribute:: Class.udocattr',
@ -1557,6 +1585,8 @@ def test_autodoc_default_options(app):
assert ' .. py:attribute:: EnumCls.val4' not in actual
actual = do_autodoc(app, 'class', 'target.CustomIter')
assert ' .. py:method:: target.CustomIter' not in actual
actual = do_autodoc(app, 'module', 'target')
assert '.. py:function:: save_traceback(app)' not in actual
# with :members:
app.config.autodoc_default_options = {'members': None}
@ -1620,6 +1650,15 @@ def test_autodoc_default_options(app):
assert ' .. py:method:: CustomIter.snafucate()' in actual
assert ' Makes this snafucated.' in actual
# with :imported-members:
app.config.autodoc_default_options = {
'members': None,
'imported-members': None,
'ignore-module-all': None,
}
actual = do_autodoc(app, 'module', 'target')
assert '.. py:function:: save_traceback(app)' in actual
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_default_options_with_values(app):
@ -1648,7 +1687,7 @@ def test_autodoc_default_options_with_values(app):
' .. py:attribute:: Class.docattr',
' .. py:attribute:: Class.udocattr',
' .. py:attribute:: Class.mdocattr',
' .. py:classmethod:: Class.moore(a, e, f) -> happiness',
' .. py:method:: Class.moore(a, e, f) -> happiness',
' .. py:attribute:: Class.inst_attr_inline',
' .. py:attribute:: Class.inst_attr_comment',
' .. py:attribute:: Class.inst_attr_string',

View File

@ -315,25 +315,54 @@ def test_pyfunction(app):
assert domain.objects['func'] == ('index', 'function')
def test_pymethod(app):
def test_pymethod_options(app):
text = (".. py:class:: Class\n"
"\n"
" .. py:method:: meth\n")
" .. py:method:: meth1\n"
" .. py:method:: meth2\n"
" :classmethod:\n"
" .. py:method:: meth3\n"
" :staticmethod:\n")
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc,
addnodes.index,
desc,
addnodes.index,
desc)])]))
# method
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'meth() (Class method)', 'Class.meth', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth"],
entries=[('single', 'meth1() (Class method)', 'Class.meth1', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_name, "meth1"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'method')
assert 'Class.meth1' in domain.objects
assert domain.objects['Class.meth1'] == ('index', 'method')
# :classmethod:
assert_node(doctree[1][1][2], addnodes.index,
entries=[('single', 'meth2() (Class class method)', 'Class.meth2', '', None)])
assert_node(doctree[1][1][3], ([desc_signature, ([desc_annotation, "classmethod "],
[desc_name, "meth2"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth2' in domain.objects
assert domain.objects['Class.meth2'] == ('index', 'method')
# :staticmethod:
assert_node(doctree[1][1][4], addnodes.index,
entries=[('single', 'meth3() (Class static method)', 'Class.meth3', '', None)])
assert_node(doctree[1][1][5], ([desc_signature, ([desc_annotation, "static "],
[desc_name, "meth3"],
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth3' in domain.objects
assert domain.objects['Class.meth3'] == ('index', 'method')
def test_pyclassmethod(app):
@ -354,7 +383,7 @@ def test_pyclassmethod(app):
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'classmethod')
assert domain.objects['Class.meth'] == ('index', 'method')
def test_pystaticmethod(app):
@ -375,7 +404,7 @@ def test_pystaticmethod(app):
[desc_parameterlist, ()])],
[desc_content, ()]))
assert 'Class.meth' in domain.objects
assert domain.objects['Class.meth'] == ('index', 'staticmethod')
assert domain.objects['Class.meth'] == ('index', 'method')
def test_pyattribute(app):