Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 82 additions & 1 deletion traits/util/tests/test_trait_documenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
else:
from pathlib import Path

no_index_entry = sphinx.version_info >= (8, 2)
no_index = 'noindex' if sphinx.version_info < (7, 2) else 'no-index'


# Configuration file content for testing.
CONF_PY = """\
Expand Down Expand Up @@ -97,6 +100,12 @@ def not_a_trait(self):
"""


class MySubClass(MyTestClass):

#: A new attribute.
foo = Bool(True)


@requires_sphinx
class TestTraitDocumenter(unittest.TestCase):
""" Tests for the trait documenter. """
Expand Down Expand Up @@ -128,7 +137,6 @@ def test_get_definition_tokens(self):
self.assertEqual(src.rstrip(), string)

def test_add_line(self):

mocked_directive = mock.MagicMock()

documenter = TraitDocumenter(mocked_directive, "test", " ")
Expand Down Expand Up @@ -209,6 +217,79 @@ def test_can_document_member(self):
)
)

def test_class(self):
# given
documenter = TraitDocumenter(mock.Mock(), 'test')
documenter.parent = MyTestClass
documenter.object_name = 'bar'
documenter.modname = 'traits.util.tests.test_trait_documenter'
documenter.get_sourcename = mock.Mock(return_value='<autodoc>')
documenter.objpath = ['MyTestClass', 'bar']
documenter.add_line = mock.Mock()

# when
documenter.add_directive_header('')

# then
self.assertEqual(documenter.directive.warn.call_args_list, [])
expected = [
('.. py:attribute:: MyTestClass.bar', '<autodoc>'),
(f' :{no_index}:', '<autodoc>'),
(' :module: traits.util.tests.test_trait_documenter', '<autodoc>'), # noqa
(' :annotation: = Int(42, desc=""" First line …', '<autodoc>')] # noqa
if no_index_entry:
expected.insert(2, (' :no-index-entry:', '<autodoc>'))
calls = documenter.add_line.call_args_list
for index, line in enumerate(expected):
self.assertEqual(calls[index][0], line)

def test_subclass(self):
# given
documenter = TraitDocumenter(mock.Mock(), 'test')
documenter.object_name = 'bar'
documenter.objpath = ['MySubClass', 'bar']
documenter.parent = MySubClass
documenter.modname = 'traits.util.tests.test_trait_documenter'
documenter.get_sourcename = mock.Mock(return_value='<autodoc>')
documenter.add_line = mock.Mock()

# when
documenter.add_directive_header('')

# then
self.assertEqual(documenter.directive.warn.call_args_list, [])
expected = [
('.. py:attribute:: MySubClass.bar', '<autodoc>'),
(f' :{no_index}:', '<autodoc>'),
(' :module: traits.util.tests.test_trait_documenter', '<autodoc>'), # noqa
(' :annotation: = Int(42, desc=""" First line …', '<autodoc>')] # noqa
if no_index_entry:
expected.insert(2, (' :no-index-entry:', '<autodoc>'))
calls = documenter.add_line.call_args_list
for index, line in enumerate(expected):
self.assertEqual(calls[index][0], line)

# given
documenter.object_name = 'foo'
documenter.objpath = ['MySubClass', 'foo']
documenter.add_line = mock.Mock()

# when
documenter.add_directive_header('')

# then
self.assertEqual(documenter.directive.warn.call_args_list, [])
expected = [
('.. py:attribute:: MySubClass.foo', '<autodoc>'),
(f' :{no_index}:', '<autodoc>'),
(' :module: traits.util.tests.test_trait_documenter', '<autodoc>'), # noqa
(' :annotation: = Bool(True)', '<autodoc>')] # noqa
if no_index_entry:
expected.insert(2, (' :no-index-entry:', '<autodoc>'))
calls = documenter.add_line.call_args_list
for index, line in enumerate(expected):
self.assertEqual(calls[index][0], line)

@contextlib.contextmanager
def create_directive(self):
"""
Expand Down
24 changes: 15 additions & 9 deletions traits/util/trait_documenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from importlib import import_module
import inspect
import io
import types
import token
import tokenize
import traceback
Expand Down Expand Up @@ -114,17 +115,22 @@ def add_directive_header(self, sig):

"""
ClassLevelDocumenter.add_directive_header(self, sig)
try:
definition = trait_definition(
cls=self.parent,
trait_name=self.object_name,
)
except ValueError:
# Without this, a failure to find the trait definition aborts
# the whole documentation build.
# Look into the class and parent classes:
parent = self.parent
classes = list(types.resolve_bases(parent.__bases__))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice - I didn't know about types.resolve_bases.

classes.insert(0, parent)
for cls in classes:
try:
definition = trait_definition(
cls=cls, trait_name=self.object_name)
except ValueError:
continue
else:
break
else:
logger.warning(
"No definition for the trait {!r} could be found in "
"class {!r}.".format(self.object_name, self.parent),
"class {!r}.".format(self.object_name, parent),
exc_info=True)
return

Expand Down
Loading