From 59a3ac6383594d84cc59468d81fbada0ba45a12f Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 26 Jun 2025 12:27:54 +1000 Subject: [PATCH 01/12] feat(numpydoc/hooks/validate_docstrings.py): Added a pyproject.toml config option `exclude_files` that allows regex path exclusions Solution to Issue #497 --- doc/validation.rst | 6 ++++ numpydoc/hooks/validate_docstrings.py | 28 +++++++++++++++- numpydoc/tests/hooks/test_validate_hook.py | 39 ++++++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) diff --git a/doc/validation.rst b/doc/validation.rst index 3d2babae..4f7188ae 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -36,6 +36,8 @@ the pre-commit hook as follows: expressions ``\.undocumented_method$`` or ``\.__repr__$``. This maps to ``numpydoc_validation_exclude`` from the :ref:`Sphinx build configuration `. +* ``exclude_files``: Exclude file paths matching the regular expressions + ``^tests/.*`` or ``^module/gui.*``. * ``override_SS05``: Allow docstrings to start with "Process ", "Assess ", or "Access ". To override different checks, add a field for each code in the form of ``override_`` with a collection of regular expression(s) @@ -57,6 +59,10 @@ the pre-commit hook as follows: '\.undocumented_method$', '\.__repr__$', ] + exclude_files = [ # don't process filepaths that match these regex + '^tests/.*', + '^module/gui.*', + ] override_SS05 = [ # override SS05 to allow docstrings starting with these words '^Process ', '^Assess ', diff --git a/numpydoc/hooks/validate_docstrings.py b/numpydoc/hooks/validate_docstrings.py index aa318e47..a718e4ef 100644 --- a/numpydoc/hooks/validate_docstrings.py +++ b/numpydoc/hooks/validate_docstrings.py @@ -273,7 +273,12 @@ def parse_config(dir_path: os.PathLike = None) -> dict: dict Config options for the numpydoc validation hook. """ - options = {"checks": {"all"}, "exclude": set(), "overrides": {}} + options = { + "checks": {"all"}, + "exclude": set(), + "overrides": {}, + "exclude_files": set(), + } dir_path = Path(dir_path).expanduser().resolve() toml_path = dir_path / "pyproject.toml" @@ -306,6 +311,13 @@ def extract_check_overrides(options, config_items): else [global_exclusions] ) + file_exclusions = config.get("exclude_files", options["exclude_files"]) + options["exclude_files"] = set( + file_exclusions + if not isinstance(file_exclusions, str) + else [file_exclusions] + ) + extract_check_overrides(options, config.items()) elif cfg_path.is_file(): @@ -332,6 +344,16 @@ def extract_check_overrides(options, config_items): except configparser.NoOptionError: pass + try: + options["exclude_files"] = set( + config.get(numpydoc_validation_config_section, "exclude_files") + .rstrip(",") + .split(",") + or options["exclude_files"] + ) + except configparser.NoOptionError: + pass + extract_check_overrides( options, config.items(numpydoc_validation_config_section) ) @@ -341,6 +363,7 @@ def extract_check_overrides(options, config_items): options["checks"] = validate.get_validation_checks(options["checks"]) options["exclude"] = compile_regex(options["exclude"]) + options["exclude_files"] = compile_regex(options["exclude_files"]) return options @@ -395,9 +418,12 @@ def run_hook( project_root, _ = find_project_root(files) config_options = parse_config(config or project_root) config_options["checks"] -= set(ignore or []) + exclude_re = config_options["exclude_files"] findings = False for file in files: + if exclude_re and exclude_re.match(file): + continue if file_issues := process_file(file, config_options): findings = True diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index 47f315c2..0711e81f 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -139,6 +139,45 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys): assert capsys.readouterr().err.strip() == expected +def test_validate_hook_with_toml_config_exclude_files(example_module, tmp_path, capsys): + """ + Test that a file is correctly processed in the absence of config files + with command line ignore options. + """ + + with open(tmp_path / "pyproject.toml", "w") as config_file: + config_file.write( + inspect.cleandoc( + """ + [tool.numpydoc_validation] + checks = [ + "all", + "EX01", + "SA01", + "ES01", + ] + exclude = '\\.__init__$' + override_SS05 = [ + '^Creates', + ] + + exclude_files = [ + '.*/example.*\.py', + ] + """ + ) + ) + + expected = inspect.cleandoc( + """ + """ + ) + + return_code = run_hook([example_module], config=tmp_path) + assert return_code == 1 + assert capsys.readouterr().err.strip() == expected + + def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys): """ Test that a file is correctly processed with the config coming from From 1b2a68d2b62ad769b05810dfde9a6d2ede1aca49 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 26 Jun 2025 12:50:23 +1000 Subject: [PATCH 02/12] fix(test_validate_hook.py): Corrected test for exclude_files toml option, which should have 0 findings --- numpydoc/tests/hooks/test_validate_hook.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index 0711e81f..7355a6db 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -168,14 +168,8 @@ def test_validate_hook_with_toml_config_exclude_files(example_module, tmp_path, ) ) - expected = inspect.cleandoc( - """ - """ - ) - return_code = run_hook([example_module], config=tmp_path) - assert return_code == 1 - assert capsys.readouterr().err.strip() == expected + assert return_code == 0 # Should report no findings. def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys): From acbc13debd2390317f17f5c661267cd3a108fcc3 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 26 Jun 2025 13:01:33 +1000 Subject: [PATCH 03/12] refactor(test_validate_hook.py): Added extra testcase to verify no-matching exclude_files option --- numpydoc/tests/hooks/test_validate_hook.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index 7355a6db..e74004cd 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -139,7 +139,13 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys): assert capsys.readouterr().err.strip() == expected -def test_validate_hook_with_toml_config_exclude_files(example_module, tmp_path, capsys): +@pytest.mark.parametrize( + "regex, expected_code", + [(".*/example.*\.py", 0), (".*/non_existent_match.*\.py", 1)], +) +def test_validate_hook_with_toml_config_exclude_files( + example_module, tmp_path, capsys, regex, expected_code +): """ Test that a file is correctly processed in the absence of config files with command line ignore options. @@ -160,16 +166,17 @@ def test_validate_hook_with_toml_config_exclude_files(example_module, tmp_path, override_SS05 = [ '^Creates', ] - exclude_files = [ - '.*/example.*\.py', + """ + + regex + + """ ] """ ) ) return_code = run_hook([example_module], config=tmp_path) - assert return_code == 0 # Should report no findings. + assert return_code == expected_code # Should not-report/report findings. def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys): From 8fc7b8357b13da1301a7a91a26980e1de703261f Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 26 Jun 2025 13:09:33 +1000 Subject: [PATCH 04/12] refactor(test_validate_hook.py): Modified toml exclude_files test mark, to correct parameter order --- numpydoc/tests/hooks/test_validate_hook.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index e74004cd..73ff2149 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -144,7 +144,7 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys): [(".*/example.*\.py", 0), (".*/non_existent_match.*\.py", 1)], ) def test_validate_hook_with_toml_config_exclude_files( - example_module, tmp_path, capsys, regex, expected_code + example_module, regex, expected_code, tmp_path, capsys ): """ Test that a file is correctly processed in the absence of config files @@ -169,8 +169,7 @@ def test_validate_hook_with_toml_config_exclude_files( exclude_files = [ """ + regex - + """ - ] + + """] """ ) ) From f4658ac75a02b32a855f5e0fcd8e5dc686b9dfdb Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Thu, 26 Jun 2025 13:13:24 +1000 Subject: [PATCH 05/12] refactor(test_validate_hook.py): Change string type --- numpydoc/tests/hooks/test_validate_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index 73ff2149..0c50f6a5 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -141,7 +141,7 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys): @pytest.mark.parametrize( "regex, expected_code", - [(".*/example.*\.py", 0), (".*/non_existent_match.*\.py", 1)], + [(""".*/example.*\.py""", 0), (""".*/non_existent_match.*\.py""", 1)], ) def test_validate_hook_with_toml_config_exclude_files( example_module, regex, expected_code, tmp_path, capsys From 6fadb95919a805481d86f324bae53b884d606000 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Fri, 27 Jun 2025 22:57:22 +1000 Subject: [PATCH 06/12] test(test_validate_hook.py): Added correct pyproject.toml and setup.cfg test cases for the `exclude_files` option --- numpydoc/tests/hooks/test_validate_hook.py | 105 +++++++++++++-------- 1 file changed, 66 insertions(+), 39 deletions(-) diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py index 0c50f6a5..b235313b 100644 --- a/numpydoc/tests/hooks/test_validate_hook.py +++ b/numpydoc/tests/hooks/test_validate_hook.py @@ -139,45 +139,6 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys): assert capsys.readouterr().err.strip() == expected -@pytest.mark.parametrize( - "regex, expected_code", - [(""".*/example.*\.py""", 0), (""".*/non_existent_match.*\.py""", 1)], -) -def test_validate_hook_with_toml_config_exclude_files( - example_module, regex, expected_code, tmp_path, capsys -): - """ - Test that a file is correctly processed in the absence of config files - with command line ignore options. - """ - - with open(tmp_path / "pyproject.toml", "w") as config_file: - config_file.write( - inspect.cleandoc( - """ - [tool.numpydoc_validation] - checks = [ - "all", - "EX01", - "SA01", - "ES01", - ] - exclude = '\\.__init__$' - override_SS05 = [ - '^Creates', - ] - exclude_files = [ - """ - + regex - + """] - """ - ) - ) - - return_code = run_hook([example_module], config=tmp_path) - assert return_code == expected_code # Should not-report/report findings. - - def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys): """ Test that a file is correctly processed with the config coming from @@ -285,3 +246,69 @@ def test_validate_hook_exclude_option_setup_cfg(example_module, tmp_path, capsys return_code = run_hook([example_module], config=tmp_path) assert return_code == 1 assert capsys.readouterr().err.strip() == expected + + +@pytest.mark.parametrize( + "regex, expected_code", + [(".*(/|\\\\)example.*\.py", 0), (".*/non_existent_match.*\.py", 1)], +) +def test_validate_hook_exclude_files_option_pyproject( + example_module, regex, expected_code, tmp_path +): + """ + Test that the hook correctly processes the toml config and either includes + or excludes files based on the `exclude_files` option. + """ + + with open(tmp_path / "pyproject.toml", "w") as config_file: + config_file.write( + inspect.cleandoc( + f""" + [tool.numpydoc_validation] + checks = [ + "all", + "EX01", + "SA01", + "ES01", + ] + exclude = '\\.__init__$' + override_SS05 = [ + '^Creates', + ] + exclude_files = [ + '{regex}', + ]""" + ) + ) + + return_code = run_hook([example_module], config=tmp_path) + assert return_code == expected_code # Should not-report/report findings. + + +@pytest.mark.parametrize( + "regex, expected_code", + [(".*(/|\\\\)example.*\.py", 0), (".*/non_existent_match.*\.py", 1)], +) +def test_validate_hook_exclude_files_option_setup_cfg( + example_module, regex, expected_code, tmp_path +): + """ + Test that the hook correctly processes the setup config and either includes + or excludes files based on the `exclude_files` option. + """ + + with open(tmp_path / "setup.cfg", "w") as config_file: + config_file.write( + inspect.cleandoc( + f""" + [tool:numpydoc_validation] + checks = all,EX01,SA01,ES01 + exclude = \\.NewClass$,\\.__init__$ + override_SS05 = ^Creates + exclude_files = {regex} + """ + ) + ) + + return_code = run_hook([example_module], config=tmp_path) + assert return_code == expected_code # Should not-report/report findings. From 2e09ea68466a570dfe7b49c244a5c7aad6e5a8c2 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 01:36:29 +1000 Subject: [PATCH 07/12] feat(numpydoc.py): Added config option `numpydoc_validation_exclude_files` for Sphinx plugin Uses very similar regex processing to `numpydoc_validation_exclude` but instead applies to a module check before any numpydoc validation is performed. --- numpydoc/numpydoc.py | 44 ++++++++++++++++++++---- numpydoc/tests/test_docscrape.py | 1 + numpydoc/tests/test_numpydoc.py | 57 ++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 9a920e35..442cd4f8 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -27,6 +27,7 @@ from docutils.nodes import Text, citation, comment, inline, reference, section from sphinx.addnodes import desc_content, pending_xref +from sphinx.application import Sphinx as SphinxApp from sphinx.util import logging from . import __version__ @@ -52,7 +53,7 @@ def _traverse_or_findall(node, condition, **kwargs): ) -def rename_references(app, what, name, obj, options, lines): +def rename_references(app: SphinxApp, what, name, obj, options, lines): # decorate reference numbers so that there are no duplicates # these are later undecorated in the doctree, in relabel_references references = set() @@ -114,7 +115,7 @@ def is_docstring_section(node): return False -def relabel_references(app, doc): +def relabel_references(app: SphinxApp, doc): # Change 'hash-ref' to 'ref' in label text for citation_node in _traverse_or_findall(doc, citation): if not _is_cite_in_numpydoc_docstring(citation_node): @@ -141,7 +142,7 @@ def matching_pending_xref(node): ref.replace(ref_text, new_text.copy()) -def clean_backrefs(app, doc, docname): +def clean_backrefs(app: SphinxApp, doc, docname): # only::latex directive has resulted in citation backrefs without reference known_ref_ids = set() for ref in _traverse_or_findall(doc, reference, descend=True): @@ -161,7 +162,7 @@ def clean_backrefs(app, doc, docname): DEDUPLICATION_TAG = " !! processed by numpydoc !!" -def mangle_docstrings(app, what, name, obj, options, lines): +def mangle_docstrings(app: SphinxApp, what, name, obj, options, lines): if DEDUPLICATION_TAG in lines: return show_inherited_class_members = app.config.numpydoc_show_inherited_class_members @@ -190,6 +191,19 @@ def mangle_docstrings(app, what, name, obj, options, lines): title_re = re.compile(pattern, re.IGNORECASE | re.DOTALL) lines[:] = title_re.sub("", u_NL.join(lines)).split(u_NL) else: + # Test the obj to find the module path, and skip the check if it's path is matched by + # numpydoc_validation_exclude_files + if app.config.numpydoc_validation_exclude_files: + excluder = app.config.numpydoc_validation_files_excluder + module = getattr(obj, "__module__", None) + if module: + # Perform the exclusion check solely on the module if there's no __path__. + path = getattr(obj, "__path__", module) + exclude_from_validation = excluder.search(path) if excluder else False + if exclude_from_validation: + # Skip validation for this object. + return + try: doc = get_doc_object( obj, what, u_NL.join(lines), config=cfg, builder=app.builder @@ -239,7 +253,7 @@ def mangle_docstrings(app, what, name, obj, options, lines): lines += ["..", DEDUPLICATION_TAG] -def mangle_signature(app, what, name, obj, options, sig, retann): +def mangle_signature(app: SphinxApp, what, name, obj, options, sig, retann): # Do not try to inspect classes that don't define `__init__` if inspect.isclass(obj) and ( not hasattr(obj, "__init__") @@ -273,7 +287,7 @@ def _clean_text_signature(sig): return start_sig + sig + ")" -def setup(app, get_doc_object_=get_doc_object): +def setup(app: SphinxApp, get_doc_object_=get_doc_object): if not hasattr(app, "add_config_value"): return None # probably called by nose, better bail out @@ -299,6 +313,7 @@ def setup(app, get_doc_object_=get_doc_object): app.add_config_value("numpydoc_xref_ignore", set(), True, types=[set, str]) app.add_config_value("numpydoc_validation_checks", set(), True) app.add_config_value("numpydoc_validation_exclude", set(), False) + app.add_config_value("numpydoc_validation_exclude_files", set(), False) app.add_config_value("numpydoc_validation_overrides", dict(), False) # Extra mangling domains @@ -309,7 +324,7 @@ def setup(app, get_doc_object_=get_doc_object): return metadata -def update_config(app, config=None): +def update_config(app: SphinxApp, config=None): """Update the configuration with default values.""" if config is None: # needed for testing and old Sphinx config = app.config @@ -342,6 +357,21 @@ def update_config(app, config=None): ) config.numpydoc_validation_excluder = exclude_expr + # Generate the regexp for files to ignore during validation + if isinstance(config.numpydoc_validation_exclude_files, str): + raise ValueError( + f"numpydoc_validation_exclude_files must be a container of strings, " + f"e.g. [{config.numpydoc_validation_exclude_files!r}]." + ) + + config.numpydoc_validation_files_excluder = None + if config.numpydoc_validation_exclude_files: + exclude_files_expr = re.compile( + r"|".join(exp for exp in config.numpydoc_validation_exclude_files) + ) + config.numpydoc_validation_files_excluder = exclude_files_expr + + # Generate the regexp for validation overrides for check, patterns in config.numpydoc_validation_overrides.items(): config.numpydoc_validation_overrides[check] = re.compile( r"|".join(exp for exp in patterns) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index f10adc45..c122515a 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -1593,6 +1593,7 @@ def __init__(self, a, b): # numpydoc.update_config fails if this config option not present self.numpydoc_validation_checks = set() self.numpydoc_validation_exclude = set() + self.numpydoc_validation_exclude_files = set() self.numpydoc_validation_overrides = dict() xref_aliases_complete = deepcopy(DEFAULT_LINKS) diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index bedef5fa..5e6bb432 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -31,6 +31,7 @@ class MockConfig: numpydoc_attributes_as_param_list = True numpydoc_validation_checks = set() numpydoc_validation_exclude = set() + numpydoc_validation_exclude_files = set() numpydoc_validation_overrides = dict() @@ -287,6 +288,62 @@ def test_clean_backrefs(): assert "id1" in citation["backrefs"] +@pytest.mark.parametrize( + "exclude_files, has_warnings", + [ + ( + [ + r"^doesnt_match_any_file$", + ], + True, + ), + ( + [ + r"^test_numpydoc$", + ], + False, + ), + ], +) +def test_mangle_skip_exclude_files(exclude_files, has_warnings): + """ + Check that the regex expressions in numpydoc_validation_files_exclude + are correctly used to skip checks on files that match the patterns. + """ + + def process_something_noop_function(): + """Process something.""" + + app = MockApp() + app.config.numpydoc_validation_checks = {"all"} + + # Class attributes for config persist - need to reset them to unprocessed states. + app.config.numpydoc_validation_exclude = set() # Reset to default... + app.config.numpydoc_validation_overrides = dict() # Reset to default... + + app.config.numpydoc_validation_exclude_files = exclude_files + update_config(app) + + # Setup for catching warnings + status, warning = StringIO(), StringIO() + logging.setup(app, status, warning) + + # Simulate a file that matches the exclude pattern + docname = "test_numpydoc" + mangle_docstrings( + app, + "function", + process_something_noop_function.__name__, + process_something_noop_function, + None, + process_something_noop_function.__doc__.split("\n"), + ) + + # Are warnings generated? + print(warning.getvalue()) + assert bool(warning.getvalue()) is has_warnings + + if __name__ == "__main__": import pytest From 69f23fcb4bcddd23c17fc8929202923e5cea2c4f Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 02:08:41 +1000 Subject: [PATCH 08/12] fix(numpydoc.py): Corrected module path check for `numpydoc_validation_exclude_files` option --- numpydoc/numpydoc.py | 24 ++++++++++++++++++++---- numpydoc/tests/test_numpydoc.py | 3 +-- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 442cd4f8..3c0d2092 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -22,6 +22,7 @@ import itertools import pydoc import re +import sys from collections.abc import Callable from copy import deepcopy @@ -197,10 +198,25 @@ def mangle_docstrings(app: SphinxApp, what, name, obj, options, lines): excluder = app.config.numpydoc_validation_files_excluder module = getattr(obj, "__module__", None) if module: - # Perform the exclusion check solely on the module if there's no __path__. - path = getattr(obj, "__path__", module) - exclude_from_validation = excluder.search(path) if excluder else False - if exclude_from_validation: + # Check if module is a string + if isinstance(module, str): + if module in sys.modules: + module_obj = sys.modules[module] + path = ( + module_obj.__file__ + if hasattr(module_obj, "__file__") + else None + ) + else: + # Just filter the module string as the path. + path = module + # Check if module is module instance: + elif module.__class__.__name__ == "module": + path = getattr(module, "__path__", None) + else: + path = None + + if path and excluder and excluder.search(path): # Skip validation for this object. return diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py index 5e6bb432..f879d7fb 100644 --- a/numpydoc/tests/test_numpydoc.py +++ b/numpydoc/tests/test_numpydoc.py @@ -299,7 +299,7 @@ def test_clean_backrefs(): ), ( [ - r"^test_numpydoc$", + r"^.*test_numpydoc\.py$", ], False, ), @@ -329,7 +329,6 @@ def process_something_noop_function(): logging.setup(app, status, warning) # Simulate a file that matches the exclude pattern - docname = "test_numpydoc" mangle_docstrings( app, "function", From d6b6aeedff23cb4956e91e2b29c70d72e87f32b2 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 02:24:26 +1000 Subject: [PATCH 09/12] refactor(numpydoc.py): Changed `numpydoc_validation_exclue_files` sphinx option to use `inspect`, and simplified path checking using `__file__` --- numpydoc/numpydoc.py | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 3c0d2092..9b70f5a9 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -196,29 +196,15 @@ def mangle_docstrings(app: SphinxApp, what, name, obj, options, lines): # numpydoc_validation_exclude_files if app.config.numpydoc_validation_exclude_files: excluder = app.config.numpydoc_validation_files_excluder - module = getattr(obj, "__module__", None) - if module: - # Check if module is a string - if isinstance(module, str): - if module in sys.modules: - module_obj = sys.modules[module] - path = ( - module_obj.__file__ - if hasattr(module_obj, "__file__") - else None - ) - else: - # Just filter the module string as the path. - path = module - # Check if module is module instance: - elif module.__class__.__name__ == "module": - path = getattr(module, "__path__", None) - else: - path = None - - if path and excluder and excluder.search(path): - # Skip validation for this object. - return + module = inspect.getmodule(obj) + try: + path = module.__file__ if module else None + except AttributeError: + path = None + + if path and excluder and excluder.search(path): + # Skip validation for this object. + return try: doc = get_doc_object( From 3999c481d17cc5cccbf12b7620578d20a108334a Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 02:42:55 +1000 Subject: [PATCH 10/12] fix(Modified-`numpydoc_validation_exclude_files`-option-to-only-activate-if-`numpydoc_validation_checks`-is-not-empty): Mimicing same behaviour as `numpydoc_validation_exclude` --- numpydoc/numpydoc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 9b70f5a9..ff41b0ba 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -194,7 +194,10 @@ def mangle_docstrings(app: SphinxApp, what, name, obj, options, lines): else: # Test the obj to find the module path, and skip the check if it's path is matched by # numpydoc_validation_exclude_files - if app.config.numpydoc_validation_exclude_files: + if ( + app.config.numpydoc_validation_exclude_files + and app.config.numpydoc_validation_checks + ): excluder = app.config.numpydoc_validation_files_excluder module = inspect.getmodule(obj) try: From 439a296b548832371590beb00d7913e19bd7ef28 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 02:44:38 +1000 Subject: [PATCH 11/12] docs(validation.rst,-install.rst): Added docs for new feature `numpydoc_validation_exclude_files` for Sphinx and `exclude_files` for pyproject.toml --- doc/install.rst | 12 ++++++++++++ doc/validation.rst | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/install.rst b/doc/install.rst index c72d777f..aa0079b8 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -139,6 +139,18 @@ numpydoc_validation_exclude : set validation. Only has an effect when docstring validation is activated, i.e. ``numpydoc_validation_checks`` is not an empty set. + numpydoc_validation_exclude_files : set + A container of strings using :py:mod:`re` syntax specifying path patterns to + ignore for docstring validation. + For example, to skip docstring validation for all objects in + ``tests\``:: + + numpydoc_validation_exclude_files = {"$.*tests/.*"} + + The default is an empty set meaning no paths are excluded from docstring + validation. + Only has an effect when docstring validation is activated, i.e. + ``numpydoc_validation_checks`` is not an empty set. numpydoc_validation_overrides : dict A dictionary mapping :ref:`validation checks ` to a container of strings using :py:mod:`re` syntax specifying patterns to diff --git a/doc/validation.rst b/doc/validation.rst index 4f7188ae..775a7c7b 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -37,7 +37,9 @@ the pre-commit hook as follows: maps to ``numpydoc_validation_exclude`` from the :ref:`Sphinx build configuration `. * ``exclude_files``: Exclude file paths matching the regular expressions - ``^tests/.*`` or ``^module/gui.*``. + ``^tests/.*`` or ``^module/gui.*``. This maps to + ``numpydoc_validation_exclude_files`` from the + :ref:`Sphinx build configuration `. * ``override_SS05``: Allow docstrings to start with "Process ", "Assess ", or "Access ". To override different checks, add a field for each code in the form of ``override_`` with a collection of regular expression(s) From 8f61726d9c2defb5639cc589065888e3a0d41505 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 28 Jun 2025 02:52:28 +1000 Subject: [PATCH 12/12] docs(validation.rst,-install.rst): Fixed indentation and linebreaks to properly format --- doc/install.rst | 2 +- doc/validation.rst | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/install.rst b/doc/install.rst index aa0079b8..87f4a13a 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -139,7 +139,7 @@ numpydoc_validation_exclude : set validation. Only has an effect when docstring validation is activated, i.e. ``numpydoc_validation_checks`` is not an empty set. - numpydoc_validation_exclude_files : set +numpydoc_validation_exclude_files : set A container of strings using :py:mod:`re` syntax specifying path patterns to ignore for docstring validation. For example, to skip docstring validation for all objects in diff --git a/doc/validation.rst b/doc/validation.rst index 775a7c7b..0912e5c0 100644 --- a/doc/validation.rst +++ b/doc/validation.rst @@ -37,9 +37,9 @@ the pre-commit hook as follows: maps to ``numpydoc_validation_exclude`` from the :ref:`Sphinx build configuration `. * ``exclude_files``: Exclude file paths matching the regular expressions - ``^tests/.*`` or ``^module/gui.*``. This maps to - ``numpydoc_validation_exclude_files`` from the - :ref:`Sphinx build configuration `. + ``^tests/.*$`` or ``^module/gui.*$``. This maps to + ``numpydoc_validation_exclude_files`` from the + :ref:`Sphinx build configuration `. * ``override_SS05``: Allow docstrings to start with "Process ", "Assess ", or "Access ". To override different checks, add a field for each code in the form of ``override_`` with a collection of regular expression(s)