Repository: pandas-dev/pandas

Issue:
DOC: Validate the Returns section in the docstrings
`scripts/validate_docstrings.py` validates that the content of a docstring follows our standards. There are still some of those standards that the script does not validate, and this gives the users the wrong impression that the docstring is all right, when it's not really the case. All the missing validations are listed in #20298.

The format of the `Returns` section consists of a first line with just the type, and afterwards indented the description. In the case a tuple is returned, then the format is `name : type or types` for each parameter, and indented descriptions after them. The descriptions need to start with a capital letter and finish with a period. See: https://pandas.pydata.org/pandas-docs/stable/contributing_docstring.html#section-4-returns-or-yields

For this issue is required:
- Change `scripts/validate_docstings.py` to give errors if some of the described formats are not satisfied.
- Add tests in `scripts/tests/test_validate_docstrings.py`


Discussion:
would like to try taking this on!

few questions:

1. how can i use `test_validate_docstrings.py` to create bad docstrings and use it in the `validate_docstrings` script?
2. how can i know the structure of `doc.returns` or `doc.yields`?
To run the tests you should simply call (in a pandas dev environment, and in the project root): `python -m pytest scripts` if I'm not wrong.

If you want to know the value of variables in the `validate_docstrings.py` script, you can run it, and add `print(whatever)` in a line where it contains the value. You can run the script like `./scripts/validate_docstrings.py pandas.DataFrame.head` to run just one docstring and make it easier. Is this what you're asking?
thanks for the quick response! so technically, the docstring for pandas.DataFrame.head should fail? here's the `Returns` section:
```
Returns
-------
obj_head : same type as caller
    The first `n` rows of the caller object.
```
And according to https://pandas.pydata.org/pandas-docs/stable/contributing_docstring.html#section-4-returns-or-yields 

>  But in this case, no name will be provided, unless the method returns or yields more than one value (a tuple of values).

So the `Returns` section should not provide a name for its return value?
In this case it should fail because there is a single value being returned, and it contains the name `obj_head`.

The correct cases are:
```
Returns
--------
int
    The single integer value that the function returns.
```

```
Returns
--------
id : int
    The function returns a tuple, and the first value is an integer with the id.
name : str
    The second value of the tuple is a string with the name.
```

Resolving patch:
diff --git a/scripts/validate_docstrings.py b/scripts/validate_docstrings.py
--- a/scripts/validate_docstrings.py
+++ b/scripts/validate_docstrings.py
@@ -105,6 +105,11 @@
     'PR10': 'Parameter "{param_name}" requires a space before the colon '
             'separating the parameter name and type',
     'RT01': 'No Returns section found',
+    'RT02': 'The first line of the Returns section should contain only the '
+            'type, unless multiple values are being returned',
+    'RT03': 'Return value has no description',
+    'RT04': 'Return value description should start with a capital letter',
+    'RT05': 'Return value description should finish with "."',
     'YD01': 'No Yields section found',
     'SA01': 'See Also section not found',
     'SA02': 'Missing period at end of description for See Also '
@@ -685,8 +690,22 @@ def get_validation_data(doc):
                 errs.append(error('PR09', param_name=param))
 
     if doc.is_function_or_method:
-        if not doc.returns and 'return' in doc.method_source:
-            errs.append(error('RT01'))
+        if not doc.returns:
+            if 'return' in doc.method_source:
+                errs.append(error('RT01'))
+        else:
+            if len(doc.returns) == 1 and doc.returns[0][1]:
+                errs.append(error('RT02'))
+            for name_or_type, type_, desc in doc.returns:
+                if not desc:
+                    errs.append(error('RT03'))
+                else:
+                    desc = ' '.join(desc)
+                    if not desc[0].isupper():
+                        errs.append(error('RT04'))
+                    if not desc.endswith('.'):
+                        errs.append(error('RT05'))
+
         if not doc.yields and 'yield' in doc.method_source:
             errs.append(error('YD01'))
 


Test patch:
diff --git a/scripts/tests/test_validate_docstrings.py b/scripts/tests/test_validate_docstrings.py
--- a/scripts/tests/test_validate_docstrings.py
+++ b/scripts/tests/test_validate_docstrings.py
@@ -633,6 +633,43 @@ def no_punctuation(self):
         """
         return "Hello world!"
 
+    def named_single_return(self):
+        """
+        Provides name but returns only one value.
+
+        Returns
+        -------
+        s : str
+           A nice greeting.
+        """
+        return "Hello world!"
+
+    def no_capitalization(self):
+        """
+        Forgets capitalization in return values description.
+
+        Returns
+        -------
+        foo : str
+           The first returned string.
+        bar : str
+           the second returned string.
+        """
+        return "Hello", "World!"
+
+    def no_period_multi(self):
+        """
+        Forgets period in return values description.
+
+        Returns
+        -------
+        foo : str
+           The first returned string
+        bar : str
+           The second returned string.
+        """
+        return "Hello", "World!"
+
 
 class BadSeeAlso(object):
 
@@ -829,10 +866,18 @@ def test_bad_generic_functions(self, capsys, func):
         ('BadReturns', 'yield_not_documented', ('No Yields section found',)),
         pytest.param('BadReturns', 'no_type', ('foo',),
                      marks=pytest.mark.xfail),
-        pytest.param('BadReturns', 'no_description', ('foo',),
-                     marks=pytest.mark.xfail),
-        pytest.param('BadReturns', 'no_punctuation', ('foo',),
-                     marks=pytest.mark.xfail),
+        ('BadReturns', 'no_description',
+         ('Return value has no description',)),
+        ('BadReturns', 'no_punctuation',
+         ('Return value description should finish with "."',)),
+        ('BadReturns', 'named_single_return',
+         ('The first line of the Returns section should contain only the '
+          'type, unless multiple values are being returned',)),
+        ('BadReturns', 'no_capitalization',
+         ('Return value description should start with a capital '
+          'letter',)),
+        ('BadReturns', 'no_period_multi',
+         ('Return value description should finish with "."',)),
         # Examples tests
         ('BadGenericDocStrings', 'method',
          ('Do not import numpy, as it is imported automatically',)),