Skip to content

Commit

Permalink
MAINT: Changed class constructor __init__ GL08 reporting (#592)
Browse files Browse the repository at this point in the history
Co-authored-by: Stefan van der Walt <sjvdwalt@gmail.com>
Co-authored-by: Eric Larson <larson.eric.d@gmail.com>
  • Loading branch information
3 people authored Jan 10, 2025
1 parent 06cd4a7 commit 6d5eb42
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 2 deletions.
4 changes: 3 additions & 1 deletion doc/format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,9 @@ 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. While repetition is unnecessary, a docstring for
the class constructor (``__init__``) can, optionally, be added to provide
detailed initialization documentation.

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 that a properly formatted :ref:`class <classdoc>` docstring
silences ``G08`` for an ``__init__`` constructor without a docstring.

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
141 changes: 141 additions & 0 deletions numpydoc/tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,113 @@ def missing_whitespace_after_comma(self):
"""


class ConstructorDocumentedInClassAndInit:
"""
Class to test constructor documented via class and constructor docstrings.
A case where both the class docstring and the constructor docstring are
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 ConstructorDocumentedInClassAndInit.
"""

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 ConstructorDocumentedInClassAndInit.
"""


class ConstructorDocumentedInClass:
"""
Class to test constructor documented via class docstring.
Useful to ensure that validation of `__init__` does not signal GL08,
when the class docstring properly documents the `__init__` constructor.
Parameters
----------
param1 : int
Description of param1.
See Also
--------
otherclass : A class that does something else.
Examples
--------
This is an example of how to use ConstructorDocumentedInClass.
"""

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


class ConstructorDocumentedInClassWithNoParameters:
"""
Class to test constructor documented via class docstring with no parameters.
Useful to ensure that validation of `__init__` does not signal GL08,
when the class docstring properly documents the `__init__` constructor.
See Also
--------
otherclass : A class that does something else.
Examples
--------
This is an example of how to use ConstructorDocumentedInClassWithNoParameters.
"""

def __init__(self) -> None:
pass


class IncompleteConstructorDocumentedInClass:
"""
Class to test an incomplete constructor docstring.
This class does not properly document parameters.
Unnecessary extended summary.
See Also
--------
otherclass : A class that does something else.
Examples
--------
This is an example of how to use IncompleteConstructorDocumentedInClass.
"""

def __init__(self, param1: int):
pass


class TestValidator:
def _import_path(self, klass=None, func=None):
"""
Expand Down Expand Up @@ -1536,6 +1643,40 @@ 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",
[
("ConstructorDocumentedInClass", tuple(), ("GL08",), tuple()),
("ConstructorDocumentedInClassAndInit", tuple(), ("GL08",), tuple()),
(
"ConstructorDocumentedInClassWithNoParameters",
tuple(),
("GL08",),
tuple(),
),
(
"IncompleteConstructorDocumentedInClass",
("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
24 changes: 23 additions & 1 deletion numpydoc/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,29 @@ 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
# Also check if code_obj is defined, as undefined for the AstValidator in validate_docstrings.py.
if (
doc.name.endswith(".__init__")
and doc.is_function_or_method
and hasattr(doc, "code_obj")
):
cls_name = doc.code_obj.__qualname__.split(".")[0]
cls = Validator._load_obj(f"{doc.code_obj.__module__}.{cls_name}")
# cls = Validator._load_obj(f"{doc.name[:-9]}.{cls_name}") ## Alternative
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

0 comments on commit 6d5eb42

Please sign in to comment.