Repository: numpy/numpy

Issue:
numpy.deprecate indentation question
This code has two functions marked as deprecated with `numpy.deprecate`:

```
import numpy as np


@np.deprecate(message="foo() is no longer supported.")
def foo():
    """This is foo.

    It doesn't do anything.
    """
    return


@np.deprecate(message="bar() is no longer supported.")
def bar():
    """
    This is bar.

    It does the same thing as foo().
    """
    return
```
The docstrings of `foo` and `bar` end up like this:

```
In [5]: print(foo.__doc__)                                                        
`foo` is deprecated!
foo() is no longer supported.

    This is foo.

    It doesn't do anything.
    

In [6]: print(bar.__doc__)                                                        
`bar` is deprecated!
bar() is no longer supported.

    This is bar.

    It does the same thing as foo().
```
Note that the indentation of the added deprecation text doesn't match the indentation of the original docstring.  Is this intentional?  It messes up the HTML documentation; see, for example, the online documentation for [`scipy.stats.itemfreq`](https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.itemfreq.html).  That gray bar in front of what was originally the first line of the docstring ("Return a 2-D array...") shouldn't be there.

A related issue was reported in https://github.com/numpy/numpy/issues/8058, which was closed by https://github.com/numpy/numpy/pull/8131.


Discussion:
I would say it is not intended. If we are going to touch this, the modified decorator should accept additional arguments and insert its message after the first line so that the docstring looks like
```
This is bar.

.. deprecated:: 1.19
  bar() is no longer supported.

It does the same thing as foo().
```

This might be tricky to dedent if the `message` is longer than 78 characters

Resolving patch:
diff --git a/numpy/lib/utils.py b/numpy/lib/utils.py
--- a/numpy/lib/utils.py
+++ b/numpy/lib/utils.py
@@ -1,5 +1,6 @@
 import os
 import sys
+import textwrap
 import types
 import re
 import warnings
@@ -117,6 +118,7 @@ def newfunc(*args,**kwds):
                         break
                     skip += len(line) + 1
                 doc = doc[skip:]
+            depdoc = textwrap.indent(depdoc, ' ' * indent)
             doc = '\n\n'.join([depdoc, doc])
         newfunc.__doc__ = doc
         try:


Test patch:
diff --git a/numpy/lib/tests/test_utils.py b/numpy/lib/tests/test_utils.py
--- a/numpy/lib/tests/test_utils.py
+++ b/numpy/lib/tests/test_utils.py
@@ -49,7 +49,7 @@ def old_func5(self, x):
         Bizarre indentation.
     """
     return x
-new_func5 = deprecate(old_func5)
+new_func5 = deprecate(old_func5, message="This function is\ndeprecated.")
 
 
 def old_func6(self, x):
@@ -74,10 +74,20 @@ def test_deprecate_fn():
 
 
 @pytest.mark.skipif(sys.flags.optimize == 2, reason="-OO discards docstrings")
-def test_deprecate_help_indentation():
-    _compare_docs(old_func4, new_func4)
-    _compare_docs(old_func5, new_func5)
-    _compare_docs(old_func6, new_func6)
+@pytest.mark.parametrize('old_func, new_func', [
+    (old_func4, new_func4),
+    (old_func5, new_func5),
+    (old_func6, new_func6),
+])
+def test_deprecate_help_indentation(old_func, new_func):
+    _compare_docs(old_func, new_func)
+    # Ensure we don't mess up the indentation
+    for knd, func in (('old', old_func), ('new', new_func)):
+        for li, line in enumerate(func.__doc__.split('\n')):
+            if li == 0:
+                assert line.startswith('    ') or not line.startswith(' '), knd
+            elif line:
+                assert line.startswith('    '), knd
 
 
 def _compare_docs(old_func, new_func):