Skip to content

Fix docstring of functions created with the deprecate() function #24215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 13, 2018
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
63 changes: 63 additions & 0 deletions pandas/tests/util/test_deprecate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from textwrap import dedent

import pytest

from pandas.util._decorators import deprecate
Copy link
Contributor

Choose a reason for hiding this comment

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

are these inplace of tests_deprecate_kwarg.py? (this is pretty new)

Copy link
Member Author

Choose a reason for hiding this comment

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

No, they are different. We have an old (I guess) deprecate that is used to deprecate argmax (and argmin) with deprecate('argmax', idmax).

The function was not tested, and I had to modify it, because it was injecting the .. deprecate:: directive in a way that the docstring format was wrong.


import pandas.util.testing as tm


def new_func():
"""
This is the summary. The deprecate directive goes next.

This is the extended summary. The deprecate directive goes before this.
"""
return 'new_func called'


def new_func_no_docstring():
return 'new_func_no_docstring called'


def new_func_wrong_docstring():
"""Summary should be in the next line."""
return 'new_func_wrong_docstring called'


def new_func_with_deprecation():
"""
This is the summary. The deprecate directive goes next.

.. deprecated:: 1.0
Use new_func instead.

This is the extended summary. The deprecate directive goes before this.
"""
pass


def test_deprecate_ok():
depr_func = deprecate('depr_func', new_func, '1.0',
msg='Use new_func instead.')

with tm.assert_produces_warning(FutureWarning):
result = depr_func()

assert result == 'new_func called'
assert depr_func.__doc__ == dedent(new_func_with_deprecation.__doc__)


def test_deprecate_no_docstring():
depr_func = deprecate('depr_func', new_func_no_docstring, '1.0',
msg='Use new_func instead.')
with tm.assert_produces_warning(FutureWarning):
result = depr_func()
assert result == 'new_func_no_docstring called'


def test_deprecate_wrong_docstring():
with pytest.raises(AssertionError, match='deprecate needs a correctly '
'formatted docstring'):
deprecate('depr_func', new_func_wrong_docstring, '1.0',
msg='Use new_func instead.')
47 changes: 29 additions & 18 deletions pandas/util/_decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from functools import WRAPPER_ASSIGNMENTS, update_wrapper, wraps
from functools import wraps
import inspect
from textwrap import dedent, wrap
from textwrap import dedent
import warnings

from pandas._libs.properties import cache_readonly # noqa
Expand Down Expand Up @@ -39,26 +39,37 @@ def deprecate(name, alternative, version, alt_name=None,
warning_msg = msg or '{} is deprecated, use {} instead'.format(name,
alt_name)

# adding deprecated directive to the docstring
msg = msg or 'Use `{alt_name}` instead.'.format(alt_name=alt_name)
msg = '\n '.join(wrap(msg, 70))

@Substitution(version=version, msg=msg)
@Appender(alternative.__doc__)
@wraps(alternative)
def wrapper(*args, **kwargs):
"""
.. deprecated:: %(version)s

%(msg)s

"""
warnings.warn(warning_msg, klass, stacklevel=stacklevel)
return alternative(*args, **kwargs)

# Since we are using Substitution to create the required docstring,
# remove that from the attributes that should be assigned to the wrapper
assignments = tuple(x for x in WRAPPER_ASSIGNMENTS if x != '__doc__')
update_wrapper(wrapper, alternative, assigned=assignments)
# adding deprecated directive to the docstring
msg = msg or 'Use `{alt_name}` instead.'.format(alt_name=alt_name)
doc_error_msg = ('deprecate needs a correctly formatted docstring in '
'the target function (should have a one liner short '
'summary, and opening quotes should be in their own '
'line). Found:\n{}'.format(alternative.__doc__))

# when python is running in optimized mode (i.e. `-OO`), docstrings are
# removed, so we check that a docstring with correct formatting is used
# but we allow empty docstrings
if alternative.__doc__:
if alternative.__doc__.count('\n') < 3:
raise AssertionError(doc_error_msg)
empty1, summary, empty2, doc = alternative.__doc__.split('\n', 3)
if empty1 or empty2 and not summary:
raise AssertionError(doc_error_msg)
wrapper.__doc__ = dedent("""
{summary}

.. deprecated:: {depr_version}
{depr_msg}

{rest_of_docstring}""").format(summary=summary.strip(),
depr_version=version,
depr_msg=msg,
rest_of_docstring=dedent(doc))

return wrapper

Expand Down