diff --git a/CHANGES b/CHANGES index 56fc6b11d..4786391c3 100644 --- a/CHANGES +++ b/CHANGES @@ -280,6 +280,9 @@ Release 1.4.10 (in development) Bugs fixed ---------- +* #3015: fix a broken test on Windows. +* #1843: Fix documentation of descriptor classes that have a custom metaclass. + Thanks to Erik Bray. Release 1.4.9 (released Nov 23, 2016) ===================================== diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 59e585678..c17b754e7 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -42,6 +42,11 @@ try: except ImportError: typing = None +# This type isn't exposed directly in any modules, but can be found +# here in most Python versions +MethodDescriptorType = type(type.__subclasses__) + + #: extended signature RE: with explicit module name separated by :: py_ext_sig_re = re.compile( r'''^ ([\w.]+::)? # explicit module name @@ -1479,10 +1484,13 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): @classmethod def can_document_member(cls, member, membername, isattr, parent): + non_attr_types = cls.method_types + (type, MethodDescriptorType) isdatadesc = isdescriptor(member) and not \ - isinstance(member, cls.method_types) and not \ - type(member).__name__ in ("type", "method_descriptor", - "instancemethod") + isinstance(member, non_attr_types) and not \ + type(member).__name__ == "instancemethod" + # That last condition addresses an obscure case of C-defined + # methods using a deprecated type in Python 3, that is not otherwise + # exported anywhere by Python return isdatadesc or (not isinstance(parent, ModuleDocumenter) and not inspect.isroutine(member) and not isinstance(member, class_types)) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index d2ba95608..8df943ec7 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -15,7 +15,7 @@ from util import TestApp, Struct, raises, SkipTest # NOQA from nose.tools import with_setup, eq_ import enum -from six import StringIO +from six import StringIO, add_metaclass from docutils.statemachine import ViewList from sphinx.ext.autodoc import AutoDirective, add_documenter, \ @@ -847,11 +847,13 @@ def test_generate(): del directive.env.temp_data['autodoc:module'] # test descriptor class documentation - options.members = ['CustomDataDescriptor'] + options.members = ['CustomDataDescriptor', 'CustomDataDescriptor2'] assert_result_contains('.. py:class:: CustomDataDescriptor(doc)', 'module', 'test_autodoc') assert_result_contains(' .. py:method:: CustomDataDescriptor.meth()', 'module', 'test_autodoc') + assert_result_contains('.. py:class:: CustomDataDescriptor2(doc)', + 'module', 'test_autodoc') # test mocked module imports options.members = ['TestAutodoc'] @@ -894,6 +896,14 @@ class CustomDataDescriptor(object): return "The Answer" +class CustomDataDescriptorMeta(type): + """Descriptor metaclass docstring.""" + +@add_metaclass(CustomDataDescriptorMeta) +class CustomDataDescriptor2(CustomDataDescriptor): + """Descriptor class with custom metaclass docstring.""" + + def _funky_classmethod(name, b, c, d, docstring=None): """Generates a classmethod for a class from a template by filling out some arguments.""" diff --git a/tests/test_build.py b/tests/test_build.py index cc34de2c1..d61291a2f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -18,7 +18,7 @@ from textwrap import dedent from sphinx.errors import SphinxError import sphinx.builders.linkcheck -from util import with_app, with_tempdir, rootdir, tempdir, SkipTest, TestApp +from util import with_app, with_tempdir, rootdir, tempdir, SkipTest, TestApp, path try: from docutils.writers.manpage import Writer as ManWriter @@ -149,16 +149,17 @@ def test_image_glob(app, status, warning): doctree = pickle.loads((app.doctreedir / 'subdir/index.doctree').bytes()) assert isinstance(doctree[0][1], nodes.image) - assert doctree[0][1]['candidates'] == {'*': 'subdir/rimg.png'} - assert doctree[0][1]['uri'] == 'subdir/rimg.png' + sub = path('subdir') + assert doctree[0][1]['candidates'] == {'*': sub / 'rimg.png'} + assert doctree[0][1]['uri'] == sub / 'rimg.png' assert isinstance(doctree[0][2], nodes.image) assert doctree[0][2]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.svg'} - assert doctree[0][2]['uri'] == 'subdir/svgimg.*' + assert doctree[0][2]['uri'] == sub / 'svgimg.*' assert isinstance(doctree[0][3], nodes.figure) assert isinstance(doctree[0][3][0], nodes.image) assert doctree[0][3][0]['candidates'] == {'application/pdf': 'subdir/svgimg.pdf', 'image/svg+xml': 'subdir/svgimg.svg'} - assert doctree[0][3][0]['uri'] == 'subdir/svgimg.*' + assert doctree[0][3][0]['uri'] == sub / 'svgimg.*'