From bd64a7d520e895d7da01b607365e29f50c073900 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 11 Dec 2024 23:40:50 +1100 Subject: [PATCH 01/11] MAINT: Changed class constructor __init__ GL08 reporting to only if class docstring isn't complete --- numpydoc/tests/test_validate.py | 112 ++++++++++++++++++++++++++++++++ numpydoc/validate.py | 18 ++++- 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py index 8b40794f..0d39a97c 100644 --- a/numpydoc/tests/test_validate.py +++ b/numpydoc/tests/test_validate.py @@ -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): """ @@ -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.""" diff --git a/numpydoc/validate.py b/numpydoc/validate.py index 138e9d48..c0bbd3d4 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -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, From 998d72f699f5a5c3b8003433dc8f81a88c499b17 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 12 Dec 2024 00:03:32 +1100 Subject: [PATCH 02/11] DOC: Described new class docstring and constructor GL08 interaction --- doc/format.rst | 3 ++- doc/validation.rst | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/format.rst b/doc/format.rst index 6579191c..bf00d452 100644 --- a/doc/format.rst +++ b/doc/format.rst @@ -557,7 +557,8 @@ Class docstring Use the same sections as outlined above (all except :ref:`Returns ` are applicable). The constructor (``__init__``) should also be documented here, the :ref:`Parameters ` 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 ` section, may be used to describe non-method attributes of the class:: diff --git a/doc/validation.rst b/doc/validation.rst index aa9d5236..d2b22241 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -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 `, :ref:`pre-commit hook `, and :ref:`Sphinx extension `. From a43b5b92d83f2996645340ed9b6f2675a617f573 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 12 Dec 2024 00:14:52 +1100 Subject: [PATCH 03/11] DOC: Fixed invalid classdoc reference --- doc/validation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/validation.rst b/doc/validation.rst index d2b22241..b2815f66 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -183,7 +183,7 @@ inline comments: def __init__(self): # numpydoc ignore=GL08 pass -Note, if the :ref:`class <_classdoc>` docstring properly documents the +Note, if the :ref:`class ` docstring properly documents the constructor, the ``GL08`` will be ignored by default. This is supported by the :ref:`CLI `, From 3f51f53d36ba43ff78fa47cc3dcaf06d6b4dd007 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 18 Dec 2024 14:10:24 +1100 Subject: [PATCH 04/11] test(test_validate.py): Changed test class names to better reflect the conditions they're used to check --- numpydoc/tests/test_validate.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py index 0d39a97c..45d1e41f 100644 --- a/numpydoc/tests/test_validate.py +++ b/numpydoc/tests/test_validate.py @@ -1198,11 +1198,12 @@ def missing_whitespace_after_comma(self): """ -class GoodConstructorInclusion: +class ConstructorDocumentedInClassAndInit: """ 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. + A case where both the class docstring and the constructor docstring are + defined. Parameters ---------- @@ -1215,7 +1216,7 @@ class GoodConstructorInclusion: Examples -------- - This is an example of how to use GoodConstructorInclusion. + This is an example of how to use ConstructorDocumentedInClassAndInit. """ def __init__(self, param1: int) -> None: @@ -1235,15 +1236,16 @@ def __init__(self, param1: int) -> None: Examples -------- - This is an example of how to use GoodConstructorInclusion. + This is an example of how to use ConstructorDocumentedInClassAndInit. """ -class GoodConstructorExclusion: +class ConstructorDocumentedInClass: """ 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. + Useful to ensure that validation of `__init__` does not signal GL08, + when the class docstring properly documents the `__init__` constructor. Parameters ---------- @@ -1256,14 +1258,14 @@ class GoodConstructorExclusion: Examples -------- - This is an example of how to use GoodConstructorExclusion. + This is an example of how to use ConstructorDocumentedInClass. """ def __init__(self, param1: int) -> None: pass -class BadConstructorExclusion: +class IncompleteConstructorDocumentedInClass: """ Class to test undocumented constructor docstring. @@ -1275,7 +1277,7 @@ class BadConstructorExclusion: Examples -------- - This is an example of how to use BadConstructorExclusion. + This is an example of how to use IncompleteConstructorDocumentedInClass. """ def __init__(self, param1: int): @@ -1623,10 +1625,10 @@ def test_bad_docstrings(self, capsys, klass, func, msgs): @pytest.mark.parametrize( "klass,exp_init_codes,exc_init_codes,exp_klass_codes", [ - ("GoodConstructorExclusion", tuple(), ("GL08",), tuple()), - ("GoodConstructorInclusion", tuple(), ("GL08",), tuple()), + ("ConstructorDocumentedInClass", tuple(), ("GL08",), tuple()), + ("ConstructorDocumentedInClassAndInit", tuple(), ("GL08",), tuple()), ( - "BadConstructorExclusion", + "IncompleteConstructorDocumentedInClass", ("GL08",), tuple(), ("PR01"), # Parameter not documented in class constructor From 66818eaec40c3b438ac039155eb73356dbc0e9d1 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 18 Dec 2024 14:23:18 +1100 Subject: [PATCH 05/11] docs(validation.rst,-format.rst): Cleaned up doc references to constructor checking --- doc/format.rst | 5 +++-- doc/validation.rst | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/format.rst b/doc/format.rst index bf00d452..c1a077c1 100644 --- a/doc/format.rst +++ b/doc/format.rst @@ -557,8 +557,9 @@ Class docstring Use the same sections as outlined above (all except :ref:`Returns ` are applicable). The constructor (``__init__``) should also be documented here, the :ref:`Parameters ` section of the docstring details the -constructor's parameters. The class docstring does not need to be repeated -in a constructor, but is optional. +constructor's parameters. While repetition is unnecessary, a docstring for +the class constructor (``__init__``) can be added optionally to provide +detailed initialization documentation. An **Attributes** section, located below the :ref:`Parameters ` section, may be used to describe non-method attributes of the class:: diff --git a/doc/validation.rst b/doc/validation.rst index b2815f66..158cc20a 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -183,8 +183,8 @@ inline comments: def __init__(self): # numpydoc ignore=GL08 pass -Note, if the :ref:`class ` docstring properly documents the -constructor, the ``GL08`` will be ignored by default. +Note that a properly formatted :ref:`class ` docstring +silences ``G08`` for an undocumented ``__init__`` constructor. This is supported by the :ref:`CLI `, :ref:`pre-commit hook `, and From b22eb3b1bfcf133cfa40c8af88a3d56cace0ea34 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 18 Dec 2024 14:27:44 +1100 Subject: [PATCH 06/11] test(test_validate.py): Extra test case for a constructor docstring when the constructor has no parameters, also modified docstring descriptions for clarity --- numpydoc/tests/test_validate.py | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py index 45d1e41f..9f0f7942 100644 --- a/numpydoc/tests/test_validate.py +++ b/numpydoc/tests/test_validate.py @@ -1200,7 +1200,7 @@ def missing_whitespace_after_comma(self): class ConstructorDocumentedInClassAndInit: """ - Class to test optional constructor docstring inclusion. + Class to test constructor documented via class and constructor docstrings. A case where both the class docstring and the constructor docstring are defined. @@ -1242,7 +1242,7 @@ def __init__(self, param1: int) -> None: class ConstructorDocumentedInClass: """ - Class to test optional constructor docstring exclusion. + 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. @@ -1265,10 +1265,31 @@ 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 undocumented constructor docstring. + Class to test an incomplete constructor docstring. + This class does not properly document parameters. Unnecessary extended summary. See Also @@ -1627,6 +1648,12 @@ def test_bad_docstrings(self, capsys, klass, func, msgs): [ ("ConstructorDocumentedInClass", tuple(), ("GL08",), tuple()), ("ConstructorDocumentedInClassAndInit", tuple(), ("GL08",), tuple()), + ( + "ConstructorDocumentedInClassWithNoParameters", + tuple(), + ("GL08",), + tuple(), + ), ( "IncompleteConstructorDocumentedInClass", ("GL08",), From be2bd62da2044076e435c3bd44d520e11e6eabcd Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 18 Dec 2024 14:32:48 +1100 Subject: [PATCH 07/11] docs(validation.rst): Changed words to better articular docstring dependency --- doc/validation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/validation.rst b/doc/validation.rst index 158cc20a..68844c2b 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -184,7 +184,7 @@ inline comments: pass Note that a properly formatted :ref:`class ` docstring -silences ``G08`` for an undocumented ``__init__`` constructor. +silences ``G08`` for an ``__init__`` constructor without a docstring. This is supported by the :ref:`CLI `, :ref:`pre-commit hook `, and From 296043dd2a101aba55376c19ae309d83fb7ff2e0 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Wed, 18 Dec 2024 22:34:16 +1100 Subject: [PATCH 08/11] doc(doc/format.rst): Grammar Co-authored-by: Stefan van der Walt --- doc/format.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/format.rst b/doc/format.rst index c1a077c1..ecdd1d4f 100644 --- a/doc/format.rst +++ b/doc/format.rst @@ -558,7 +558,7 @@ Use the same sections as outlined above (all except :ref:`Returns ` are applicable). The constructor (``__init__``) should also be documented here, the :ref:`Parameters ` section of the docstring details the constructor's parameters. While repetition is unnecessary, a docstring for -the class constructor (``__init__``) can be added optionally to provide +the class constructor (``__init__``) can, optionally, be added to provide detailed initialization documentation. An **Attributes** section, located below the :ref:`Parameters ` From de00113062ffd88958f9e195cc44ac228b283f1d Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 9 Jan 2025 07:30:41 +1100 Subject: [PATCH 09/11] Update numpydoc/validate.py MAINT: Changed constructor identification in validation by including class method '.' Co-authored-by: Eric Larson --- numpydoc/validate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpydoc/validate.py b/numpydoc/validate.py index 22a24ff4..f4361c0d 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -639,7 +639,7 @@ def validate(obj_name, validator_cls=None, **validator_kwargs): if not doc.raw_doc: 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: + 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)) From abe64b2de093e170d7a20d007fd53ebea94b07dd Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 9 Jan 2025 07:54:35 +1100 Subject: [PATCH 10/11] fix(validate.py): Added requirement for 'code_obj' attribute to be defined when checking constructor docstring to decide if reporting GL08 error hooks/validate_docstrings.py implmements an ASTValidator class that does not define a 'code_obj' attribute. Possibly another way to link to the class docstring. --- numpydoc/validate.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/numpydoc/validate.py b/numpydoc/validate.py index f4361c0d..a60a97d1 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -639,7 +639,12 @@ def validate(obj_name, validator_cls=None, **validator_kwargs): if not doc.raw_doc: 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: + # 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 = getattr(importlib.import_module(doc.code_obj.__module__), cls_name) cls_doc = Validator(get_doc_object(cls)) From 8fb92b580e013df706ac7cc55877893e296c8df9 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 9 Jan 2025 23:28:42 +1100 Subject: [PATCH 11/11] refactor(validate.py): Used Validator._load_obj instead of importlib/getattr module accessors --- numpydoc/validate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/numpydoc/validate.py b/numpydoc/validate.py index a60a97d1..858a06d2 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -646,7 +646,8 @@ def validate(obj_name, validator_cls=None, **validator_kwargs): and hasattr(doc, "code_obj") ): cls_name = doc.code_obj.__qualname__.split(".")[0] - cls = getattr(importlib.import_module(doc.code_obj.__module__), cls_name) + 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.