Skip to content

Commit

Permalink
Bugfix release 5.1.2 (#498)
Browse files Browse the repository at this point in the history
* Avoid multiline annotations in Traits documenter.

* Regression test.

* Use a temporary source directory.

* Flake8: remove unused variable

* Fix import order, docstrings

* Another import order fix.

* Update changelog, bump version.

* Fix release date.

* Improve description.
  • Loading branch information
mdickinson authored Jul 8, 2019
1 parent e2fe1b9 commit e62cb0a
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 19 deletions.
11 changes: 11 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
Traits CHANGELOG
================

Release 5.1.2
-------------

Released: 2019-07-08

Fixes

* Traits documenter no longer generates bad reST for traits whose definition
spans multiple source lines. (#494)


Release 5.1.1
-------------

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

MAJOR = 5
MINOR = 1
MICRO = 1
MICRO = 2

IS_RELEASED = True

Expand Down
125 changes: 107 additions & 18 deletions traits/util/tests/test_trait_documenter.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,66 @@
# -*- coding: utf-8 -*-
""" Tests for the trait documenter. """


import contextlib
import io
import os
import shutil
import tempfile
import textwrap
import tokenize
import unittest
try:
# Python 3: mock in the standard library.
import unittest.mock as mock
except ImportError:
# Python 2: need to use 3rd-party mock.
import mock

import six
if six.PY2:
import mock
else:
import unittest.mock as mock

try:
import sphinx # noqa: F401
except ImportError:
sphinx_available = False
else:
sphinx_available = True

def _sphinx_present():
try:
import sphinx # noqa
except ImportError:
return False
from traits.api import HasTraits, Int

return True
if sphinx_available:
from sphinx.ext.autodoc import Options
from sphinx.ext.autodoc.directive import DocumenterBridge
from sphinx.testing.path import path
from sphinx.testing.util import SphinxTestApp
from sphinx.util.docutils import LoggingReporter

from traits.util.trait_documenter import (
_get_definition_tokens,
TraitDocumenter,
)

@unittest.skipIf(
not _sphinx_present(), "Sphinx not available. Cannot test documenter"
skip_unless_sphinx_present = unittest.skipUnless(
sphinx_available,
"Sphinx is not available. Cannot test documenter.",
)


class MyTestClass(HasTraits):
"""
Class-level docstring.
"""
#: I'm a troublesome trait with a long definition.
bar = Int(42, desc=""" First line
The answer to
Life,
the Universe,
and Everything.
""")


@skip_unless_sphinx_present
class TestTraitDocumenter(unittest.TestCase):
""" Tests for the trait documenter. """

Expand All @@ -38,9 +74,6 @@ def setUp(self):
self.tokens = tokens

def test_get_definition_tokens(self):

from traits.util.trait_documenter import _get_definition_tokens

src = textwrap.dedent(
"""\
depth_interval = Property(Tuple(Float, Float),
Expand All @@ -59,8 +92,6 @@ def test_get_definition_tokens(self):

def test_add_line(self):

from traits.util.trait_documenter import TraitDocumenter

src = textwrap.dedent(
"""\
class Fake(HasTraits):
Expand All @@ -87,3 +118,61 @@ class Fake(HasTraits):

self.assertEqual(
len(documenter.directive.result.append.mock_calls), 1)

def test_abbreviated_annotations(self):
# Regression test for enthought/traits#493.
with self.create_test_directive() as directive:
documenter = TraitDocumenter(
directive, __name__ + ".MyTestClass.bar")
documenter.generate(all_members=True)
result = directive.result

# Find annotations line.
for item in result:
if item.lstrip().startswith(":annotation:"):
break
else:
self.fail("Didn't find the expected trait :annotation:")

# Annotation should be a single line.
self.assertIn("First line", item)
self.assertNotIn("\n", item)

@contextlib.contextmanager
def create_test_directive(self):
"""
Helper function to create a a "directive" suitable
for instantiating the TraitDocumenter with, along with resources
to support that directive, and clean up the resources afterwards.
Returns
-------
contextmanager
A context manager that returns a DocumenterBridge instance.
"""
with self.tmpdir() as tmpdir:
# The configuration file must exist, but it's okay if it's empty.
conf_file = os.path.join(tmpdir, "conf.py")
with io.open(conf_file, "w", encoding="utf-8"):
pass

app = SphinxTestApp(srcdir=path(tmpdir))
app.builder.env.app = app
app.builder.env.temp_data["docname"] = "dummy"
yield DocumenterBridge(app.env, LoggingReporter(''), Options(), 1)

@contextlib.contextmanager
def tmpdir(self):
"""
Helper function to create a temporary directory.
Returns
-------
contextmanager
Context manager that returns the path to a temporary directory.
"""
tmpdir = tempfile.mkdtemp()
try:
yield tmpdir
finally:
shutil.rmtree(tmpdir)
6 changes: 6 additions & 0 deletions traits/util/trait_documenter.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ def add_directive_header(self, sig):
"""
ClassLevelDocumenter.add_directive_header(self, sig)
definition = self._get_trait_definition()

# Workaround for enthought/traits#493: if the definition is multiline,
# throw away all lines after the first.
if "\n" in definition:
definition = definition.partition("\n")[0] + u" …"

self.add_line(u" :annotation: = {0}".format(definition), "<autodoc>")

# Private Interface #####################################################
Expand Down

0 comments on commit e62cb0a

Please sign in to comment.