Skip to content

Commit

Permalink
Pre-commit hook for running numpydoc validation (#454)
Browse files Browse the repository at this point in the history
* Add numpydoc.hooks directory.

* Add numpydoc.hooks to packages.

* Add tabulate dependency for use in validate hook.

* Add option to pass a Validator class to validate() function.

* Add AST-based validation logic to review files.

* Add entry point for validation hook.

* Add pre-commit hook configuration.

* Add SS05 match pattern for allowed verbs; add some type annotations.

* Add config option to override GL08 by name.

* Add option specify a path to a config file.

* Add information on the hook to the docs.

* Grab arg name off ast arg nodes for *args/**kwargs; don't alter filepath.

* Update spacing in example.

* Reduce maxcolwidths in report of findings by hook.

* Show file in a separate column of the findings; item is from module onward.

* Update example in docs for new column.

* Update example in docs for new column.

* Switch to a stack for visiting.

* Add line to file column; show module lineno as 1.

* Expand user on config file path.

* Use Path operations.

* Add support for using inline comments to ignore specific checks.

* Add support for comments at the end of a multiline declaration.

* Add a note on inline option to docs.

* Simplify check for the declaration start.

* Add support for reading hook config from pyproject.toml

* Add some initial tests for the validate hook.

* Tweak docstring.

* Add tests for hook using config files.

* Add finding of project root.

* Update link in docstring.

* Tweak code blocks in docs.

* Shorten table to avoid scroll in docs.
  • Loading branch information
stefmolin authored Jun 29, 2023
1 parent 4c740e4 commit a58ab90
Show file tree
Hide file tree
Showing 12 changed files with 844 additions and 6 deletions.
7 changes: 7 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
- id: numpydoc-validation
name: numpydoc-validation
description: This hook validates that docstrings in committed files adhere to numpydoc standards.
entry: validate-docstrings
require_serial: true
language: python
types: [python]
77 changes: 77 additions & 0 deletions doc/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,83 @@
Validation
==========

Docstring Validation using Pre-Commit Hook
------------------------------------------

To enable validation of docstrings as you commit files, add the
following to your ``.pre-commit-config.yaml`` file:

.. code-block:: yaml
- repo: https://github.com/numpy/numpydoc
rev: <version>
hooks:
- id: numpydoc-validation
After installing ``numpydoc``, run the following to see available
command line options for this hook:

.. code-block:: bash
$ python -m numpydoc.hooks.validate_docstrings --help
Using a config file provides additional customization. Both
``pyproject.toml`` and ``setup.cfg`` are supported; however, if the
project contains both you must use the ``pyproject.toml`` file.
The example below configures the pre-commit hook to ignore three checks
and specifies exceptions to the checks ``SS05`` (allow docstrings to
start with "Process ", "Assess ", or "Access ") and ``GL08`` (allow
the class/method/function with name "__init__" to not have a docstring).

``pyproject.toml``::

[tool.numpydoc_validation]
ignore = [
"EX01",
"SA01",
"ES01",
]
override_SS05 = '^((Process|Assess|Access) )'
override_GL08 = '^(__init__)$'

``setup.cfg``::

[tool:numpydoc_validation]
ignore = EX01,SA01,ES01
override_SS05 = ^((Process|Assess|Access) )
override_GL08 = ^(__init__)$

For more fine-tuned control, you can also include inline comments to tell the
validation hook to ignore certain checks:

.. code-block:: python
class SomeClass: # numpydoc ignore=EX01,SA01,ES01
"""This is the docstring for SomeClass."""
def __init__(self): # numpydoc ignore=GL08
pass
If any issues are found when commiting, a report is printed out and the
commit is halted:

.. code-block:: output
numpydoc-validation......................................................Failed
- hook id: numpydoc-validation
- exit code: 1
+----------------------+----------------------+---------+--------------------------------------+
| file | item | check | description |
+======================+======================+=========+======================================+
| src/pkg/utils.py:1 | utils | GL08 | The object does not have a docstring |
| src/pkg/utils.py:90 | utils.normalize | PR04 | Parameter "field" has no type |
| src/pkg/module.py:12 | module.MyClass | GL08 | The object does not have a docstring |
| src/pkg/module.py:33 | module.MyClass.parse | RT03 | Return value has no description |
+----------------------+----------------------+---------+--------------------------------------+
See below for a full listing of checks.

Docstring Validation using Python
---------------------------------

Expand Down
1 change: 1 addition & 0 deletions numpydoc/hooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Pre-commit hooks using numpydoc."""
48 changes: 48 additions & 0 deletions numpydoc/hooks/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Utility functions for pre-commit hooks."""

import itertools
import os
from pathlib import Path
from typing import Sequence


def find_project_root(srcs: Sequence[str]):
"""
Return a directory containing .git, .hg, pyproject.toml, or setup.cfg.
That directory can be one of the directories passed in ``srcs`` or their
common parent. If no directory in the tree contains a marker that would
specify it's the project root, the root of the file system is returned.
Parameters
----------
srcs : Sequence[str]
The filepaths to run the hook on.
Returns
-------
str
The project root directory.
See Also
--------
black.find_project_root :
This function was adapted from
`Black <https://github.com/psf/black/blob/main/src/black/files.py>`_.
"""
if not srcs:
return Path(".").resolve(), "current directory"

common_path = Path(
os.path.commonpath([Path(src).expanduser().resolve() for src in srcs])
)

for dir in itertools.chain([common_path], common_path.parents):
if (dir / "pyproject.toml").is_file():
return dir, "pyproject.toml"
if (dir / "setup.cfg").is_file():
return dir, "setup.cfg"
if (dir / ".git").exists() or (dir / ".hg").is_dir():
return dir, "version control"

return dir, "file system root"
Loading

0 comments on commit a58ab90

Please sign in to comment.