From 54a584c22b2c06e5aab78c7ee0e93498be177537 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Mon, 10 Dec 2018 23:11:58 +0000 Subject: [PATCH 1/4] Fix docstring of functions created with the deprecate() function --- pandas/tests/util/test_deprecate.py | 50 +++++++++++++++++++++++++++++ pandas/util/_decorators.py | 41 +++++++++++++---------- 2 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 pandas/tests/util/test_deprecate.py diff --git a/pandas/tests/util/test_deprecate.py b/pandas/tests/util/test_deprecate.py new file mode 100644 index 0000000000000..e893d5a54c431 --- /dev/null +++ b/pandas/tests/util/test_deprecate.py @@ -0,0 +1,50 @@ +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 1234 + + +def new_func_no_docstring(): + pass + + +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 == 1234 + + assert depr_func.__doc__ == dedent(new_func_with_deprecation.__doc__) + + +def test_deprecate_no_docstring(): + with pytest.raises(ValueError, match='deprecate needs a correctly ' + 'formatted docstring'): + deprecate('depr_func', new_func_no_docstring, '1.0', + msg='Use new_func instead.') diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 46d6a3cdf4a8d..20b470f2efec0 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -1,4 +1,4 @@ -from functools import WRAPPER_ASSIGNMENTS, update_wrapper, wraps +from functools import wraps import inspect from textwrap import dedent, wrap import warnings @@ -39,26 +39,33 @@ 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)') + + if not alternative.__doc__ or alternative.__doc__.count('\n') < 3: + raise ValueError(doc_error_msg) + empty1, summary, empty2, doc = alternative.__doc__.split('\n', 3) + if empty1 or empty2 and not summary: + raise ValueError(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 From 24c20eccafebe9a535991521242f39a6e1389db5 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Mon, 10 Dec 2018 23:14:29 +0000 Subject: [PATCH 2/4] Removing unused import --- pandas/util/_decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/util/_decorators.py b/pandas/util/_decorators.py index 20b470f2efec0..03f774aa10a9f 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -1,6 +1,6 @@ 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 From cc291128a46bfe17d996c56efbf24b8f7c8357e0 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Tue, 11 Dec 2018 16:08:19 +0000 Subject: [PATCH 3/4] Allowing deprecate to be used with functions without docstring --- pandas/tests/util/test_deprecate.py | 26 ++++++++++++++------ pandas/util/_decorators.py | 38 ++++++++++++++++------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/pandas/tests/util/test_deprecate.py b/pandas/tests/util/test_deprecate.py index e893d5a54c431..a8563c97ad8c9 100644 --- a/pandas/tests/util/test_deprecate.py +++ b/pandas/tests/util/test_deprecate.py @@ -12,11 +12,16 @@ def new_func(): This is the extended summary. The deprecate directive goes before this. """ - return 1234 + return 'new_func called' def new_func_no_docstring(): - pass + 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(): @@ -38,13 +43,20 @@ def test_deprecate_ok(): with tm.assert_produces_warning(FutureWarning): result = depr_func() - assert result == 1234 - + assert result == 'new_func called' assert depr_func.__doc__ == dedent(new_func_with_deprecation.__doc__) def test_deprecate_no_docstring(): - with pytest.raises(ValueError, match='deprecate needs a correctly ' - 'formatted docstring'): - deprecate('depr_func', new_func_no_docstring, '1.0', + 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 03f774aa10a9f..86cd8b1e698c6 100644 --- a/pandas/util/_decorators.py +++ b/pandas/util/_decorators.py @@ -49,23 +49,27 @@ def wrapper(*args, **kwargs): 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)') - - if not alternative.__doc__ or alternative.__doc__.count('\n') < 3: - raise ValueError(doc_error_msg) - empty1, summary, empty2, doc = alternative.__doc__.split('\n', 3) - if empty1 or empty2 and not summary: - raise ValueError(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)) + '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 From 7315e63090ed5452c32360e834275fd5332a4a67 Mon Sep 17 00:00:00 2001 From: Marc Garcia Date: Tue, 11 Dec 2018 17:43:21 +0000 Subject: [PATCH 4/4] Making isort happy with the import ordering and spacing (and making myself less happy) --- pandas/tests/util/test_deprecate.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/tests/util/test_deprecate.py b/pandas/tests/util/test_deprecate.py index a8563c97ad8c9..7fa7989eff690 100644 --- a/pandas/tests/util/test_deprecate.py +++ b/pandas/tests/util/test_deprecate.py @@ -3,6 +3,7 @@ import pytest from pandas.util._decorators import deprecate + import pandas.util.testing as tm