Skip to content
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

MAINT: Changed class constructor __init__ GL08 reporting #592

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 2 additions & 1 deletion doc/format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,8 @@ Class docstring
Use the same sections as outlined above (all except :ref:`Returns <returns>`
are applicable). The constructor (``__init__``) should also be documented
here, the :ref:`Parameters <params>` section of the docstring details the
constructor's parameters.
constructor's parameters. The class docstring does not need to be repeated
in a constructor, but is optional.

An **Attributes** section, located below the :ref:`Parameters <params>`
section, may be used to describe non-method attributes of the class::
Expand Down
3 changes: 3 additions & 0 deletions doc/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,9 @@ inline comments:
def __init__(self): # numpydoc ignore=GL08
pass

Note, if the :ref:`class <classdoc>` docstring properly documents the
constructor, the ``GL08`` will be ignored by default.

This is supported by the :ref:`CLI <validation_via_cli>`,
:ref:`pre-commit hook <pre_commit_hook>`, and
:ref:`Sphinx extension <validation_during_sphinx_build>`.
Expand Down
112 changes: 112 additions & 0 deletions numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,90 @@ def missing_whitespace_after_comma(self):
"""


class GoodConstructorInclusion:
"""
Class to test optional constructor docstring inclusion.

As the class docstring can define the constructor, a check should raise GL08 if a constructor docstring is defined.

Parameters
----------
param1 : int
Description of param1.

See Also
--------
otherclass : A class that does something else.

Examples
--------
This is an example of how to use GoodConstructorInclusion.
"""

def __init__(self, param1: int) -> None:
"""
Constructor docstring with additional information.

Extended information.

Parameters
----------
param1 : int
Description of param1 with extra details.

See Also
--------
otherclass : A class that does something else.

Examples
--------
This is an example of how to use GoodConstructorInclusion.
"""


class GoodConstructorExclusion:
"""
Class to test optional constructor docstring exclusion.

As the class docstring can define the constructor, a check should not raise GL08 if no constructor docstring is defined.

Parameters
----------
param1 : int
Description of param1.

See Also
--------
otherclass : A class that does something else.

Examples
--------
This is an example of how to use GoodConstructorExclusion.
"""

def __init__(self, param1: int) -> None:
pass


class BadConstructorExclusion:
"""
Class to test undocumented constructor docstring.

Unnecessary extended summary.

See Also
--------
otherclass : A class that does something else.

Examples
--------
This is an example of how to use BadConstructorExclusion.
"""

def __init__(self, param1: int):
pass


class TestValidator:
def _import_path(self, klass=None, func=None):
"""
Expand Down Expand Up @@ -1536,6 +1620,34 @@ def test_bad_docstrings(self, capsys, klass, func, msgs):
for msg in msgs:
assert msg in " ".join(err[1] for err in result["errors"])

@pytest.mark.parametrize(
"klass,exp_init_codes,exc_init_codes,exp_klass_codes",
[
("GoodConstructorExclusion", tuple(), ("GL08",), tuple()),
("GoodConstructorInclusion", tuple(), ("GL08",), tuple()),
(
"BadConstructorExclusion",
("GL08",),
tuple(),
("PR01"), # Parameter not documented in class constructor
),
],
)
def test_constructor_docstrings(
self, klass, exp_init_codes, exc_init_codes, exp_klass_codes
):
# First test the class docstring itself, checking expected_klass_codes match
result = validate_one(self._import_path(klass=klass))
for err in result["errors"]:
assert err[0] in exp_klass_codes

# Then test the constructor docstring
result = validate_one(self._import_path(klass=klass, func="__init__"))
for code in exp_init_codes:
assert code in " ".join(err[0] for err in result["errors"])
for code in exc_init_codes:
assert code not in " ".join(err[0] for err in result["errors"])


def decorator(x):
"""Test decorator."""
Expand Down
18 changes: 17 additions & 1 deletion numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,7 +633,23 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):

errs = []
if not doc.raw_doc:
if "GL08" not in ignore_validation_comments:
report_GL08: bool = True
# Check if the object is a class and has a docstring in the constructor
if doc.name.endswith("__init__") and doc.is_function_or_method:
cls_name = doc.code_obj.__qualname__.split(".")[0]
cls = getattr(importlib.import_module(doc.code_obj.__module__), cls_name)
cls_doc = Validator(get_doc_object(cls))

# Parameter_mismatches, PR01, PR02, PR03 are checked for the class docstring.
# If cls_doc has PR01, PR02, PR03 errors, i.e. invalid class docstring,
# then we also report missing constructor docstring, GL08.
report_GL08 = len(cls_doc.parameter_mismatches) > 0

# Check if GL08 is to be ignored:
if "GL08" in ignore_validation_comments:
report_GL08 = False
# Add GL08 error?
if report_GL08:
errs.append(error("GL08"))
return {
"type": doc.type,
Expand Down