diff --git a/pandas/tests/util/test_deprecate.py b/pandas/tests/util/test_deprecate.py new file mode 100644 index 0000000000000..7fa7989eff690 --- /dev/null +++ b/pandas/tests/util/test_deprecate.py @@ -0,0 +1,63 @@ +from textwrap import dedent + +import pytest + +from pandas.util._decorators import deprecate + +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.') diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 46d6a3cdf4a8d..86cd8b1e698c6 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -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 @@ -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