Skip to content

Commit

Permalink
Fix #33: Change the class :class:~deprecated.sphinx.SphinxAdapter: …
Browse files Browse the repository at this point in the history
…add the ``line_length`` keyword argument to the constructor to specify the max line length of the directive text. Sphinx decorators also accept the ``line_length`` argument.
  • Loading branch information
tantale committed Dec 18, 2020
1 parent 1d1d7f0 commit 2357422
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Fix

- Fix packit configuration: use ``upstream_tag_template: v{version}``.

- Fix #33: Change the class :class:`~deprecated.sphinx.SphinxAdapter`:
add the ``line_length`` keyword argument to the constructor to specify the max line length of the directive text.
Sphinx decorators also accept the ``line_length`` argument.

- Fix #34: ``versionadded`` and ``versionchanged`` decorators don't emit ``DeprecationWarning``
anymore on decorated classes.

Expand Down
78 changes: 63 additions & 15 deletions deprecated/sphinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
Of course, the ``@deprecated`` decorator will emit a deprecation warning
when the function/method is called or the class is constructed.
"""
import re
import textwrap

import wrapt
Expand All @@ -40,7 +41,15 @@ class SphinxAdapter(ClassicAdapter):
- The reason message is obviously added in the directive block if not empty.
"""

def __init__(self, directive, reason="", version="", action=None, category=DeprecationWarning):
def __init__(
self,
directive,
reason="",
version="",
action=None,
category=DeprecationWarning,
line_length=70,
):
"""
Construct a wrapper adapter.
Expand Down Expand Up @@ -70,8 +79,13 @@ def __init__(self, directive, reason="", version="", action=None, category=Depre
The warning category to use for the deprecation warning.
By default, the category class is :class:`~DeprecationWarning`,
you can inherit this class to define your own deprecation warning category.
:type line_length: int
:param line_length:
Max line length of the directive text. If non nul, a long text is wrapped in several lines.
"""
self.directive = directive
self.line_length = line_length
super(SphinxAdapter, self).__init__(reason=reason, version=version, action=action, category=category)

def __call__(self, wrapped):
Expand All @@ -82,26 +96,39 @@ def __call__(self, wrapped):
:return: the decorated class or function.
"""
# -- build the directive division
fmt = ".. {directive}:: {version}" if self.version else ".. {directive}::"
div_lines = [fmt.format(directive=self.directive, version=self.version)]
width = self.line_length - 3 if self.line_length > 3 else 2 ** 16
reason = textwrap.dedent(self.reason).strip()
reason = '\n'.join(
textwrap.fill(line, width=70, initial_indent=' ', subsequent_indent=' ') for line in reason.splitlines()
).strip()
for paragraph in reason.splitlines():
if paragraph:
div_lines.extend(
textwrap.fill(
paragraph,
width=width,
initial_indent=" ",
subsequent_indent=" ",
).splitlines()
)
else:
div_lines.append("")

# -- get the docstring, normalize the trailing newlines
docstring = textwrap.dedent(wrapped.__doc__ or "")
if docstring:
docstring += "\n\n"
if self.version:
docstring += ".. {directive}:: {version}\n".format(directive=self.directive, version=self.version)
else:
docstring += ".. {directive}::\n".format(directive=self.directive)
if reason:
docstring += " {reason}\n".format(reason=reason)
docstring = re.sub(r"\n*$", "\n\n", docstring, flags=re.DOTALL)

# -- append the directive division to the docstring
docstring += "".join("{}\n".format(line) for line in div_lines)

wrapped.__doc__ = docstring
if self.directive in {"versionadded", "versionchanged"}:
return wrapped
return super(SphinxAdapter, self).__call__(wrapped)


def versionadded(reason="", version=""):
def versionadded(reason="", version="", line_length=70):
"""
This decorator can be used to insert a "versionadded" directive
in your function/class docstring in order to documents the
Expand All @@ -116,9 +143,18 @@ def versionadded(reason="", version=""):
the version number has the format "MAJOR.MINOR.PATCH", and,
in the case of a new functionality, the "PATCH" component should be "0".
:type line_length: int
:param line_length:
Max line length of the directive text. If non nul, a long text is wrapped in several lines.
:return: the decorated function.
"""
adapter = SphinxAdapter('versionadded', reason=reason, version=version)
adapter = SphinxAdapter(
'versionadded',
reason=reason,
version=version,
line_length=line_length,
)

# noinspection PyUnusedLocal
@wrapt.decorator(adapter=adapter)
Expand All @@ -128,7 +164,7 @@ def wrapper(wrapped, instance, args, kwargs):
return wrapper


def versionchanged(reason="", version=""):
def versionchanged(reason="", version="", line_length=70):
"""
This decorator can be used to insert a "versionchanged" directive
in your function/class docstring in order to documents the
Expand All @@ -142,9 +178,18 @@ def versionchanged(reason="", version=""):
If you follow the `Semantic Versioning <https://semver.org/>`_,
the version number has the format "MAJOR.MINOR.PATCH".
:type line_length: int
:param line_length:
Max line length of the directive text. If non nul, a long text is wrapped in several lines.
:return: the decorated function.
"""
adapter = SphinxAdapter('versionchanged', reason=reason, version=version)
adapter = SphinxAdapter(
'versionchanged',
reason=reason,
version=version,
line_length=line_length,
)

# noinspection PyUnusedLocal
@wrapt.decorator(adapter=adapter)
Expand Down Expand Up @@ -180,6 +225,9 @@ def deprecated(*args, **kwargs):
By default, the category class is :class:`~DeprecationWarning`,
you can inherit this class to define your own deprecation warning category.
- "line_length":
Max line length of the directive text. If non nul, a long text is wrapped in several lines.
:return: the decorated function.
"""
directive = kwargs.pop('directive', 'deprecated')
Expand Down
128 changes: 128 additions & 0 deletions tests/test_sphinx_adapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# coding: utf-8
import textwrap

import pytest

from deprecated.sphinx import SphinxAdapter
from deprecated.sphinx import deprecated
from deprecated.sphinx import versionadded
from deprecated.sphinx import versionchanged


@pytest.mark.parametrize(
"line_length, expected",
[
(
50,
textwrap.dedent(
"""
Description of foo
:return: nothing
.. {directive}:: 1.2.3
foo has changed in this version
bar bar bar bar bar bar bar bar bar bar bar
bar bar bar bar bar bar bar bar bar bar bar
bar
"""
),
),
(
0,
textwrap.dedent(
"""
Description of foo
:return: nothing
.. {directive}:: 1.2.3
foo has changed in this version
bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
"""
),
),
],
ids=["wrapped", "long"],
)
@pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"])
def test_sphinx_adapter(directive, line_length, expected):
lines = [
"foo has changed in this version",
"", # newline
"bar " * 23, # long line
"", # trailing newline
]
reason = "\n".join(lines)
adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=line_length)

def foo():
"""
Description of foo
:return: nothing
"""

wrapped = adapter.__call__(foo)
expected = expected.format(directive=directive)
assert wrapped.__doc__ == expected


@pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"])
def test_sphinx_adapter__empty_docstring(directive):
lines = [
"foo has changed in this version",
"", # newline
"bar " * 23, # long line
"", # trailing newline
]
reason = "\n".join(lines)
adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=50)

def foo():
pass

wrapped = adapter.__call__(foo)
expected = textwrap.dedent(
"""\
.. {directive}:: 1.2.3
foo has changed in this version
bar bar bar bar bar bar bar bar bar bar bar
bar bar bar bar bar bar bar bar bar bar bar
bar
"""
)
expected = expected.format(directive=directive)
assert wrapped.__doc__ == expected


@pytest.mark.parametrize(
"decorator_factory, directive",
[
(versionadded, "versionadded"),
(versionchanged, "versionchanged"),
(deprecated, "deprecated"),
],
)
def test_decorator_accept_line_length(decorator_factory, directive):
reason = "bar " * 30
decorator = decorator_factory(reason=reason, version="1.2.3", line_length=50)

def foo():
pass

foo = decorator(foo)

expected = textwrap.dedent(
"""\
.. {directive}:: 1.2.3
bar bar bar bar bar bar bar bar bar bar bar
bar bar bar bar bar bar bar bar bar bar bar
bar bar bar bar bar bar bar bar
"""
)
expected = expected.format(directive=directive)
assert foo.__doc__ == expected

0 comments on commit 2357422

Please sign in to comment.