diff --git a/.circleci/config.yml b/.circleci/config.yml
index 66a60c8d..98cae9bc 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -20,7 +20,7 @@ jobs:
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip wheel setuptools
- python -m pip install --upgrade -r requirements/doc.txt
+ python -m pip install --upgrade --group doc
python -m pip list
- save_cache:
key: pip-cache
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 9984dc57..d873f107 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -1 +1,2 @@
4b38c01e680cf2032acb09819bc0985e5dfc5ca8
+be316333f436519e0e2ab1379cbdedde79c9ac68
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 918c3a5c..66c89692 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -6,3 +6,7 @@ updates:
interval: "monthly"
labels:
- "type: Maintenance"
+ groups:
+ actions:
+ patterns:
+ - "*"
diff --git a/.github/workflows/label-check.yml b/.github/workflows/label-check.yml
index 5c4aee62..d6e97f22 100644
--- a/.github/workflows/label-check.yml
+++ b/.github/workflows/label-check.yml
@@ -4,7 +4,7 @@ on:
pull_request:
types:
- opened
- - repoened
+ - reopened
- labeled
- unlabeled
- synchronize
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 7e2f5eea..ebed3deb 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -5,7 +5,7 @@ jobs:
name: Run CircleCI artifacts redirector
steps:
- name: GitHub Action step
- uses: larsoner/circleci-artifacts-redirector-action@master
+ uses: scientific-python/circleci-artifacts-redirector-action@839631420e45a08af893032e5a5e8843bf47e8ff # v1.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
api-token: ${{ secrets.CIRCLECI_ARTIFACT_REDIRECTOR_TOKEN }}
diff --git a/.github/workflows/milestone-merged-prs.yml b/.github/workflows/milestone-merged-prs.yml
index 71ae037c..f455839d 100644
--- a/.github/workflows/milestone-merged-prs.yml
+++ b/.github/workflows/milestone-merged-prs.yml
@@ -12,7 +12,7 @@ jobs:
name: attach to PR
runs-on: ubuntu-latest
steps:
- - uses: scientific-python/attach-next-milestone-action@bc07be829f693829263e57d5e8489f4e57d3d420
+ - uses: scientific-python/attach-next-milestone-action@c9cfab10ad0c67fed91b01103db26b7f16634639
with:
token: ${{ secrets.MILESTONE_LABELER_TOKEN }}
force: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..3d77cdf9
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,47 @@
+name: Build Wheel and Release
+on:
+ pull_request:
+ branches:
+ - main
+ push:
+ tags:
+ - v*
+
+jobs:
+ sdist_wheel:
+ name: sdist and wheels
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
+ with:
+ python-version: "3.12"
+ - name: Build wheels
+ run: |
+ git clean -fxd
+ pip install -U build twine wheel
+ python -m build --sdist --wheel
+ - run: twine check --strict dist/*
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: dist
+ path: dist
+
+ pypi-publish:
+ needs: sdist_wheel
+ name: upload release to PyPI
+ if: github.repository_owner == 'numpy' && startsWith(github.ref, 'refs/tags/v') && github.actor == 'jarrodmillman' && always()
+ runs-on: ubuntu-latest
+ # Specifying a GitHub environment is optional, but strongly encouraged
+ environment: release
+ permissions:
+ # IMPORTANT: this permission is mandatory for trusted publishing
+ id-token: write
+ steps:
+ - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
+ with:
+ name: dist
+ path: dist
+ - uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1af5e7a5..b188cba4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,17 +16,18 @@ jobs:
strategy:
matrix:
os: [Ubuntu]
- python-version: ["3.8", "3.9", "3.10", "3.11"]
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
sphinx-version:
- [
- "sphinx==5.0",
- "sphinx==5.3",
- "sphinx==6.0",
- "sphinx==6.2",
- "sphinx>=7.0",
- ]
+ ["sphinx==6.0", "sphinx==6.2", "sphinx==7.0", "sphinx>=7.3"]
+ include:
+ - os: Windows
+ python-version: "3.12"
+ sphinx-version: "sphinx" # version shouldn't really matter here
+ defaults:
+ run:
+ shell: bash -eo pipefail {0}
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Python setup
uses: actions/setup-python@v5
@@ -36,14 +37,13 @@ jobs:
- name: Setup environment
run: |
python -m pip install --upgrade pip wheel setuptools
- python -m pip install -r requirements/test.txt -r requirements/doc.txt
python -m pip install codecov
- python -m pip install ${{ matrix.sphinx-version }}
+ python -m pip install "${{ matrix.sphinx-version }}"
python -m pip list
- name: Install
run: |
- python -m pip install .
+ python -m pip install . --group test --group doc
pip list
- name: Run test suite
@@ -56,29 +56,31 @@ jobs:
- name: Make sure CLI works
run: |
- python -m numpydoc numpydoc.tests.test_main._capture_stdout
- echo '! python -m numpydoc numpydoc.tests.test_main._invalid_docstring' | bash
- python -m numpydoc --validate numpydoc.tests.test_main._capture_stdout
- echo '! python -m numpydoc --validate numpydoc.tests.test_main._docstring_with_errors' | bash
+ numpydoc render numpydoc.tests.test_main._capture_stdout
+ echo '! numpydoc render numpydoc.tests.test_main._invalid_docstring' | bash
+ numpydoc validate numpydoc.tests.test_main._capture_stdout
+ echo '! numpydoc validate numpydoc.tests.test_main._docstring_with_errors' | bash
- name: Setup for doc build
run: |
sudo apt-get update
sudo apt install texlive texlive-latex-extra latexmk dvipng
+ if: runner.os == 'Linux'
- name: Build documentation
run: |
make -C doc html SPHINXOPTS="-nT"
make -C doc latexpdf SPHINXOPTS="-nT"
+ if: runner.os == 'Linux'
prerelease:
runs-on: ${{ matrix.os }}-latest
strategy:
matrix:
os: [ubuntu]
- python-version: ["3.9", "3.10"]
+ python-version: ["3.11", "3.12", "3.13"]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Python setup
uses: actions/setup-python@v5
@@ -88,13 +90,12 @@ jobs:
- name: Setup environment
run: |
python -m pip install --upgrade pip wheel setuptools
- python -m pip install --pre -r requirements/test.txt -r requirements/doc.txt
python -m pip install codecov
python -m pip list
- name: Install
run: |
- python -m pip install .
+ python -m pip install . --group test --group doc
pip list
- name: Run test suite
@@ -107,10 +108,10 @@ jobs:
- name: Make sure CLI works
run: |
- python -m numpydoc numpydoc.tests.test_main._capture_stdout
- echo '! python -m numpydoc numpydoc.tests.test_main._invalid_docstring' | bash
- python -m numpydoc --validate numpydoc.tests.test_main._capture_stdout
- echo '! python -m numpydoc --validate numpydoc.tests.test_main._docstring_with_errors' | bash
+ numpydoc render numpydoc.tests.test_main._capture_stdout
+ echo '! numpydoc render numpydoc.tests.test_main._invalid_docstring' | bash
+ numpydoc validate numpydoc.tests.test_main._capture_stdout
+ echo '! numpydoc validate numpydoc.tests.test_main._docstring_with_errors' | bash
- name: Setup for doc build
run: |
diff --git a/.gitignore b/.gitignore
index 26f7400b..60696edd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ doc/_build
numpydoc/tests/tinybuild/_build
numpydoc/tests/tinybuild/generated
MANIFEST
+node_modules
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 25b53f36..edb0146e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -3,56 +3,36 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: c4a0b883114b00d8d76b479c820ce7950211c99b # frozen: v4.5.0
+ rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- - id: trailing-whitespace
- - id: end-of-file-fixer
- - id: debug-statements
+ - id: check-added-large-files
- id: check-ast
- - id: mixed-line-ending
- - id: check-yaml
- args: [--allow-multiple-documents]
+ - id: check-case-conflict
- id: check-json
- id: check-toml
- - id: check-added-large-files
-
- - repo: https://github.com/psf/black
- rev: ec91a2be3c44d88e1a3960a4937ad6ed3b63464e # frozen: 23.12.1
- hooks:
- - id: black
+ - id: check-yaml
+ args: [--allow-multiple-documents]
+ - id: debug-statements
+ - id: end-of-file-fixer
+ - id: mixed-line-ending
+ - id: trailing-whitespace
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8
+ rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8
hooks:
- id: prettier
- entry: env PRETTIER_LEGACY_CLI=1 prettier # temporary fix for https://github.com/prettier/prettier/issues/15742
- files: \.(html|md|yml|yaml)
+ types_or: [yaml, toml, markdown, css, scss, javascript, json]
args: [--prose-wrap=preserve]
- - repo: https://github.com/adamchainz/blacken-docs
- rev: 960ead214cd1184149d366c6d27ca6c369ce46b6 # frozen: 1.16.0
- hooks:
- - id: blacken-docs
-
- - repo: https://github.com/asottile/pyupgrade
- rev: 1bbebc88c6925a4e56fd5446b830b12c38c1c24a # frozen: v3.15.0
- hooks:
- - id: pyupgrade
- args: [--py38-plus]
-
- - repo: local
+ - repo: https://github.com/astral-sh/ruff-pre-commit
+ rev: "9c89adb347f6b973f4905a4be0051eb2ecf85dea" # frozen: v0.13.3
hooks:
- - id: generate_requirements.py
- name: generate_requirements.py
- language: system
- entry: python tools/generate_requirements.py
- files: "pyproject.toml|requirements/.*\\.txt|tools/generate_requirements.py"
+ - id: ruff
+ args: ["--fix", "--show-fixes", "--exit-non-zero-on-fix"]
+ - id: ruff-format
ci:
- # This ensures that pr's aren't autofixed by the bot, rather you call
- # the bot to make the fix
autofix_prs: false
autofix_commit_msg: |
'[pre-commit.ci 🤖] Apply code format tools to PR'
- # Update hook versions every month (so we don't get hit with weekly update pr's)
- autoupdate_schedule: monthly
+ autoupdate_schedule: quarterly
diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml
index b88e9ed2..69493db7 100644
--- a/.pre-commit-hooks.yaml
+++ b/.pre-commit-hooks.yaml
@@ -1,7 +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
+ entry: numpydoc lint
+ require_serial: false
language: python
types: [python]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index a124b7ca..ebc00e82 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -6,29 +6,22 @@ version: 2
# Set the OS, Python version and other tools you might need
build:
- os: ubuntu-22.04
+ os: ubuntu-24.04
tools:
- python: "3.11"
+ python: "3.13"
# You can also specify other tool versions:
# nodejs: "19"
# rust: "1.64"
# golang: "1.19"
+ jobs:
+ install:
+ - python -m pip install --upgrade pip wheel setuptools
+ - python -m pip install . --group doc
# Build documentation in the "doc/" directory with Sphinx
sphinx:
configuration: doc/conf.py
-
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
# - pdf
# - epub
-
-# Optional but recommended, declare the Python requirements required
-# to build your documentation
-# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
-python:
- install:
- - method: pip
- path: .
- extra_requirements:
- - doc
diff --git a/MANIFEST.in b/MANIFEST.in
index d1508eee..0a6df3ac 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,7 +2,6 @@ include MANIFEST.in
include *.txt
include *.rst
recursive-include doc *
-recursive-include requirements *
recursive-include numpydoc *
# Exclude what we don't want to include
diff --git a/README.rst b/README.rst
index a5af9a00..fdd35942 100644
--- a/README.rst
+++ b/README.rst
@@ -4,7 +4,6 @@ numpydoc -- Numpy's Sphinx extensions
.. image:: https://readthedocs.org/projects/numpydoc/badge/?version=latest
:alt: Documentation Status
- :scale: 100%
:target: https://numpydoc.readthedocs.io/en/latest/
.. image:: https://codecov.io/gh/numpy/numpydoc/branch/main/graph/badge.svg
@@ -18,7 +17,7 @@ docstrings formatted according to the NumPy documentation format.
The extension also adds the code description directives
``np:function``, ``np-c:function``, etc.
-numpydoc requires Python 3.8+ and sphinx 5+.
+numpydoc requires Python 3.10+ and sphinx 6+.
For usage information, please refer to the `documentation
`_.
diff --git a/RELEASE.rst b/RELEASE.rst
index f4390f02..21781bc9 100644
--- a/RELEASE.rst
+++ b/RELEASE.rst
@@ -6,29 +6,41 @@ Introduction
Example ``__version__``
-- 1.8.dev0 # development version of 1.8 (release candidate 1)
-- 1.8rc1 # 1.8 release candidate 1
-- 1.8rc2.dev0 # development version of 1.8 (release candidate 2)
+- 1.8rc0.dev0 # development version of 1.8 (first release candidate)
+- 1.8rc0 # 1.8 release candidate 1
+- 1.8rc1.dev0 # development version of 1.8 (second release candidate)
- 1.8 # 1.8 release
-- 1.9.dev0 # development version of 1.9 (release candidate 1)
+- 1.9rc0.dev0 # development version of 1.9 (first release candidate)
Test release candidates on numpy, scipy, matplotlib, scikit-image, and networkx.
Process
-------
-- Review and update ``doc/release_notes.rst``.
+- Set release variables::
+
+ export VERSION=
+ export PREVIOUS=
+ export ORG="numpy"
+ export REPO="numpydoc"
+ export LOG="doc/release/notes.rst"
+
+- Autogenerate release notes::
+
+ changelist ${ORG}/${REPO} v${PREVIOUS} main --version ${VERSION} --config pyproject.toml --format rst --out ${VERSION}.rst
+ changelist ${ORG}/${REPO} v${PREVIOUS} main --version ${VERSION} --config pyproject.toml --out ${VERSION}.md
+ cat ${VERSION}.rst | cat - ${LOG} > temp && mv temp ${LOG} && rm ${VERSION}.rst
- Update ``__version__`` in ``numpydoc/_version.py``.
- Commit changes::
- git add numpydoc/_version.py doc/release_notes.rst
- git commit -m 'Designate release'
+ git add numpydoc/_version.py ${LOG}
+ git commit -m "Designate ${VERSION} release"
- Add the version number (e.g., `v1.2.0`) as a tag in git::
- git tag -s [-u ] v -m 'signed tag'
+ git tag -s v${VERSION} -m "signed ${VERSION} tag"
If you do not have a gpg key, use -u instead; it is important for
Debian packaging that the tags are annotated
@@ -39,16 +51,18 @@ Process
where ``origin`` is the name of the ``github.com:numpy/numpydoc`` repository
-- Review the github release page::
+- Create release from tag::
+
+ - go to https://github.com/numpy/numpydoc/releases/new?tag=v${VERSION}
+ - add v${VERSION} for the `Release title`
+ - paste contents (or upload) of ${VERSION}.md in the `Describe this release section`
+ - if pre-release check the box labelled `Set as a pre-release`
- https://github.com/numpy/numpydoc/releases
-- Publish on PyPi::
+- Update https://github.com/numpy/numpydoc/milestones::
- git clean -fxd
- pip install --upgrade build wheel twine
- python -m build --sdist --wheel
- twine upload -s dist/*
+ - close old milestone
+ - ensure new milestone exists (perhaps setting due date)
- Update ``__version__`` in ``numpydoc/_version.py``.
diff --git a/doc/conf.py b/doc/conf.py
index 315bac73..ca41a854 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -4,17 +4,17 @@
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
-from datetime import date
-import numpydoc
-
# -- Path setup --------------------------------------------------------------
-
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-
import os
import sys
+from datetime import date
+
+from intersphinx_registry import get_intersphinx_mapping
+
+import numpydoc
# for example.py
sys.path.insert(0, os.path.abspath("."))
@@ -82,7 +82,7 @@
html_theme = "pydata_sphinx_theme"
html_theme_options = {
"show_prev_next": False,
- "navbar_end": ["theme-switcher", "search-field.html", "navbar-icon-links.html"],
+ "navbar_end": ["theme-switcher", "navbar-icon-links.html"],
"icon_links": [
{
"name": "GitHub",
@@ -138,9 +138,6 @@
# -- Intersphinx setup ----------------------------------------------------
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {
- "python": ("https://docs.python.org/3/", None),
- "numpy": ("https://numpy.org/devdocs/", None),
- "sklearn": ("https://scikit-learn.org/stable/", None),
-}
+# Example configuration for intersphinx: refer to several Python libraries.
+
+intersphinx_mapping = get_intersphinx_mapping(packages=["python", "numpy", "sklearn"])
diff --git a/doc/example.py b/doc/example.py
index 9d3eccaa..c8d4a85f 100644
--- a/doc/example.py
+++ b/doc/example.py
@@ -127,4 +127,3 @@ def foo(var1, var2, *args, long_var_name="hi", only_seldom_used_keyword=0, **kwa
# separate following codes (according to PEP257).
# But for function, method and module, there should be no blank lines
# after closing the docstring.
- pass
diff --git a/doc/format.rst b/doc/format.rst
index 2f9753c5..3b3693a4 100644
--- a/doc/format.rst
+++ b/doc/format.rst
@@ -175,8 +175,9 @@ respective types.
y
Description of parameter `y` (with type not specified).
-Enclose variables in single backticks. The colon must be preceded
-by a space, or omitted if the type is absent.
+The colon must be preceded by a space, or omitted if the type is absent.
+When referring to a parameter anywhere within the docstring, enclose its
+name in single backticks.
For the parameter types, be as precise as possible. Below are a
few examples of parameters and their types.
@@ -224,11 +225,11 @@ description, they can be combined::
Input arrays, description of `x1`, `x2`.
When documenting variable length positional, or keyword arguments, leave the
-leading star(s) in front of the name::
+leading star(s) in front of the name and do not specify a type::
- *args : tuple
+ *args
Additional arguments should be passed as keyword arguments
- **kwargs : dict, optional
+ **kwargs
Extra arguments to `metric`: refer to each metric documentation for a
list of all possible arguments.
@@ -549,12 +550,16 @@ not explicitly imported, `.. plot::` can be used directly if
Documenting classes
-------------------
+.. _classdoc:
+
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. 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 `
section, may be used to describe non-method attributes of the class::
@@ -562,10 +567,12 @@ section, may be used to describe non-method attributes of the class::
Attributes
----------
x : float
- The X coordinate.
+ Description of attribute `x`.
y : float
- The Y coordinate.
+ Description of attribute `y`.
+When referring to an attribute anywhere within the docstring, enclose its
+name in single backticks.
Attributes that are properties and have their own docstrings can be
simply listed by name::
@@ -606,6 +613,8 @@ becomes useful to have an additional **Methods** section:
"""
+When referring to a method anywhere within the docstring, enclose its
+name in single backticks.
If it is necessary to explain a private method (use with care!), it can
be referred to in the :ref:`Extended Summary ` or the
:ref:`Notes ` section.
@@ -690,11 +699,11 @@ belong in docstrings.
Other points to keep in mind
----------------------------
* Equations : as discussed in the :ref:`Notes ` section above, LaTeX
- formatting should be kept to a minimum. Often it's possible to show equations as
- Python code or pseudo-code instead, which is much more readable in a
- terminal. For inline display use double backticks (like ``y = np.sin(x)``).
- For display with blank lines above and below, use a double colon and indent
- the code, like::
+ formatting should be kept to a minimum. Often it's possible to show
+ equations as Python code or pseudo-code instead, which is much more readable
+ in a terminal. For inline display of code, use double backticks
+ like ````y = np.sin(x)````. For display with blank lines above and below,
+ use a double colon and indent the code, like::
end of previous sentence::
@@ -717,9 +726,13 @@ Other points to keep in mind
(i.e. scalar types, sequence types), those arguments can be documented
with type `array_like`.
-* Links : If you need to include hyperlinks in your docstring, note that
- some docstring sections are not parsed as standard reST, and in these
- sections, numpydoc may become confused by hyperlink targets such as::
+* Links : Depending on project settings, hyperlinks to documentation of
+ modules, classes, functions, methods, and attributes should automatically
+ be created if a recognized name is included within single backticks (e.g.
+ ```numpy``` renders as :any:`numpy`). If you need to include other
+ hyperlinks, note that some docstring sections are not parsed as standard
+ reST, and in these sections, numpydoc may become confused by hyperlink
+ targets such as::
.. _Example: http://www.example.com
@@ -729,22 +742,36 @@ Other points to keep in mind
`Example `_
-
Common reST concepts
--------------------
For paragraphs, indentation is significant and indicates indentation in the
output. New paragraphs are marked with a blank line.
-Use ``*italics*``, ``**bold**`` and ````monospace```` if needed in any
-explanations
-(but not for variable names and doctest code or multi-line code).
-Variable, module, function, and class names should be written between
-single back-ticks (```numpy```).
+Use ``*italics*``, ``**bold**`` if needed in any explanations.
+
+Use of backticks in reST is a common point of confusion because it is different
+from markdown. In most flavors of markdown, single backticks are used for
+monospaced font; in reST, *double* backticks are for ``monospaced font``,
+whereas the behavior of single backticks is defined by the default role. This
+leads to the following style recommendations:
+
+- Module, class, function, method, and attribute names should render as
+ hyperlinks in monospaced font (e.g. :any:`numpy`); depending on project
+ settings, this may be accomplished simply be enclosing them in single
+ backticks. If the hyperlink does not render as intended, explicitly
+ include the appropriate role and/or namespace.
+- This guide continues to recommended that parameter names be enclosed within
+ single backticks. Currently, this may cause parameter names to render
+ improperly and cause warnings, but numpydoc will soon release a feature
+ that causes them to render as monospaced hyperlinks to the parameter
+ documentation.
+- All other text that is intended to render in ``monospaced`` font should be
+ enclosed within ````double backticks````.
A more extensive example of reST markup can be found in `this example
-document `_;
+document `_;
the `quick reference
-`_ is
+`_ is
useful while editing.
Line spacing and indentation are significant and should be carefully
diff --git a/doc/index.rst b/doc/index.rst
index 389e3cb1..240bb5b5 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -23,5 +23,5 @@ Sphinx, and adds the code description directives ``np:function``,
install
format
validation
- release_notes
+ release/index
example
diff --git a/doc/install.rst b/doc/install.rst
index 480bd59a..5d61010f 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -5,10 +5,11 @@ Getting started
Installation
============
-This extension requires Python 3.8+, sphinx 5+ and is available from:
+This extension requires Python 3.10+, sphinx 6+ and is available from:
* `numpydoc on PyPI `_
* `numpydoc on GitHub `_
+* `numpydoc on conda-forge `_
`'numpydoc'` should be added to the ``extensions`` option in your Sphinx
``conf.py``. ``'sphinx.ext.autosummary'`` will automatically be loaded
@@ -138,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, relative to the package root.
+ 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/release/index.rst b/doc/release/index.rst
new file mode 100644
index 00000000..de1bbfee
--- /dev/null
+++ b/doc/release/index.rst
@@ -0,0 +1,13 @@
+Release notes
+=============
+
+.. toctree::
+ :maxdepth: 2
+
+ notes
+ old
+
+.. note::
+
+ For release notes (sparsely) kept prior to 1.0.0, look at the `releases page
+ on GitHub `__.
diff --git a/doc/release/notes.rst b/doc/release/notes.rst
new file mode 100644
index 00000000..88b052ba
--- /dev/null
+++ b/doc/release/notes.rst
@@ -0,0 +1,220 @@
+1.9.0
+=====
+
+We're happy to announce the release of numpydoc 1.9.0!
+
+Enhancements
+------------
+
+- ignore some errors at module level (`#593 `_).
+- Rework hook output to remove the table (`#611 `_).
+- Switch to storing AST nodes on the stack for more accurate method signature check and easy access to parent nodes (`#623 `_).
+
+Bug Fixes
+---------
+
+- MAINT: Changed class constructor __init__ GL08 reporting (`#592 `_).
+- BUG: Correct functionality of numpydoc SS05 (`#613 `_).
+- Specity the types of ``numpydoc_xref_ignore`` option (`#631 `_).
+
+Documentation
+-------------
+
+- DOC: Do not use types for ``*args``, ``**kwargs`` (`#585 `_).
+- mention conda-forge in installation docs (`#595 `_).
+- Fix typo in validation.rst (`#605 `_).
+- Fix broken link in ``format.rst`` (`#628 `_).
+
+Maintenance
+-----------
+
+- CI: use hashes for actions' versions in publishing job (`#579 `_).
+- Bump the actions group with 2 updates (`#581 `_).
+- Bump pypa/gh-action-pypi-publish from 1.10.0 to 1.10.2 in the actions group (`#582 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#583 `_).
+- MAINT: Add _exception_on_warning to MockApp (`#586 `_).
+- Bump the actions group across 1 directory with 2 updates (`#590 `_).
+- don't pass maxsplit as positional arg (`#596 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#598 `_).
+- Add Python 3.13 support (`#599 `_).
+- Update readthedocs config (`#600 `_).
+- Bump the actions group with 2 updates (`#603 `_).
+- Bump the actions group with 3 updates (`#609 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#614 `_).
+- Bump actions/download-artifact from 4.2.1 to 4.3.0 in the actions group (`#620 `_).
+- Bump scientific-python/circleci-artifacts-redirector-action from 1.0.0 to 1.1.0 in the actions group (`#627 `_).
+- Switch to dependency groups (`#626 `_).
+- Fix pip setup command in github workflow (`#629 `_).
+
+Contributors
+------------
+
+13 authors added to this release (alphabetically):
+
+- Brigitta Sipőcz (`@bsipocz `_)
+- Daniel McCloy (`@drammock `_)
+- Eric Larson (`@larsoner `_)
+- Gilles Peiffer (`@Peiffap `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- Lucas Colley (`@lucascolley `_)
+- Matt Gebert (`@mattgebert `_)
+- Maxine Hartnett (`@maxinelasp `_)
+- Ross Barnowski (`@rossbar `_)
+- Stefan van der Walt (`@stefanv `_)
+- Stefanie Molin (`@stefmolin `_)
+- Tim Hoffmann (`@timhoffm `_)
+- Yuki Kobayashi (`@koyuki7w `_)
+
+7 reviewers added to this release (alphabetically):
+
+- Charles Harris (`@charris `_)
+- Eric Larson (`@larsoner `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- Lucas Colley (`@lucascolley `_)
+- Matt Gebert (`@mattgebert `_)
+- Ross Barnowski (`@rossbar `_)
+- Stefan van der Walt (`@stefanv `_)
+
+_These lists are automatically generated, and may not be complete or may contain duplicates._
+
+1.8.0
+=====
+
+We're happy to announce the release of numpydoc 1.8.0!
+
+Enhancements
+------------
+
+- Unify CLIs (`#537 `_).
+- Move "Attributes" and "Methods" below "Parameters" (`#571 `_).
+
+Bug Fixes
+---------
+
+- FIX: coroutines can have a return statement (`#542 `_).
+- Unwrap decorated objects for YD01 validation check (`#541 `_).
+- Fix bug with validation encoding (`#550 `_).
+
+Documentation
+-------------
+
+- Classify development status as Production/Stable (`#548 `_).
+- Add note about TOML regex; fix typo (`#552 `_).
+- DOC: Clarify recommendations regarding use of backticks (`#525 `_).
+
+Maintenance
+-----------
+
+- Fix typo in label-check.yml (`#538 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#539 `_).
+- DEV: Rm xfails from pytest summary (`#540 `_).
+- Drop Python 3.8 support (`#545 `_).
+- Clean up old sphinx cruft (`#549 `_).
+- Test on sphinx 7.3 (`#547 `_).
+- Require GHA update grouping (`#553 `_).
+- Update pre-commit config (`#554 `_).
+- Use ruff for linting and formatting (`#555 `_).
+- Use intersphinx registry to avoid out of date links (`#563 `_).
+- Do not rely on requirements.txt in ci, use .[test,doc] (`#566 `_).
+- CI: update action that got moved org (`#567 `_).
+- Fix navbar for documentation pages (`#569 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#570 `_).
+- docscrape: fixes from SciPy (`#576 `_).
+- MAINT: Remove scale to work around PyPI bug (`#578 `_).
+
+Contributors
+------------
+
+10 authors added to this release (alphabetically):
+
+- Brigitta Sipőcz (`@bsipocz `_)
+- Eric Larson (`@larsoner `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- Lucas Colley (`@lucascolley `_)
+- M Bussonnier (`@Carreau `_)
+- Matt Haberland (`@mdhaber `_)
+- Melissa Weber Mendonça (`@melissawm `_)
+- Ross Barnowski (`@rossbar `_)
+- Stefanie Molin (`@stefmolin `_)
+- Thomas A Caswell (`@tacaswell `_)
+
+7 reviewers added to this release (alphabetically):
+
+- Eric Larson (`@larsoner `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- M Bussonnier (`@Carreau `_)
+- Matt Haberland (`@mdhaber `_)
+- Ross Barnowski (`@rossbar `_)
+- Stefan van der Walt (`@stefanv `_)
+- Stefanie Molin (`@stefmolin `_)
+
+_These lists are automatically generated, and may not be complete or may contain duplicates._
+
+1.7.0
+=====
+
+We're happy to announce the release of numpydoc 1.7.0!
+
+Enhancements
+------------
+
+- PERF: wrap inspect.getsourcelines with cache (`#532 `_).
+
+Bug Fixes
+---------
+
+- during tokenize, use UTF8 encoding on all platforms (`#510 `_).
+- fix 'Alias for field number X' problem with NamedTuples (`#527 `_).
+
+Documentation
+-------------
+
+- DOC: Fix typos found by codespell (`#514 `_).
+- DOC: Update link to mailing list (`#518 `_).
+- Add Python 3.12 to classifiers (`#529 `_).
+- Update release process (`#534 `_).
+- Update release process (`#535 `_).
+
+Maintenance
+-----------
+
+- [pre-commit.ci] pre-commit autoupdate (`#508 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#513 `_).
+- MAINT: apply refurb suggestion (`#515 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#516 `_).
+- Bump actions/setup-python from 4 to 5 (`#520 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#521 `_).
+- Filter ``DeprecationWarning`` in failing test for python 3.12 (`#523 `_).
+- MAINT: Replace NameConstant with Constant (`#524 `_).
+- [pre-commit.ci] pre-commit autoupdate (`#526 `_).
+- Update precommit repos (`#531 `_).
+- Require sphinx 6 (`#530 `_).
+- Use trusted publisher (`#533 `_).
+
+Contributors
+------------
+
+8 authors added to this release (alphabetically):
+
+- Chiara Marmo (`@cmarmo `_)
+- Daniel McCloy (`@drammock `_)
+- Dimitri Papadopoulos Orfanos (`@DimitriPapadopoulos `_)
+- Eric Larson (`@larsoner `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- Niko Föhr (`@fohrloop `_)
+- Philipp Hoffmann (`@dontgoto `_)
+- Ross Barnowski (`@rossbar `_)
+
+9 reviewers added to this release (alphabetically):
+
+- Antoine Pitrou (`@pitrou `_)
+- Charles Harris (`@charris `_)
+- Daniel McCloy (`@drammock `_)
+- Eric Larson (`@larsoner `_)
+- GitHub Web Flow (`@web-flow `_)
+- Jarrod Millman (`@jarrodmillman `_)
+- Niko Föhr (`@fohrloop `_)
+- Ross Barnowski (`@rossbar `_)
+- Stefan van der Walt (`@stefanv `_)
+
+_These lists are automatically generated, and may not be complete or may contain duplicates._
diff --git a/doc/release_notes.rst b/doc/release/old.rst
similarity index 97%
rename from doc/release_notes.rst
rename to doc/release/old.rst
index 2f64f4cb..a75c129d 100644
--- a/doc/release_notes.rst
+++ b/doc/release/old.rst
@@ -1,22 +1,3 @@
-Release notes
-=============
-
-.. roughly following https://sphinx-gallery.github.io/dev/maintainers.html,
-.. 1.0.0 notes were generated by:
-.. 1. tagging PRs as enhancement/bug/removed
-.. 2. $ github_changelog_generator -u numpy -p numpydoc --since-tag=v0.9.2
-.. 3. $ pandoc CHANGELOG.md --wrap=none -o release_notes.rst
-.. 4. adding a manual addition (CSS note), tweaking heading levels, adding TOC
-
-.. contents:: Page contents
- :local:
- :depth: 2
-
-.. note::
-
- For release notes (sparsely) kept prior to 1.0.0, look at the `releases page
- on GitHub `__.
-
1.6.0
-----
diff --git a/doc/validation.rst b/doc/validation.rst
index 4ce89017..a0729b6b 100644
--- a/doc/validation.rst
+++ b/doc/validation.rst
@@ -22,7 +22,7 @@ command line options for this hook:
.. code-block:: bash
- $ python -m numpydoc.hooks.validate_docstrings --help
+ $ numpydoc lint --help
Using a config file provides additional customization. Both ``pyproject.toml``
and ``setup.cfg`` are supported; however, if the project contains both
@@ -33,9 +33,13 @@ the pre-commit hook as follows:
``ES01`` (using the same logic as the :ref:`validation during Sphinx build
` for ``numpydoc_validation_checks``).
* ``exclude``: Don't report issues on objects matching any of the regular
- regular expressions ``\.undocumented_method$`` or ``\.__repr__$``. This
+ expressions ``\.undocumented_method$`` or ``\.__repr__$``. This
maps to ``numpydoc_validation_exclude`` from the
:ref:`Sphinx build configuration `.
+* ``exclude_files``: Exclude file paths (relative to the package root) matching
+ the regular expressions ``^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)
@@ -52,10 +56,15 @@ the pre-commit hook as follows:
"SA01",
"ES01",
]
+ # remember to use single quotes for regex in TOML
exclude = [ # don't report on objects that match any of these regex
'\.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 ',
@@ -102,12 +111,12 @@ can be called. For example, to do it for ``numpy.ndarray``, use:
.. code-block:: bash
- $ python -m numpydoc numpy.ndarray
+ $ numpydoc validate numpy.ndarray
This will validate that the docstring can be built.
For an exhaustive validation of the formatting of the docstring, use the
-``--validate`` parameter. This will report the errors detected, such as
+``validate`` subcommand. This will report the errors detected, such as
incorrect capitalization, wrong order of the sections, and many other
issues. Note that this will honor :ref:`inline ignore comments `,
but will not look for any configuration like the :ref:`pre-commit hook `
@@ -182,6 +191,9 @@ inline comments:
def __init__(self): # numpydoc ignore=GL08
pass
+Note that a properly formatted :ref:`class ` docstring
+silences ``GL08`` for an ``__init__`` constructor without a docstring.
+
This is supported by the :ref:`CLI `,
:ref:`pre-commit hook `, and
:ref:`Sphinx extension `.
diff --git a/numpydoc/__init__.py b/numpydoc/__init__.py
index 97f53053..5458b67f 100644
--- a/numpydoc/__init__.py
+++ b/numpydoc/__init__.py
@@ -2,6 +2,7 @@
This package provides the numpydoc Sphinx extension for handling docstrings
formatted according to the NumPy documentation format.
"""
+
from ._version import __version__
diff --git a/numpydoc/__main__.py b/numpydoc/__main__.py
index 4a50da9b..8cd0943e 100644
--- a/numpydoc/__main__.py
+++ b/numpydoc/__main__.py
@@ -1,55 +1,7 @@
"""
-Implementing `python -m numpydoc` functionality.
-"""
-import sys
-import argparse
-import ast
-
-from .docscrape_sphinx import get_doc_object
-from .validate import validate, Validator
-
-
-def render_object(import_path, config=None):
- """Test numpydoc docstring generation for a given object"""
- # TODO: Move Validator._load_obj to a better place than validate
- print(get_doc_object(Validator._load_obj(import_path), config=dict(config or [])))
- return 0
-
-
-def validate_object(import_path):
- exit_status = 0
- results = validate(import_path)
- for err_code, err_desc in results["errors"]:
- exit_status += 1
- print(":".join([import_path, err_code, err_desc]))
- return exit_status
-
-
-if __name__ == "__main__":
- ap = argparse.ArgumentParser(description=__doc__)
- ap.add_argument("import_path", help="e.g. numpy.ndarray")
-
- def _parse_config(s):
- key, _, value = s.partition("=")
- value = ast.literal_eval(value)
- return key, value
-
- ap.add_argument(
- "-c",
- "--config",
- type=_parse_config,
- action="append",
- help="key=val where val will be parsed by literal_eval, "
- "e.g. -c use_plots=True. Multiple -c can be used.",
- )
- ap.add_argument(
- "--validate", action="store_true", help="validate the object and report errors"
- )
- args = ap.parse_args()
+Implementing `python -m numpydoc` functionality
+""" # '.' omitted at end of docstring for testing purposes!
- if args.validate:
- exit_code = validate_object(args.import_path)
- else:
- exit_code = render_object(args.import_path, args.config)
+from .cli import main
- sys.exit(exit_code)
+raise SystemExit(main())
diff --git a/numpydoc/_version.py b/numpydoc/_version.py
index 8b264c14..21565c81 100644
--- a/numpydoc/_version.py
+++ b/numpydoc/_version.py
@@ -1 +1 @@
-__version__ = "1.7.0rc0.dev0"
+__version__ = "1.10.0rc0.dev0"
diff --git a/numpydoc/cli.py b/numpydoc/cli.py
new file mode 100644
index 00000000..30daa3f5
--- /dev/null
+++ b/numpydoc/cli.py
@@ -0,0 +1,130 @@
+"""The CLI for numpydoc."""
+
+import argparse
+import ast
+from collections.abc import Sequence
+from pathlib import Path
+from typing import List
+
+from .docscrape_sphinx import get_doc_object
+from .hooks import utils, validate_docstrings
+from .validate import ERROR_MSGS, Validator, validate
+
+
+def render_object(import_path: str, config: List[str] | None = None) -> int:
+ """Test numpydoc docstring generation for a given object."""
+ # TODO: Move Validator._load_obj to a better place than validate
+ print(get_doc_object(Validator._load_obj(import_path), config=dict(config or [])))
+ return 0
+
+
+def validate_object(import_path: str) -> int:
+ """Run numpydoc docstring validation for a given object."""
+ exit_status = 0
+ results = validate(import_path)
+ for err_code, err_desc in results["errors"]:
+ exit_status += 1
+ print(":".join([import_path, err_code, err_desc]))
+ return exit_status
+
+
+def get_parser() -> argparse.ArgumentParser:
+ """
+ Build an argument parser.
+
+ Returns
+ -------
+ argparse.ArgumentParser
+ The argument parser.
+ """
+ ap = argparse.ArgumentParser(prog="numpydoc", description=__doc__)
+ subparsers = ap.add_subparsers(title="subcommands")
+
+ def _parse_config(s):
+ key, _, value = s.partition("=")
+ value = ast.literal_eval(value)
+ return key, value
+
+ render = subparsers.add_parser(
+ "render",
+ description="Generate an expanded RST-version of the docstring.",
+ help="generate the RST docstring with numpydoc",
+ )
+ render.add_argument("import_path", help="e.g. numpy.ndarray")
+ render.add_argument(
+ "-c",
+ "--config",
+ type=_parse_config,
+ action="append",
+ help="key=val where val will be parsed by literal_eval, "
+ "e.g. -c use_plots=True. Multiple -c can be used.",
+ )
+ render.set_defaults(func=render_object)
+
+ validate = subparsers.add_parser(
+ "validate",
+ description="Validate an object's docstring against the numpydoc standard.",
+ help="validate the object's docstring and report errors",
+ )
+ validate.add_argument("import_path", help="e.g. numpy.ndarray")
+ validate.set_defaults(func=validate_object)
+
+ project_root_from_cwd, config_file = utils.find_project_root(["."])
+ config_options = validate_docstrings.parse_config(project_root_from_cwd)
+ ignored_checks = [
+ f"- {check}: {ERROR_MSGS[check]}"
+ for check in set(ERROR_MSGS.keys()) - config_options["checks"]
+ ]
+ ignored_checks_text = "\n " + "\n ".join(ignored_checks) + "\n"
+
+ lint_parser = subparsers.add_parser(
+ "lint",
+ description="Run numpydoc validation on files with option to ignore individual checks.",
+ help="validate all docstrings in file(s) using the abstract syntax tree",
+ formatter_class=argparse.RawTextHelpFormatter,
+ )
+ lint_parser.add_argument(
+ "files", type=str, nargs="+", help="File(s) to run numpydoc validation on."
+ )
+ lint_parser.add_argument(
+ "--config",
+ type=str,
+ help=(
+ "Path to a directory containing a pyproject.toml or setup.cfg file.\n"
+ "The hook will look for it in the root project directory.\n"
+ "If both are present, only pyproject.toml will be used.\n"
+ "Options must be placed under\n"
+ " - [tool:numpydoc_validation] for setup.cfg files and\n"
+ " - [tool.numpydoc_validation] for pyproject.toml files."
+ ),
+ )
+ lint_parser.add_argument(
+ "--ignore",
+ type=str,
+ nargs="*",
+ help=(
+ f"""Check codes to ignore.{
+ " Currently ignoring the following from "
+ f"{Path(project_root_from_cwd) / config_file}: {ignored_checks_text}"
+ "Values provided here will be in addition to the above, unless an alternate config is provided."
+ if ignored_checks
+ else ""
+ }"""
+ ),
+ )
+ lint_parser.set_defaults(func=validate_docstrings.run_hook)
+
+ return ap
+
+
+def main(argv: Sequence[str] | None = None) -> int:
+ """CLI for numpydoc."""
+ ap = get_parser()
+
+ args = vars(ap.parse_args(argv))
+
+ try:
+ func = args.pop("func")
+ return func(**args)
+ except KeyError:
+ ap.exit(status=2, message=ap.format_help())
diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py
index 315be6c3..26a08259 100644
--- a/numpydoc/docscrape.py
+++ b/numpydoc/docscrape.py
@@ -1,17 +1,15 @@
-"""Extract reference documentation from the NumPy source tree.
+"""Extract reference documentation from the NumPy source tree."""
-"""
+import copy
import inspect
-import textwrap
-import re
import pydoc
-from warnings import warn
+import re
+import sys
+import textwrap
from collections import namedtuple
from collections.abc import Callable, Mapping
-import copy
-import sys
-
from functools import cached_property
+from warnings import warn
def strip_blank_lines(l):
@@ -122,17 +120,17 @@ class NumpyDocString(Mapping):
"Summary": [""],
"Extended Summary": [],
"Parameters": [],
+ "Attributes": [],
+ "Methods": [],
"Returns": [],
"Yields": [],
"Receives": [],
+ "Other Parameters": [],
"Raises": [],
"Warns": [],
- "Other Parameters": [],
- "Attributes": [],
- "Methods": [],
+ "Warnings": [],
"See Also": [],
"Notes": [],
- "Warnings": [],
"References": "",
"Examples": "",
"index": {},
@@ -234,8 +232,7 @@ def _parse_param_list(self, content, single_element_is_type=False):
# NOTE: param line with single element should never have a
# a " :" before the description line, so this should probably
# warn.
- if header.endswith(" :"):
- header = header[:-2]
+ header = header.removesuffix(" :")
if single_element_is_type:
arg_name, arg_type = "", header
else:
@@ -346,7 +343,7 @@ def parse_item_name(text):
def _parse_index(self, section, content):
"""
- .. index: default
+ .. index:: default
:refguide: something, else, and more
"""
@@ -393,12 +390,8 @@ def _parse(self):
sections = list(self._read_sections())
section_names = {section for section, content in sections}
- has_returns = "Returns" in section_names
has_yields = "Yields" in section_names
# We could do more tests, but we are not. Arbitrarily.
- if has_returns and has_yields:
- msg = "Docstring contains both a Returns and Yields section."
- raise ValueError(msg)
if not has_yields and "Receives" in section_names:
msg = "Docstring contains a Receives section but not Yields."
raise ValueError(msg)
@@ -452,7 +445,7 @@ def _error_location(self, msg, error=True):
if error:
raise ValueError(msg)
else:
- warn(msg)
+ warn(msg, stacklevel=3)
# string conversion routines
@@ -555,8 +548,10 @@ def __str__(self, func_role=""):
out += self._str_signature()
out += self._str_summary()
out += self._str_extended_summary()
+ out += self._str_param_list("Parameters")
+ for param_list in ("Attributes", "Methods"):
+ out += self._str_param_list(param_list)
for param_list in (
- "Parameters",
"Returns",
"Yields",
"Receives",
@@ -569,8 +564,6 @@ def __str__(self, func_role=""):
out += self._str_see_also(func_role)
for s in ("Notes", "References", "Examples"):
out += self._str_section(s)
- for param_list in ("Attributes", "Methods"):
- out += self._str_param_list(param_list)
out += self._str_index()
return "\n".join(out)
@@ -604,7 +597,7 @@ def get_func(self):
def __str__(self):
out = ""
- func, func_name = self.get_func()
+ _func, func_name = self.get_func()
roles = {"func": "function", "meth": "method"}
@@ -705,21 +698,34 @@ def properties(self):
for name, func in inspect.getmembers(self._cls)
if (
not name.startswith("_")
+ and not self._should_skip_member(name, self._cls)
and (
func is None
- or isinstance(func, (property, cached_property))
+ or isinstance(func, property | cached_property)
or inspect.isdatadescriptor(func)
)
and self._is_show_member(name)
)
]
+ @staticmethod
+ def _should_skip_member(name, klass):
+ return (
+ # Namedtuples should skip everything in their ._fields as the
+ # docstrings for each of the members is: "Alias for field number X"
+ issubclass(klass, tuple)
+ and hasattr(klass, "_asdict")
+ and hasattr(klass, "_fields")
+ and name in klass._fields
+ )
+
def _is_show_member(self, name):
- if self.show_inherited_members:
- return True # show all class members
- if name not in self._cls.__dict__:
- return False # class member is inherited, we do not show it
- return True
+ return (
+ # show all class members
+ self.show_inherited_members
+ # or class member is not inherited
+ or name in self._cls.__dict__
+ )
def get_doc_object(
diff --git a/numpydoc/docscrape_sphinx.py b/numpydoc/docscrape_sphinx.py
index 26a8e6b3..3b2f325a 100644
--- a/numpydoc/docscrape_sphinx.py
+++ b/numpydoc/docscrape_sphinx.py
@@ -1,20 +1,17 @@
-import re
import inspect
-import textwrap
-import pydoc
-from collections.abc import Callable
import os
+import pydoc
+import re
+import textwrap
from jinja2 import FileSystemLoader
from jinja2.sandbox import SandboxedEnvironment
-import sphinx
from sphinx.jinja2glue import BuiltinTemplateLoader
-from .docscrape import NumpyDocString, FunctionDoc, ClassDoc, ObjDoc
+from .docscrape import ClassDoc, FunctionDoc, NumpyDocString, ObjDoc
from .docscrape import get_doc_object as get_doc_object_orig
from .xref import make_xref
-
IMPORT_MATPLOTLIB_RE = r"\b(import +matplotlib|from +matplotlib +import)\b"
@@ -163,7 +160,7 @@ def _process_param(self, param, desc, fake_autosummary):
display_param = f":obj:`{param} <{link_prefix}{param}>`"
if obj_doc:
# Overwrite desc. Take summary logic of autosummary
- desc = re.split(r"\n\s*\n", obj_doc.strip(), 1)[0]
+ desc = re.split(r"\n\s*\n", obj_doc.strip(), maxsplit=1)[0]
# XXX: Should this have DOTALL?
# It does not in autosummary
m = re.search(r"^([A-Z].*?\.)(?:\s|$)", " ".join(desc.split()))
@@ -332,7 +329,7 @@ def _str_references(self):
out += [".. only:: latex", ""]
items = []
for line in self["References"]:
- m = re.match(r".. \[([a-z0-9._-]+)\]", line, re.I)
+ m = re.match(r".. \[([a-z0-9._-]+)\]", line, re.IGNORECASE)
if m:
items.append(m.group(1))
out += [" " + ", ".join([f"[{item}]_" for item in items]), ""]
@@ -362,6 +359,12 @@ def __str__(self, indent=0, func_role="obj"):
"summary": self._str_summary(),
"extended_summary": self._str_extended_summary(),
"parameters": self._str_param_list("Parameters"),
+ "attributes": (
+ self._str_param_list("Attributes", fake_autosummary=True)
+ if self.attributes_as_param_list
+ else self._str_member_list("Attributes")
+ ),
+ "methods": self._str_member_list("Methods"),
"returns": self._str_returns("Returns"),
"yields": self._str_returns("Yields"),
"receives": self._str_returns("Receives"),
@@ -373,10 +376,6 @@ def __str__(self, indent=0, func_role="obj"):
"notes": self._str_section("Notes"),
"references": self._str_references(),
"examples": self._str_examples(),
- "attributes": self._str_param_list("Attributes", fake_autosummary=True)
- if self.attributes_as_param_list
- else self._str_member_list("Attributes"),
- "methods": self._str_member_list("Methods"),
}
ns = {k: "\n".join(v) for k, v in ns.items()}
diff --git a/numpydoc/hooks/utils.py b/numpydoc/hooks/utils.py
index 4f1d82aa..6f40c69c 100644
--- a/numpydoc/hooks/utils.py
+++ b/numpydoc/hooks/utils.py
@@ -2,8 +2,8 @@
import itertools
import os
+from collections.abc import Sequence
from pathlib import Path
-from typing import Sequence
def find_project_root(srcs: Sequence[str]):
@@ -31,7 +31,7 @@ def find_project_root(srcs: Sequence[str]):
`Black `_.
"""
if not srcs:
- return Path(".").resolve(), "current directory"
+ return Path.cwd(), "current directory"
common_path = Path(
os.path.commonpath([Path(src).expanduser().resolve() for src in srcs])
diff --git a/numpydoc/hooks/validate_docstrings.py b/numpydoc/hooks/validate_docstrings.py
index db8141d8..70823f10 100644
--- a/numpydoc/hooks/validate_docstrings.py
+++ b/numpydoc/hooks/validate_docstrings.py
@@ -1,6 +1,5 @@
"""Run numpydoc validation on contents of a file."""
-import argparse
import ast
import configparser
import os
@@ -13,9 +12,7 @@
import tomli as tomllib
from pathlib import Path
-from typing import Sequence, Tuple, Union
-
-from tabulate import tabulate
+from typing import Any, Dict, List, Tuple, Union
from .. import docscrape, validate
from .utils import find_project_root
@@ -36,7 +33,12 @@ class AstValidator(validate.Validator):
"""
def __init__(
- self, *, ast_node: ast.AST, filename: os.PathLike, obj_name: str
+ self,
+ *,
+ ast_node: ast.AST,
+ filename: os.PathLike,
+ obj_name: str,
+ ancestry: list[ast.AST],
) -> None:
self.node: ast.AST = ast_node
self.raw_doc: str = ast.get_docstring(self.node, clean=False) or ""
@@ -49,6 +51,8 @@ def __init__(
self.is_class: bool = isinstance(ast_node, ast.ClassDef)
self.is_module: bool = isinstance(ast_node, ast.Module)
+ self.ancestry: list[ast.AST] = ancestry
+
@staticmethod
def _load_obj(name):
raise NotImplementedError("AstValidator does not support this method.")
@@ -59,7 +63,11 @@ def name(self) -> str:
@property
def is_function_or_method(self) -> bool:
- return isinstance(self.node, (ast.FunctionDef, ast.AsyncFunctionDef))
+ return isinstance(self.node, ast.FunctionDef | ast.AsyncFunctionDef)
+
+ @property
+ def is_mod(self) -> bool:
+ return self.is_module
@property
def is_generator_function(self) -> bool:
@@ -90,7 +98,7 @@ def source_file_def_line(self) -> int:
@property
def signature_parameters(self) -> Tuple[str]:
- def extract_signature(node):
+ def extract_signature(node, parent):
args_node = node.args
params = []
for arg_type in ["posonlyargs", "args", "vararg", "kwonlyargs", "kwarg"]:
@@ -103,17 +111,21 @@ def extract_signature(node):
else:
params.extend([arg.arg for arg in entries])
params = tuple(params)
- if params and params[0] in {"self", "cls"}:
+ if (
+ params
+ and params[0] in {"self", "cls"}
+ and isinstance(parent, ast.ClassDef)
+ ):
return params[1:]
return params
params = tuple()
if self.is_function_or_method:
- params = extract_signature(self.node)
+ params = extract_signature(self.node, self.ancestry[-1])
elif self.is_class:
for child in self.node.body:
if isinstance(child, ast.FunctionDef) and child.name == "__init__":
- params = extract_signature(child)
+ params = extract_signature(child, self.node)
return params
@property
@@ -143,9 +155,23 @@ def __init__(
self.config: dict = config
self.filepath: str = filepath
self.module_name: str = Path(self.filepath).stem
- self.stack: list[str] = []
+ self.stack: list[ast.AST] = []
self.findings: list = []
+ @property
+ def node_name(self) -> str:
+ """
+ Get the full name of the current node in the stack.
+
+ Returns
+ -------
+ str
+ The full name of the current node in the stack.
+ """
+ return ".".join(
+ [getattr(node, "name", self.module_name) for node in self.stack]
+ )
+
def _ignore_issue(self, node: ast.AST, check: str) -> bool:
"""
Check whether the issue should be ignored.
@@ -184,13 +210,17 @@ def _get_numpydoc_issues(self, node: ast.AST) -> None:
node : ast.AST
The node under inspection.
"""
- name = ".".join(self.stack)
+ name = self.node_name
report = validate.validate(
- name, AstValidator, ast_node=node, filename=self.filepath
+ name,
+ AstValidator,
+ ast_node=node,
+ filename=self.filepath,
+ ancestry=self.stack[:-1],
)
self.findings.extend(
[
- [f'{self.filepath}:{report["file_line"]}', name, check, description]
+ [f"{self.filepath}:{report['file_line']}", name, check, description]
for check, description in report["errors"]
if not self._ignore_issue(node, check)
]
@@ -206,15 +236,13 @@ def visit(self, node: ast.AST) -> None:
The node to visit.
"""
if isinstance(
- node, (ast.Module, ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)
+ node, ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef
):
- self.stack.append(
- self.module_name if isinstance(node, ast.Module) else node.name
- )
+ self.stack.append(node)
if not (
self.config["exclude"]
- and re.search(self.config["exclude"], ".".join(self.stack))
+ and re.search(self.config["exclude"], self.node_name)
):
self._get_numpydoc_issues(node)
@@ -245,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"
@@ -278,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():
@@ -304,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)
)
@@ -313,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
@@ -341,77 +392,42 @@ def process_file(filepath: os.PathLike, config: dict) -> "list[list[str]]":
return docstring_visitor.findings
-def main(argv: Union[Sequence[str], None] = None) -> int:
- """Run the numpydoc validation hook."""
-
- project_root_from_cwd, config_file = find_project_root(["."])
- config_options = parse_config(project_root_from_cwd)
- ignored_checks = (
- "\n "
- + "\n ".join(
- [
- f"- {check}: {validate.ERROR_MSGS[check]}"
- for check in set(validate.ERROR_MSGS.keys()) - config_options["checks"]
- ]
- )
- + "\n"
- )
-
- parser = argparse.ArgumentParser(
- description="Run numpydoc validation on files with option to ignore individual checks.",
- formatter_class=argparse.RawTextHelpFormatter,
- )
- parser.add_argument(
- "files", type=str, nargs="+", help="File(s) to run numpydoc validation on."
- )
- parser.add_argument(
- "--config",
- type=str,
- help=(
- "Path to a directory containing a pyproject.toml or setup.cfg file.\n"
- "The hook will look for it in the root project directory.\n"
- "If both are present, only pyproject.toml will be used.\n"
- "Options must be placed under\n"
- " - [tool:numpydoc_validation] for setup.cfg files and\n"
- " - [tool.numpydoc_validation] for pyproject.toml files."
- ),
- )
- parser.add_argument(
- "--ignore",
- type=str,
- nargs="*",
- help=(
- f"""Check codes to ignore.{
- ' Currently ignoring the following from '
- f'{Path(project_root_from_cwd) / config_file}: {ignored_checks}'
- 'Values provided here will be in addition to the above, unless an alternate config is provided.'
- if config_options["checks"] else ''
- }"""
- ),
- )
-
- args = parser.parse_args(argv)
- project_root, _ = find_project_root(args.files)
- config_options = parse_config(args.config or project_root)
- config_options["checks"] -= set(args.ignore or [])
-
- findings = []
- for file in args.files:
- findings.extend(process_file(file, config_options))
-
- if findings:
- print(
- tabulate(
- findings,
- headers=["file", "item", "check", "description"],
- tablefmt="grid",
- maxcolwidths=50,
- ),
- file=sys.stderr,
- )
- return 1
- return 0
+def run_hook(
+ files: List[str],
+ *,
+ config: Dict[str, Any] | None = None,
+ ignore: List[str] | None = None,
+) -> int:
+ """
+ Run the numpydoc validation hook.
+ Parameters
+ ----------
+ files : list[str]
+ The absolute or relative paths to the files to inspect.
+ config : Union[dict[str, Any], None], optional
+ Configuration options for reviewing flagged issues.
+ ignore : Union[list[str], None], optional
+ Checks to ignore in the results.
-if __name__ == "__main__":
- raise SystemExit(main())
+ Returns
+ -------
+ int
+ The return status: 1 if issues were found, 0 otherwise.
+ """
+ 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
+
+ for line, obj, check, description in file_issues:
+ print(f"\n{line}: {check} {description}", file=sys.stderr)
+
+ return int(findings)
diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py
index 335de5a8..2b9757d7 100644
--- a/numpydoc/numpydoc.py
+++ b/numpydoc/numpydoc.py
@@ -16,27 +16,27 @@
.. [1] https://github.com/numpy/numpydoc
"""
-from copy import deepcopy
-import re
-import pydoc
-import inspect
-from collections.abc import Callable
+
import hashlib
+import importlib
+import inspect
import itertools
+import pydoc
+import re
+import sys
+from collections.abc import Callable
+from copy import deepcopy
+from pathlib import Path
-from docutils.nodes import citation, Text, section, comment, reference, inline
-import sphinx
-from sphinx.addnodes import pending_xref, desc_content
+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 sphinx.errors import ExtensionError
-
-if sphinx.__version__ < "5":
- raise RuntimeError("Sphinx 5 or newer is required")
+from . import __version__
from .docscrape_sphinx import get_doc_object
-from .validate import validate, ERROR_MSGS, get_validation_checks
+from .validate import get_validation_checks, validate
from .xref import DEFAULT_LINKS
-from . import __version__
logger = logging.getLogger(__name__)
@@ -56,13 +56,15 @@ 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()
for line in lines:
line = line.strip()
- m = re.match(r"^\.\. +\[(%s)\]" % app.config.numpydoc_citation_re, line, re.I)
+ m = re.match(
+ r"^\.\. +\[(%s)\]" % app.config.numpydoc_citation_re, line, re.IGNORECASE
+ )
if m:
references.add(m.group(1))
@@ -86,7 +88,7 @@ def _is_cite_in_numpydoc_docstring(citation_node):
section_node = citation_node.parent
def is_docstring_section(node):
- return isinstance(node, (section, desc_content))
+ return isinstance(node, section | desc_content)
while not is_docstring_section(section_node):
section_node = section_node.parent
@@ -116,7 +118,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):
@@ -143,7 +145,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):
@@ -163,7 +165,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
@@ -183,15 +185,51 @@ def mangle_docstrings(app, what, name, obj, options, lines):
"xref_aliases": app.config.numpydoc_xref_aliases_complete,
"xref_ignore": app.config.numpydoc_xref_ignore,
}
-
- cfg.update(options or {})
+ # TODO: Find a cleaner way to take care of this change away from dict
+ # https://github.com/sphinx-doc/sphinx/issues/13942
+ try:
+ cfg.update(options or {})
+ except TypeError:
+ cfg.update(options.__dict__ or {})
u_NL = "\n"
if what == "module":
# Strip top title
pattern = "^\\s*[#*=]{4,}\\n[a-z0-9 -]+\\n[#*=]{4,}\\s*"
- title_re = re.compile(pattern, re.I | re.S)
+ 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
+ and app.config.numpydoc_validation_checks
+ ):
+ excluder = app.config.numpydoc_validation_files_excluder
+ module = inspect.getmodule(obj)
+ try:
+ # Get the module relative path from the name
+ if module:
+ mod_path = Path(module.__file__)
+ package_rel_path = mod_path.parent.relative_to(
+ Path(
+ importlib.import_module(
+ module.__name__.split(".")[0]
+ ).__file__
+ ).parent
+ ).as_posix()
+ module_file = mod_path.as_posix().replace(
+ mod_path.parent.as_posix(), ""
+ )
+ path = package_rel_path + module_file
+ else:
+ path = None
+ except AttributeError as e:
+ path = None
+
+ if path and excluder and excluder.search(path):
+ # Skip validation for this object.
+ return
+
try:
doc = get_doc_object(
obj, what, u_NL.join(lines), config=cfg, builder=app.builder
@@ -241,7 +279,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__")
@@ -250,10 +288,10 @@ def mangle_signature(app, what, name, obj, options, sig, retann):
return "", ""
if not (isinstance(obj, Callable) or hasattr(obj, "__argspec_is_invalid_")):
- return
+ return None
if not hasattr(obj, "__doc__"):
- return
+ return None
doc = get_doc_object(obj, config={"show_class_members": False})
sig = doc["Signature"] or _clean_text_signature(
getattr(obj, "__text_signature__", None)
@@ -275,9 +313,9 @@ 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 # probably called by nose, better bail out
+ return None # probably called by nose, better bail out
global get_doc_object
get_doc_object = get_doc_object_
@@ -298,9 +336,10 @@ def setup(app, get_doc_object_=get_doc_object):
app.add_config_value("numpydoc_attributes_as_param_list", True, True)
app.add_config_value("numpydoc_xref_param_type", False, True)
app.add_config_value("numpydoc_xref_aliases", dict(), True)
- app.add_config_value("numpydoc_xref_ignore", set(), True)
+ 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
@@ -311,7 +350,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
@@ -344,6 +383,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)
@@ -419,12 +473,18 @@ def match_items(lines, content_old):
Examples
--------
- >>> lines = ['', 'A', '', 'B', ' ', '', 'C', 'D']
- >>> lines_old = ['a', '', '', 'b', '', 'c']
- >>> items_old = [('file1.py', 0), ('file1.py', 1), ('file1.py', 2),
- ... ('file2.py', 0), ('file2.py', 1), ('file2.py', 2)]
+ >>> lines = ["", "A", "", "B", " ", "", "C", "D"]
+ >>> lines_old = ["a", "", "", "b", "", "c"]
+ >>> items_old = [
+ ... ("file1.py", 0),
+ ... ("file1.py", 1),
+ ... ("file1.py", 2),
+ ... ("file2.py", 0),
+ ... ("file2.py", 1),
+ ... ("file2.py", 2),
+ ... ]
>>> content_old = ViewList(lines_old, items=items_old)
- >>> match_items(lines, content_old) # doctest: +NORMALIZE_WHITESPACE
+ >>> match_items(lines, content_old) # doctest: +NORMALIZE_WHITESPACE
[('file1.py', 0), ('file1.py', 0), ('file2.py', 0), ('file2.py', 0),
('file2.py', 2), ('file2.py', 2), ('file2.py', 2), ('file2.py', 2)]
>>> # first 2 ``lines`` are matched to 'a', second 2 to 'b', rest to 'c'
diff --git a/numpydoc/templates/numpydoc_docstring.rst b/numpydoc/templates/numpydoc_docstring.rst
index 79ab1f8e..f6b6a0ae 100644
--- a/numpydoc/templates/numpydoc_docstring.rst
+++ b/numpydoc/templates/numpydoc_docstring.rst
@@ -2,6 +2,8 @@
{{summary}}
{{extended_summary}}
{{parameters}}
+{{attributes}}
+{{methods}}
{{returns}}
{{yields}}
{{receives}}
@@ -13,5 +15,3 @@
{{notes}}
{{references}}
{{examples}}
-{{attributes}}
-{{methods}}
diff --git a/numpydoc/tests/hooks/example_module.py b/numpydoc/tests/hooks/example_module.py
index b36f519a..9f75bdf0 100644
--- a/numpydoc/tests/hooks/example_module.py
+++ b/numpydoc/tests/hooks/example_module.py
@@ -3,7 +3,6 @@
def some_function(name):
"""Welcome to some function."""
- pass
class MyClass:
@@ -23,11 +22,9 @@ def do_something(self, *args, **kwargs):
----------
*args
"""
- pass
- def process(self):
- """Process stuff."""
- pass
+ def create(self):
+ """Creates stuff."""
class NewClass:
diff --git a/numpydoc/tests/hooks/test_validate_hook.py b/numpydoc/tests/hooks/test_validate_hook.py
index 5c635dfb..b235313b 100644
--- a/numpydoc/tests/hooks/test_validate_hook.py
+++ b/numpydoc/tests/hooks/test_validate_hook.py
@@ -5,7 +5,7 @@
import pytest
-from numpydoc.hooks.validate_docstrings import main
+from numpydoc.hooks.validate_docstrings import run_hook
@pytest.fixture
@@ -26,59 +26,47 @@ def test_validate_hook(example_module, config, capsys):
expected = inspect.cleandoc(
"""
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | file | item | check | description |
- +===========================================+=====================================+=========+====================================================+
- | numpydoc/tests/hooks/example_module.py:1 | example_module | EX01 | No examples section found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | ES01 | No extended summary found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | SA01 | See Also section not found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | EX01 | No examples section found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:9 | example_module.MyClass | ES01 | No extended summary found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:9 | example_module.MyClass | SA01 | See Also section not found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:9 | example_module.MyClass | EX01 | No examples section found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:12 | example_module.MyClass.__init__ | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | ES01 | No extended summary found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR01 | Parameters {'**kwargs'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR07 | Parameter "*args" has no description |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | SA01 | See Also section not found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | EX01 | No examples section found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:28 | example_module.MyClass.process | SS05 | Summary must start with infinitive verb, not third |
- | | | | person (e.g. use "Generate" instead of |
- | | | | "Generates") |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:28 | example_module.MyClass.process | ES01 | No extended summary found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:28 | example_module.MyClass.process | SA01 | See Also section not found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:28 | example_module.MyClass.process | EX01 | No examples section found |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:33 | example_module.NewClass | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
+ numpydoc/tests/hooks/example_module.py:4: ES01 No extended summary found
+
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+
+ numpydoc/tests/hooks/example_module.py:4: SA01 See Also section not found
+
+ numpydoc/tests/hooks/example_module.py:4: EX01 No examples section found
+
+ numpydoc/tests/hooks/example_module.py:8: ES01 No extended summary found
+
+ numpydoc/tests/hooks/example_module.py:8: SA01 See Also section not found
+
+ numpydoc/tests/hooks/example_module.py:8: EX01 No examples section found
+
+ numpydoc/tests/hooks/example_module.py:11: GL08 The object does not have a docstring
+
+ numpydoc/tests/hooks/example_module.py:17: ES01 No extended summary found
+
+ numpydoc/tests/hooks/example_module.py:17: PR01 Parameters {'**kwargs'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR07 Parameter "*args" has no description
+
+ numpydoc/tests/hooks/example_module.py:17: SA01 See Also section not found
+
+ numpydoc/tests/hooks/example_module.py:17: EX01 No examples section found
+
+ numpydoc/tests/hooks/example_module.py:26: SS05 Summary must start with infinitive verb, not third person (e.g. use "Generate" instead of "Generates")
+
+ numpydoc/tests/hooks/example_module.py:26: ES01 No extended summary found
+
+ numpydoc/tests/hooks/example_module.py:26: SA01 See Also section not found
+
+ numpydoc/tests/hooks/example_module.py:26: EX01 No examples section found
+
+ numpydoc/tests/hooks/example_module.py:30: GL08 The object does not have a docstring
"""
)
- args = [example_module]
- if config:
- args.append(f"--{config=}")
-
- return_code = main(args)
+ return_code = run_hook([example_module], config=config)
assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ assert capsys.readouterr().err.strip() == expected
def test_validate_hook_with_ignore(example_module, capsys):
@@ -89,29 +77,24 @@ def test_validate_hook_with_ignore(example_module, capsys):
expected = inspect.cleandoc(
"""
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | file | item | check | description |
- +===========================================+=====================================+=========+====================================================+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:12 | example_module.MyClass.__init__ | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR01 | Parameters {'**kwargs'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR07 | Parameter "*args" has no description |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:28 | example_module.MyClass.process | SS05 | Summary must start with infinitive verb, not third |
- | | | | person (e.g. use "Generate" instead of |
- | | | | "Generates") |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
- | numpydoc/tests/hooks/example_module.py:33 | example_module.NewClass | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------------------+
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+
+ numpydoc/tests/hooks/example_module.py:11: GL08 The object does not have a docstring
+
+ numpydoc/tests/hooks/example_module.py:17: PR01 Parameters {'**kwargs'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR07 Parameter "*args" has no description
+
+ numpydoc/tests/hooks/example_module.py:26: SS05 Summary must start with infinitive verb, not third person (e.g. use "Generate" instead of "Generates")
+
+ numpydoc/tests/hooks/example_module.py:30: GL08 The object does not have a docstring
"""
)
- return_code = main([example_module, "--ignore", "ES01", "SA01", "EX01"])
+ return_code = run_hook([example_module], ignore=["ES01", "SA01", "EX01"])
+
assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ assert capsys.readouterr().err.strip() == expected
def test_validate_hook_with_toml_config(example_module, tmp_path, capsys):
@@ -133,9 +116,7 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys):
]
exclude = '\\.__init__$'
override_SS05 = [
- '^Process',
- '^Assess',
- '^Access',
+ '^Creates',
]
"""
)
@@ -143,23 +124,19 @@ def test_validate_hook_with_toml_config(example_module, tmp_path, capsys):
expected = inspect.cleandoc(
"""
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | file | item | check | description |
- +===========================================+=====================================+=========+========================================+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR01 | Parameters {'**kwargs'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR07 | Parameter "*args" has no description |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:33 | example_module.NewClass | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR01 Parameters {'**kwargs'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR07 Parameter "*args" has no description
+
+ numpydoc/tests/hooks/example_module.py:30: GL08 The object does not have a docstring
"""
)
- return_code = main([example_module, "--config", str(tmp_path)])
+ return_code = run_hook([example_module], config=tmp_path)
assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ assert capsys.readouterr().err.strip() == expected
def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys):
@@ -175,42 +152,26 @@ def test_validate_hook_with_setup_cfg(example_module, tmp_path, capsys):
[tool:numpydoc_validation]
checks = all,EX01,SA01,ES01
exclude = \\.__init__$
- override_SS05 = ^Process,^Assess,^Access
+ override_SS05 = ^Creates
"""
)
)
expected = inspect.cleandoc(
"""
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | file | item | check | description |
- +===========================================+=====================================+=========+========================================+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR01 | Parameters {'**kwargs'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR07 | Parameter "*args" has no description |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:33 | example_module.NewClass | GL08 | The object does not have a docstring |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- """
- )
-
- return_code = main([example_module, "--config", str(tmp_path)])
- assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+ numpydoc/tests/hooks/example_module.py:17: PR01 Parameters {'**kwargs'} not documented
-def test_validate_hook_help(capsys):
- """Test that help section is displaying."""
+ numpydoc/tests/hooks/example_module.py:17: PR07 Parameter "*args" has no description
- with pytest.raises(SystemExit):
- return_code = main(["--help"])
- assert return_code == 0
+ numpydoc/tests/hooks/example_module.py:30: GL08 The object does not have a docstring
+ """
+ )
- out = capsys.readouterr().out
- assert "--ignore" in out
- assert "--config" in out
+ return_code = run_hook([example_module], config=tmp_path)
+ assert return_code == 1
+ assert capsys.readouterr().err.strip() == expected
def test_validate_hook_exclude_option_pyproject(example_module, tmp_path, capsys):
@@ -235,9 +196,7 @@ def test_validate_hook_exclude_option_pyproject(example_module, tmp_path, capsys
'\.__init__$',
]
override_SS05 = [
- '^Process',
- '^Assess',
- '^Access',
+ '^Creates',
]
"""
)
@@ -245,19 +204,15 @@ def test_validate_hook_exclude_option_pyproject(example_module, tmp_path, capsys
expected = inspect.cleandoc(
"""
- +-------------------------------------------+------------------------------+---------+--------------------------------------+
- | file | item | check | description |
- +===========================================+==============================+=========+======================================+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+------------------------------+---------+--------------------------------------+
- | numpydoc/tests/hooks/example_module.py:33 | example_module.NewClass | GL08 | The object does not have a docstring |
- +-------------------------------------------+------------------------------+---------+--------------------------------------+
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+
+ numpydoc/tests/hooks/example_module.py:30: GL08 The object does not have a docstring
"""
)
- return_code = main([example_module, "--config", str(tmp_path)])
+ return_code = run_hook([example_module], config=tmp_path)
assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ assert capsys.readouterr().err.strip() == expected
def test_validate_hook_exclude_option_setup_cfg(example_module, tmp_path, capsys):
@@ -273,25 +228,87 @@ def test_validate_hook_exclude_option_setup_cfg(example_module, tmp_path, capsys
[tool:numpydoc_validation]
checks = all,EX01,SA01,ES01
exclude = \\.NewClass$,\\.__init__$
- override_SS05 = ^Process,^Assess,^Access
+ override_SS05 = ^Creates
"""
)
)
expected = inspect.cleandoc(
"""
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | file | item | check | description |
- +===========================================+=====================================+=========+========================================+
- | numpydoc/tests/hooks/example_module.py:4 | example_module.some_function | PR01 | Parameters {'name'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR01 | Parameters {'**kwargs'} not documented |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
- | numpydoc/tests/hooks/example_module.py:18 | example_module.MyClass.do_something | PR07 | Parameter "*args" has no description |
- +-------------------------------------------+-------------------------------------+---------+----------------------------------------+
+ numpydoc/tests/hooks/example_module.py:4: PR01 Parameters {'name'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR01 Parameters {'**kwargs'} not documented
+
+ numpydoc/tests/hooks/example_module.py:17: PR07 Parameter "*args" has no description
"""
)
- return_code = main([example_module, "--config", str(tmp_path)])
+ return_code = run_hook([example_module], config=tmp_path)
assert return_code == 1
- assert capsys.readouterr().err.rstrip() == expected
+ 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.
diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py
index 5077c425..cf7c0de9 100644
--- a/numpydoc/tests/test_docscrape.py
+++ b/numpydoc/tests/test_docscrape.py
@@ -1,24 +1,22 @@
-from collections import namedtuple
-from copy import deepcopy
import re
import textwrap
import warnings
+from collections import namedtuple
+from copy import deepcopy
import jinja2
+import pytest
+from pytest import warns as assert_warns
-from numpydoc.numpydoc import update_config
-from numpydoc.xref import DEFAULT_LINKS
-from numpydoc.docscrape import NumpyDocString, FunctionDoc, ClassDoc, ParseError
+from numpydoc.docscrape import ClassDoc, FunctionDoc, NumpyDocString
from numpydoc.docscrape_sphinx import (
- SphinxDocString,
SphinxClassDoc,
+ SphinxDocString,
SphinxFunctionDoc,
get_doc_object,
)
-import pytest
-from pytest import raises as assert_raises
-from pytest import warns as assert_warns
-
+from numpydoc.numpydoc import update_config
+from numpydoc.xref import DEFAULT_LINKS
doc_txt = """\
numpy.multivariate_normal(mean, cov, shape=None, spam=None)
@@ -189,7 +187,9 @@ def test_extended_summary(doc):
def test_parameters(doc):
assert len(doc["Parameters"]) == 4
names = [n for n, _, _ in doc["Parameters"]]
- assert all(a == b for a, b in zip(names, ["mean", "cov", "shape"]))
+ assert all(
+ a == b for a, b in zip(names, ["mean", "cov", "shape", "dtype"], strict=True)
+ )
arg, arg_type, desc = doc["Parameters"][1]
assert arg_type == "(N, N) ndarray"
@@ -211,7 +211,7 @@ def test_parameters(doc):
def test_other_parameters(doc):
assert len(doc["Other Parameters"]) == 1
assert [n for n, _, _ in doc["Other Parameters"]] == ["spam"]
- arg, arg_type, desc = doc["Other Parameters"][0]
+ _arg, arg_type, desc = doc["Other Parameters"][0]
assert arg_type == "parrot"
assert desc[0].startswith("A parrot off its mortal coil")
@@ -244,7 +244,9 @@ def test_yields():
("b", "int", "bananas."),
("", "int", "unknowns."),
]
- for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth):
+ for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(
+ section, truth, strict=True
+ ):
assert arg == arg_
assert arg_type == arg_type_
assert desc[0].startswith("The number of")
@@ -255,7 +257,9 @@ def test_sent():
section = doc_sent["Receives"]
assert len(section) == 2
truth = [("b", "int", "bananas."), ("c", "int", "oranges.")]
- for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth):
+ for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(
+ section, truth, strict=True
+ ):
assert arg == arg_
assert arg_type == arg_type_
assert desc[0].startswith("The number of")
@@ -279,7 +283,9 @@ def test_returnyield():
The number of bananas.
"""
- assert_raises(ValueError, NumpyDocString, doc_text)
+ doc = NumpyDocString(doc_text)
+ assert len(doc["Returns"]) == 1
+ assert len(doc["Yields"]) == 2
def test_section_twice():
@@ -314,11 +320,9 @@ class Dummy:
def spam(self, a, b):
"""Spam\n\nSpam spam."""
- pass
def ham(self, c, d):
"""Cheese\n\nNo cheese."""
- pass
def dummy_func(arg):
"""
@@ -376,7 +380,7 @@ def line_by_line_compare(a, b, n_lines=None):
a = [l.rstrip() for l in _strip_blank_lines(a).split("\n")][:n_lines]
b = [l.rstrip() for l in _strip_blank_lines(b).split("\n")][:n_lines]
assert len(a) == len(b)
- for ii, (aa, bb) in enumerate(zip(a, b)):
+ for ii, (aa, bb) in enumerate(zip(a, b, strict=True)):
assert aa == bb
@@ -895,8 +899,6 @@ class Dummy:
func_d
"""
- pass
-
s = str(FunctionDoc(Dummy, role="func"))
assert ":func:`func_a`, :func:`func_b`" in s
assert " some relationship" in s
@@ -939,8 +941,6 @@ class BadSection:
This class has a nope section.
"""
- pass
-
with pytest.warns(UserWarning, match="Unknown section Mope") as record:
NumpyDocString(doc_text)
assert len(record) == 1
@@ -1083,11 +1083,9 @@ class Dummy:
def spam(self, a, b):
"""Spam\n\nSpam spam."""
- pass
def ham(self, c, d):
"""Cheese\n\nNo cheese."""
- pass
@property
def spammity(self):
@@ -1097,8 +1095,6 @@ def spammity(self):
class Ignorable:
"""local class, to be ignored"""
- pass
-
for cls in (ClassDoc, SphinxClassDoc):
doc = cls(Dummy, config=dict(show_class_members=False))
assert "Methods" not in str(doc), (cls, str(doc))
@@ -1126,11 +1122,9 @@ class SubDummy(Dummy):
def ham(self, c, d):
"""Cheese\n\nNo cheese.\nOverloaded Dummy.ham"""
- pass
def bar(self, a, b):
"""Bar\n\nNo bar"""
- pass
for cls in (ClassDoc, SphinxClassDoc):
doc = cls(
@@ -1215,6 +1209,17 @@ def test_duplicate_signature():
b
c
+ Other Parameters
+ ----------------
+
+ another parameter : str
+ This parameter is less important.
+
+ Notes
+ -----
+
+ Some notes about the class.
+
Examples
--------
For usage examples, see `ode`.
@@ -1235,10 +1240,6 @@ def test_class_members_doc():
jac : callable ``jac(t, y, *jac_args)``
Bbb.
- Examples
- --------
- For usage examples, see `ode`.
-
Attributes
----------
t : float
@@ -1263,6 +1264,19 @@ def test_class_members_doc():
b
c
+ Other Parameters
+ ----------------
+ another parameter : str
+ This parameter is less important.
+
+ Notes
+ -----
+ Some notes about the class.
+
+ Examples
+ --------
+ For usage examples, see `ode`.
+
""",
)
@@ -1272,7 +1286,7 @@ class Foo:
@property
def an_attribute(self):
"""Test attribute"""
- return None
+ return
@property
def no_docstring(self):
@@ -1286,12 +1300,12 @@ def no_docstring2(self):
def multiline_sentence(self):
"""This is a
sentence. It spans multiple lines."""
- return None
+ return
@property
def midword_period(self):
"""The sentence for numpy.org."""
- return None
+ return
@property
def no_period(self):
@@ -1300,7 +1314,7 @@ def no_period(self):
Apparently.
"""
- return None
+ return
doc = SphinxClassDoc(Foo, class_doc_txt)
line_by_line_compare(
@@ -1316,10 +1330,6 @@ def no_period(self):
**jac** : callable ``jac(t, y, *jac_args)``
Bbb.
- .. rubric:: Examples
-
- For usage examples, see `ode`.
-
:Attributes:
**t** : float
@@ -1357,6 +1367,19 @@ def no_period(self):
**c**
===== ==========
+ :Other Parameters:
+
+ **another parameter** : str
+ This parameter is less important.
+
+ .. rubric:: Notes
+
+ Some notes about the class.
+
+ .. rubric:: Examples
+
+ For usage examples, see `ode`.
+
""",
)
@@ -1376,7 +1399,7 @@ class Foo:
@property
def an_attribute(self):
"""Test attribute"""
- return None
+ return
attr_doc = """:Attributes:
@@ -1576,11 +1599,12 @@ 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)
- for key in xref_aliases:
- xref_aliases_complete[key] = xref_aliases[key]
+ for key, val in xref_aliases.items():
+ xref_aliases_complete[key] = val
config = Config(xref_aliases, xref_aliases_complete)
app = namedtuple("config", "config")(config)
update_config(app)
@@ -1641,6 +1665,63 @@ def val(self):
assert class_docstring["Attributes"][0].name == "val"
+def test_namedtuple_no_duplicate_attributes():
+ """
+ Ensure that attributes of namedtuples are not duplicated in the docstring.
+
+ See gh-257
+ """
+ from collections import namedtuple
+
+ foo = namedtuple("Foo", ("bar", "baz"))
+
+ # Create the SphinxClassDoc object via get_doc_object
+ sds = get_doc_object(foo)
+ assert sds["Attributes"] == []
+
+
+def test_namedtuple_class_docstring():
+ """Ensure that class docstring is preserved when inheriting from namedtuple.
+
+ See gh-257
+ """
+ from collections import namedtuple
+
+ foo = namedtuple("Foo", ("bar", "baz"))
+
+ class MyFoo(foo):
+ """MyFoo's class docstring"""
+
+ # Create the SphinxClassDoc object via get_doc_object
+ sds = get_doc_object(MyFoo)
+ assert sds["Summary"] == ["MyFoo's class docstring"]
+
+ # Example dataclass where constructor params are documented explicit.
+ # Parameter names/descriptions should be included in the docstring, but
+ # should not result in a duplicated `Attributes` section
+ class MyFooWithParams(foo):
+ """
+ MyFoo's class docstring
+
+ Parameters
+ ----------
+ bar : str
+ The bar attribute
+ baz : str
+ The baz attribute
+ """
+
+ bar: str
+ baz: str
+
+ sds = get_doc_object(MyFooWithParams)
+ assert "MyFoo's class docstring" in sds["Summary"]
+ assert len(sds["Attributes"]) == 0
+ assert len(sds["Parameters"]) == 2
+ assert sds["Parameters"][0].desc[0] == "The bar attribute"
+ assert sds["Parameters"][1].desc[0] == "The baz attribute"
+
+
if __name__ == "__main__":
import pytest
diff --git a/numpydoc/tests/test_full.py b/numpydoc/tests/test_full.py
index c4ae1340..9ab241fa 100644
--- a/numpydoc/tests/test_full.py
+++ b/numpydoc/tests/test_full.py
@@ -1,13 +1,12 @@
import os.path as op
import re
import shutil
-from packaging import version
import pytest
-import sphinx
+from docutils import __version__ as docutils_version
+from packaging import version
from sphinx.application import Sphinx
from sphinx.util.docutils import docutils_namespace
-from docutils import __version__ as docutils_version
# Test framework adapted from sphinx-gallery (BSD 3-clause)
diff --git a/numpydoc/tests/test_main.py b/numpydoc/tests/test_main.py
index 1f90b967..e07e7df2 100644
--- a/numpydoc/tests/test_main.py
+++ b/numpydoc/tests/test_main.py
@@ -1,8 +1,11 @@
-import sys
+import inspect
import io
+import sys
+
import pytest
+
import numpydoc
-import numpydoc.__main__
+import numpydoc.cli
def _capture_stdout(func_name, *args, **kwargs):
@@ -30,7 +33,7 @@ def _capture_stdout(func_name, *args, **kwargs):
Examples
--------
- >>> _capture_stdout(print, 'hello world')
+ >>> _capture_stdout(print, "hello world")
'hello world'
"""
f = io.StringIO()
@@ -50,7 +53,6 @@ def _docstring_with_errors():
----------
made_up_param : str
"""
- pass
def _invalid_docstring():
@@ -61,45 +63,43 @@ def _invalid_docstring():
--------
: this is invalid
"""
- pass
def test_renders_package_docstring():
- out = _capture_stdout(numpydoc.__main__.render_object, "numpydoc")
+ out = _capture_stdout(numpydoc.cli.render_object, "numpydoc")
assert out.startswith("This package provides the numpydoc Sphinx")
-def test_renders_module_docstring():
- out = _capture_stdout(numpydoc.__main__.render_object, "numpydoc.__main__")
- assert out.startswith("Implementing `python -m numpydoc` functionality.")
+def test_renders_module_docstring(capsys):
+ numpydoc.cli.main(["render", "numpydoc.cli"])
+ out = capsys.readouterr().out.strip("\n\r")
+ assert out.startswith(numpydoc.cli.__doc__)
def test_renders_function_docstring():
out = _capture_stdout(
- numpydoc.__main__.render_object, "numpydoc.tests.test_main._capture_stdout"
+ numpydoc.cli.render_object, "numpydoc.tests.test_main._capture_stdout"
)
assert out.startswith("Return stdout of calling")
def test_render_object_returns_correct_exit_status():
- exit_status = numpydoc.__main__.render_object(
- "numpydoc.tests.test_main._capture_stdout"
- )
+ exit_status = numpydoc.cli.render_object("numpydoc.tests.test_main._capture_stdout")
assert exit_status == 0
with pytest.raises(ValueError):
- numpydoc.__main__.render_object("numpydoc.tests.test_main._invalid_docstring")
+ numpydoc.cli.render_object("numpydoc.tests.test_main._invalid_docstring")
def test_validate_detects_errors():
out = _capture_stdout(
- numpydoc.__main__.validate_object,
+ numpydoc.cli.validate_object,
"numpydoc.tests.test_main._docstring_with_errors",
)
assert "SS02" in out
assert "Summary does not start with a capital letter" in out
- exit_status = numpydoc.__main__.validate_object(
+ exit_status = numpydoc.cli.validate_object(
"numpydoc.tests.test_main._docstring_with_errors"
)
assert exit_status > 0
@@ -107,11 +107,39 @@ def test_validate_detects_errors():
def test_validate_perfect_docstring():
out = _capture_stdout(
- numpydoc.__main__.validate_object, "numpydoc.tests.test_main._capture_stdout"
+ numpydoc.cli.validate_object, "numpydoc.tests.test_main._capture_stdout"
)
assert out == ""
- exit_status = numpydoc.__main__.validate_object(
+ exit_status = numpydoc.cli.validate_object(
"numpydoc.tests.test_main._capture_stdout"
)
assert exit_status == 0
+
+
+@pytest.mark.parametrize("args", [[], ["--ignore", "SS03"]])
+def test_lint(capsys, args):
+ argv = ["lint", "numpydoc/__main__.py"] + args
+ if args:
+ expected = ""
+ expected_status = 0
+ else:
+ expected = "numpydoc/__main__.py:1: SS03 Summary does not end with a period"
+ expected_status = 1
+
+ return_status = numpydoc.cli.main(argv)
+ err = capsys.readouterr().err.strip("\n\r")
+ assert err == expected
+ assert return_status == expected_status
+
+
+def test_lint_help(capsys):
+ """Test that lint help section is displaying."""
+
+ with pytest.raises(SystemExit):
+ return_code = numpydoc.cli.main(["lint", "--help"])
+ assert return_code == 0
+
+ out = capsys.readouterr().out
+ assert "--ignore" in out
+ assert "--config" in out
diff --git a/numpydoc/tests/test_numpydoc.py b/numpydoc/tests/test_numpydoc.py
index 8df2205c..f879d7fb 100644
--- a/numpydoc/tests/test_numpydoc.py
+++ b/numpydoc/tests/test_numpydoc.py
@@ -1,20 +1,20 @@
-import pytest
from collections import defaultdict
+from copy import deepcopy
from io import StringIO
from pathlib import PosixPath
-from copy import deepcopy
+import pytest
from docutils import nodes
+from sphinx.ext.autodoc import ALL
+from sphinx.util import logging
from numpydoc.numpydoc import (
- mangle_docstrings,
_clean_text_signature,
- update_config,
clean_backrefs,
+ mangle_docstrings,
+ update_config,
)
from numpydoc.xref import DEFAULT_LINKS
-from sphinx.ext.autodoc import ALL
-from sphinx.util import logging
class MockConfig:
@@ -31,11 +31,13 @@ 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()
class MockBuilder:
config = MockConfig()
+ _translator = None
class MockApp:
@@ -49,6 +51,7 @@ def __init__(self):
self.verbosity = 2
self._warncount = 0
self.warningiserror = False
+ self._exception_on_warning = False
def test_mangle_docstrings_basic():
@@ -150,7 +153,6 @@ def _function_without_seealso_and_examples():
Expect SA01 and EX01 errors if validation enabled.
"""
- pass
return _function_without_seealso_and_examples
@@ -286,6 +288,61 @@ 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\.py$",
+ ],
+ 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
+ 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
diff --git a/numpydoc/tests/test_validate.py b/numpydoc/tests/test_validate.py
index d41e4bd0..b8e17f8d 100644
--- a/numpydoc/tests/test_validate.py
+++ b/numpydoc/tests/test_validate.py
@@ -1,13 +1,14 @@
-import pytest
-import sys
import warnings
from contextlib import nullcontext
-from functools import cached_property, partial
-from inspect import getsourcelines, getsourcefile
+from functools import cached_property, partial, wraps
+from inspect import getsourcefile, getsourcelines
-from numpydoc import validate
-import numpydoc.tests
+import pytest
+import numpydoc.tests
+from numpydoc import validate
+from numpydoc.docscrape import get_doc_object
+from numpydoc.validate import Validator
validate_one = validate.validate
@@ -149,7 +150,6 @@ class GoodDocStrings:
def one_liner(self):
"""Allow one liner docstrings (including quotes)."""
# This should fail, but not because of the position of the quotes
- pass
def plot(self, kind, color="blue", **kwargs):
"""
@@ -163,7 +163,7 @@ def plot(self, kind, color="blue", **kwargs):
kind : str
Kind of matplotlib plot, e.g.::
- 'foo'
+ "foo"
color : str, default 'blue'
Color name or rgb code.
@@ -179,7 +179,6 @@ def plot(self, kind, color="blue", **kwargs):
--------
>>> result = 1 + 1
"""
- pass
def swap(self, arr, i, j, *args, **kwargs):
"""
@@ -205,7 +204,6 @@ def swap(self, arr, i, j, *args, **kwargs):
--------
>>> result = 1 + 1
"""
- pass
def sample(self):
"""
@@ -229,7 +227,6 @@ def sample(self):
--------
>>> result = 1 + 1
"""
- pass
def random_letters(self):
"""
@@ -255,7 +252,6 @@ def random_letters(self):
--------
>>> result = 1 + 1
"""
- pass
def sample_values(self):
"""
@@ -277,7 +273,6 @@ def sample_values(self):
--------
>>> result = 1 + 1
"""
- pass
def head(self):
"""
@@ -413,7 +408,6 @@ def contains(self, pat, case=True, na=float("NaN")):
>>> s * 2
50
"""
- pass
def mode(self, axis, numeric_only):
"""
@@ -445,7 +439,6 @@ def mode(self, axis, numeric_only):
--------
>>> result = 1 + 1
"""
- pass
def good_imports(self):
"""
@@ -465,7 +458,6 @@ def good_imports(self):
>>> datetime.MAXYEAR
9999
"""
- pass
def no_returns(self):
"""
@@ -482,7 +474,6 @@ def no_returns(self):
--------
>>> result = 1 + 1
"""
- pass
def empty_returns(self):
"""
@@ -507,7 +498,7 @@ def say_hello():
if True:
return
else:
- return None
+ return
def warnings(self):
"""
@@ -528,7 +519,6 @@ def warnings(self):
--------
>>> result = 1 + 1
"""
- pass
def multiple_variables_on_one_line(self, matrix, a, b, i, j):
"""
@@ -554,7 +544,6 @@ def multiple_variables_on_one_line(self, matrix, a, b, i, j):
--------
>>> result = 1 + 1
"""
- pass
def other_parameters(self, param1, param2):
"""
@@ -581,7 +570,6 @@ def other_parameters(self, param1, param2):
--------
>>> result = 1 + 1
"""
- pass
def valid_options_in_parameter_description_sets(self, bar):
"""
@@ -627,7 +615,6 @@ def parameters_with_trailing_underscores(self, str_):
--------
>>> result = 1 + 1
"""
- pass
def parameter_with_wrong_types_as_substrings(self, a, b, c, d, e, f):
r"""
@@ -661,7 +648,6 @@ def parameter_with_wrong_types_as_substrings(self, a, b, c, d, e, f):
--------
>>> result = 1 + 1
"""
- pass
class BadGenericDocStrings:
@@ -692,7 +678,6 @@ def astype(self, dtype):
Verb in third-person of the present simple, should be infinitive.
"""
- pass
def astype1(self, dtype):
"""
@@ -700,7 +685,6 @@ def astype1(self, dtype):
Does not start with verb.
"""
- pass
def astype2(self, dtype):
"""
@@ -708,7 +692,6 @@ def astype2(self, dtype):
Missing dot at the end.
"""
- pass
def astype3(self, dtype):
"""
@@ -717,7 +700,6 @@ def astype3(self, dtype):
Summary is too verbose and doesn't fit in a single line.
"""
- pass
def two_linebreaks_between_sections(self, foo):
"""
@@ -731,7 +713,6 @@ def two_linebreaks_between_sections(self, foo):
foo : str
Description of foo parameter.
"""
- pass
def linebreak_at_end_of_docstring(self, foo):
"""
@@ -745,7 +726,6 @@ def linebreak_at_end_of_docstring(self, foo):
Description of foo parameter.
"""
- pass
def plot(self, kind, **kwargs):
"""
@@ -769,7 +749,6 @@ def plot(self, kind, **kwargs):
kind: str
kind of matplotlib plot
"""
- pass
def unknown_section(self):
"""
@@ -791,7 +770,7 @@ def sections_in_wrong_order(self):
Examples
--------
- >>> print('So far Examples is good, as it goes before Parameters')
+ >>> print("So far Examples is good, as it goes before Parameters")
So far Examples is good, as it goes before Parameters
See Also
@@ -834,7 +813,6 @@ def directives_without_two_colons(self, first, second):
.. deprecated 0.00.0
"""
- pass
class WarnGenericFormat:
@@ -851,7 +829,6 @@ def too_short_header_underline(self, a, b):
a, b : int
Foo bar baz.
"""
- pass
class BadSummaries:
@@ -877,19 +854,16 @@ def wrong_line(self):
"""Quotes are on the wrong line.
Both opening and closing."""
- pass
def no_punctuation(self):
"""
Has the right line but forgets punctuation
"""
- pass
def no_capitalization(self):
"""
provides a lowercase summary.
"""
- pass
def no_infinitive(self):
"""
@@ -1021,7 +995,6 @@ def blank_lines(self, kind):
kind : str
Foo bar baz.
"""
- pass
def integer_parameter(self, kind):
"""
@@ -1032,7 +1005,6 @@ def integer_parameter(self, kind):
kind : integer
Foo bar baz.
"""
- pass
def string_parameter(self, kind):
"""
@@ -1043,7 +1015,6 @@ def string_parameter(self, kind):
kind : string
Foo bar baz.
"""
- pass
def boolean_parameter(self, kind):
"""
@@ -1054,7 +1025,6 @@ def boolean_parameter(self, kind):
kind : boolean
Foo bar baz.
"""
- pass
def list_incorrect_parameter_type(self, kind):
"""
@@ -1065,7 +1035,6 @@ def list_incorrect_parameter_type(self, kind):
kind : list of boolean, integer, float or string
Foo bar baz.
"""
- pass
def bad_parameter_spacing(self, a, b):
"""
@@ -1076,7 +1045,6 @@ def bad_parameter_spacing(self, a, b):
a, b : int
Foo bar baz.
"""
- pass
class BadReturns:
@@ -1170,7 +1138,6 @@ def no_desc(self):
--------
Series.tail
"""
- pass
def desc_no_period(self):
"""
@@ -1182,7 +1149,6 @@ def desc_no_period(self):
Series.iloc : Return a slice of the elements in the Series,
which can also be used to return the first or last n
"""
- pass
def desc_first_letter_lowercase(self):
"""
@@ -1194,7 +1160,6 @@ def desc_first_letter_lowercase(self):
Series.iloc : Return a slice of the elements in the Series,
which can also be used to return the first or last n.
"""
- pass
def prefix_pandas(self):
"""
@@ -1205,7 +1170,6 @@ def prefix_pandas(self):
pandas.Series.rename : Alter Series index labels or name.
DataFrame.head : The first `n` rows of the caller object.
"""
- pass
class BadExamples:
@@ -1213,27 +1177,131 @@ def missing_whitespace_around_arithmetic_operator(self):
"""
Examples
--------
- >>> 2+5
+ >>> 2 + 5
7
"""
- pass
def indentation_is_not_a_multiple_of_four(self):
"""
Examples
--------
>>> if 2 + 5:
- ... pass
+ ... pass
"""
- pass
def missing_whitespace_after_comma(self):
"""
Examples
--------
>>> import datetime
- >>> value = datetime.date(2019,1,1)
+ >>> value = datetime.date(2019, 1, 1)
+ """
+
+
+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
@@ -1323,11 +1391,8 @@ def test_bad_class(self, capsys):
],
)
def test_bad_generic_functions(self, capsys, func):
- with pytest.warns(UserWarning):
- errors = validate_one(
- self._import_path(klass="WarnGenericFormat", func=func) # noqa:F821
- )
- assert "is too short" in w.msg
+ with pytest.warns(UserWarning, match="is too short"):
+ validate_one(self._import_path(klass="WarnGenericFormat", func=func))
@pytest.mark.parametrize(
"func",
@@ -1343,7 +1408,7 @@ def test_bad_generic_functions(self, capsys, func):
)
def test_bad_generic_functions(self, capsys, func):
errors = validate_one(
- self._import_path(klass="BadGenericDocStrings", func=func) # noqa:F821
+ self._import_path(klass="BadGenericDocStrings", func=func)
)["errors"]
assert isinstance(errors, list)
assert errors
@@ -1575,6 +1640,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."""
@@ -1599,24 +1698,20 @@ class DecoratorClass:
def test_no_decorator(self):
"""Test method without decorators."""
- pass
@property
def test_property(self):
"""Test property method."""
- pass
@cached_property
def test_cached_property(self):
"""Test property method."""
- pass
@decorator
@decorator
@decorator
def test_three_decorators(self):
"""Test method with three decorators."""
- pass
class TestValidatorClass:
@@ -1636,15 +1731,12 @@ def test_raises_for_invalid_attribute_name(self, invalid_name):
with pytest.raises(AttributeError, match=msg):
numpydoc.validate.Validator._load_obj(invalid_name)
- # inspect.getsourcelines does not return class decorators for Python 3.8. This was
- # fixed starting with 3.9: https://github.com/python/cpython/issues/60060.
@pytest.mark.parametrize(
["decorated_obj", "def_line"],
[
[
"numpydoc.tests.test_validate.DecoratorClass",
- getsourcelines(DecoratorClass)[-1]
- + (2 if sys.version_info.minor > 8 else 0),
+ getsourcelines(DecoratorClass)[-1] + 2,
],
[
"numpydoc.tests.test_validate.DecoratorClass.test_no_decorator",
@@ -1692,3 +1784,43 @@ def test_source_file_name_with_properties(self, property, file_name):
)
)
assert doc.source_file_name == file_name
+
+
+def test_is_generator_validation_with_decorator():
+ """Ensure that the check for a Yields section when an object is a generator
+ (YD01) works with decorated generators."""
+
+ def tinsel(f):
+ @wraps(f)
+ def wrapper(*args, **kwargs):
+ return f(*args, **kwargs)
+
+ return wrapper
+
+ def foo():
+ """A simple generator"""
+ yield from range(10)
+
+ @tinsel
+ def bar():
+ """Generator wrapped once"""
+ yield from range(10)
+
+ @tinsel
+ @tinsel
+ @tinsel
+ def baz():
+ """Generator wrapped multiple times"""
+ yield from range(10)
+
+ # foo without wrapper is a generator
+ v = Validator(get_doc_object(foo))
+ assert v.is_generator_function
+
+ # Wrapped once
+ v = Validator(get_doc_object(bar))
+ assert v.is_generator_function
+
+ # Wrapped multiple times
+ v = Validator(get_doc_object(baz))
+ assert v.is_generator_function
diff --git a/numpydoc/tests/test_xref.py b/numpydoc/tests/test_xref.py
index b4b31252..2256f759 100644
--- a/numpydoc/tests/test_xref.py
+++ b/numpydoc/tests/test_xref.py
@@ -1,5 +1,6 @@
import pytest
-from numpydoc.xref import make_xref, DEFAULT_LINKS
+
+from numpydoc.xref import DEFAULT_LINKS, make_xref
# Use the default numpydoc link mapping
xref_aliases = DEFAULT_LINKS
@@ -99,7 +100,7 @@
dict[tuple(str, str), int]
:class:`python:dict`\[:class:`python:tuple`\(:class:`python:str`, :class:`python:str`), :class:`python:int`]
-""" # noqa: E501
+"""
data_ignore_obj = r"""
(...) array_like, float, optional
@@ -194,7 +195,7 @@
dict[tuple(str, str), int]
:class:`python:dict`\[:class:`python:tuple`\(:class:`python:str`, :class:`python:str`), :class:`python:int`]
-""" # noqa: E501
+"""
xref_ignore = {"or", "in", "of", "default", "optional"}
diff --git a/numpydoc/tests/tinybuild/conf.py b/numpydoc/tests/tinybuild/conf.py
index fb3b5283..a0719227 100644
--- a/numpydoc/tests/tinybuild/conf.py
+++ b/numpydoc/tests/tinybuild/conf.py
@@ -4,7 +4,7 @@
path = os.path.dirname(__file__)
if path not in sys.path:
sys.path.insert(0, path)
-import numpydoc_test_module # noqa
+import numpydoc_test_module
extensions = [
"sphinx.ext.autodoc",
@@ -15,7 +15,6 @@
autosummary_generate = True
autodoc_default_options = {"inherited-members": None}
source_suffix = ".rst"
-master_doc = "index" # NOTE: will be changed to `root_doc` in sphinx 4
exclude_patterns = ["_build"]
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
diff --git a/numpydoc/tests/tinybuild/numpydoc_test_module.py b/numpydoc/tests/tinybuild/numpydoc_test_module.py
index d303e9e1..d0415714 100644
--- a/numpydoc/tests/tinybuild/numpydoc_test_module.py
+++ b/numpydoc/tests/tinybuild/numpydoc_test_module.py
@@ -37,7 +37,6 @@ class MyClass:
def example(self, x):
"""Example method."""
- pass
def my_function(*args, **kwargs):
@@ -61,4 +60,4 @@ def my_function(*args, **kwargs):
----------
.. [3] https://numpydoc.readthedocs.io
"""
- return None
+ return
diff --git a/numpydoc/validate.py b/numpydoc/validate.py
index 7275758b..18373b77 100644
--- a/numpydoc/validate.py
+++ b/numpydoc/validate.py
@@ -6,8 +6,6 @@
with all the detected errors.
"""
-from copy import deepcopy
-from typing import Dict, List, Set, Optional
import ast
import collections
import functools
@@ -18,13 +16,14 @@
import re
import textwrap
import tokenize
+from copy import deepcopy
+from typing import Any, Dict, List, Set
from .docscrape import get_doc_object
-
DIRECTIVES = ["versionadded", "versionchanged", "deprecated"]
DIRECTIVE_PATTERN = re.compile(
- r"^\s*\.\. ({})(?!::)".format("|".join(DIRECTIVES)), re.I | re.M
+ r"^\s*\.\. ({})(?!::)".format("|".join(DIRECTIVES)), re.IGNORECASE | re.MULTILINE
)
ALLOWED_SECTIONS = [
"Parameters",
@@ -81,8 +80,7 @@
"PR06": 'Parameter "{param_name}" type should use "{right_type}" instead '
'of "{wrong_type}"',
"PR07": 'Parameter "{param_name}" has no description',
- "PR08": 'Parameter "{param_name}" description should start with a '
- "capital letter",
+ "PR08": 'Parameter "{param_name}" description should start with a capital letter',
"PR09": 'Parameter "{param_name}" description should finish with "."',
"PR10": 'Parameter "{param_name}" requires a space before the colon '
"separating the parameter name and type",
@@ -112,14 +110,21 @@
IGNORE_COMMENT_PATTERN = re.compile("(?:.* numpydoc ignore[=|:] ?)(.+)")
+def _unwrap(obj):
+ """Iteratively traverse obj.__wrapped__ until first non-wrapped obj found."""
+ while hasattr(obj, "__wrapped__"):
+ obj = obj.__wrapped__
+ return obj
+
+
# This function gets called once per function/method to be validated.
# We have to balance memory usage with performance here. It shouldn't be too
# bad to store these `dict`s (they should be rare), but to be safe let's keep
# the limit low-ish. This was set by looking at scipy, numpy, matplotlib,
-# and pandas and they had between ~500 and ~1300 .py files as of 2023-08-16.
+# and pandas, and they had between ~500 and ~1300 .py files as of 2023-08-16.
@functools.lru_cache(maxsize=2000)
def extract_ignore_validation_comments(
- filepath: Optional[os.PathLike],
+ filepath: os.PathLike | None,
encoding: str = "utf-8",
) -> Dict[int, List[str]]:
"""
@@ -212,7 +217,7 @@ def error(code, **kwargs):
message : str
Error message with variables replaced.
"""
- return (code, ERROR_MSGS[code].format(**kwargs))
+ return code, ERROR_MSGS[code].format(**kwargs)
class Validator:
@@ -245,10 +250,10 @@ def _load_obj(name):
Examples
--------
- >>> Validator._load_obj('datetime.datetime')
+ >>> Validator._load_obj("datetime.datetime")
"""
- for maxsplit in range(0, name.count(".") + 1):
+ for maxsplit in range(name.count(".") + 1):
module, *func_parts = name.rsplit(".", maxsplit)
try:
obj = importlib.import_module(module)
@@ -271,9 +276,13 @@ def type(self):
def is_function_or_method(self):
return inspect.isfunction(self.obj)
+ @property
+ def is_mod(self):
+ return inspect.ismodule(self.obj)
+
@property
def is_generator_function(self):
- return inspect.isgeneratorfunction(self.obj)
+ return inspect.isgeneratorfunction(_unwrap(self.obj))
@property
def source_file_name(self):
@@ -290,12 +299,18 @@ def source_file_name(self):
except TypeError:
# In some cases the object is something complex like a cython
- # object that can't be easily introspected. An it's better to
+ # object that can't be easily introspected. And it's better to
# return the source code file of the object as None, than crash
pass
else:
return fname
+ # When calling validate, files are parsed twice
+ @staticmethod
+ @functools.lru_cache(maxsize=4000)
+ def _getsourcelines(obj: Any):
+ return inspect.getsourcelines(obj)
+
@property
def source_file_def_line(self):
"""
@@ -303,11 +318,11 @@ def source_file_def_line(self):
"""
try:
if isinstance(self.code_obj, property):
- sourcelines = inspect.getsourcelines(self.code_obj.fget)
+ sourcelines = self._getsourcelines(self.code_obj.fget)
elif isinstance(self.code_obj, functools.cached_property):
- sourcelines = inspect.getsourcelines(self.code_obj.func)
+ sourcelines = self._getsourcelines(self.code_obj.func)
else:
- sourcelines = inspect.getsourcelines(self.code_obj)
+ sourcelines = self._getsourcelines(self.code_obj)
# getsourcelines will return the line of the first decorator found for the
# current function. We have to find the def declaration after that.
def_line = next(
@@ -320,7 +335,7 @@ def source_file_def_line(self):
return sourcelines[-1] + def_line
except (OSError, TypeError):
# In some cases the object is something complex like a cython
- # object that can't be easily introspected. An it's better to
+ # object that can't be easily introspected. And it's better to
# return the line number as None, than crash
pass
@@ -521,9 +536,9 @@ def get_returns_not_on_nested_functions(node):
if tree:
returns = get_returns_not_on_nested_functions(tree[0])
return_values = [r.value for r in returns]
- # Replace NameConstant nodes valued None for None.
+ # Replace Constant nodes valued None for None.
for i, v in enumerate(return_values):
- if isinstance(v, ast.NameConstant) and v.value is None:
+ if isinstance(v, ast.Constant) and v.value is None:
return_values[i] = None
return any(return_values)
else:
@@ -613,7 +628,7 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
else:
doc = validator_cls(obj_name=obj_name, **validator_kwargs)
- # lineno is only 0 if we have a module docstring in the file and we are
+ # lineno is only 0 if we have a module docstring in the file, and we are
# validating that, so we change to 1 for readability of the output
ignore_validation_comments = extract_ignore_validation_comments(
doc.source_file_name
@@ -621,7 +636,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,
@@ -673,12 +710,18 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
errs.append(error("SS03"))
if doc.summary != doc.summary.lstrip():
errs.append(error("SS04"))
- elif doc.is_function_or_method and doc.summary.split(" ")[0][-1] == "s":
+ # Heuristic to check for infinitive verbs - shouldn't end in "s"
+ elif (
+ doc.is_function_or_method
+ and len(doc.summary.split(" ")[0]) > 1
+ and doc.summary.split(" ")[0][-1] == "s"
+ and doc.summary.split(" ")[0][-2] != "s"
+ ):
errs.append(error("SS05"))
if doc.num_summary_lines > 1:
errs.append(error("SS06"))
- if not doc.extended_summary:
+ if not doc.is_mod and not doc.extended_summary:
errs.append(("ES01", "No extended summary found"))
# PR01: Parameters not documented
@@ -730,20 +773,21 @@ def validate(obj_name, validator_cls=None, **validator_kwargs):
if not doc.yields and doc.is_generator_function:
errs.append(error("YD01"))
- if not doc.see_also:
- errs.append(error("SA01"))
- else:
- for rel_name, rel_desc in doc.see_also.items():
- if rel_desc:
- if not rel_desc.endswith("."):
- errs.append(error("SA02", reference_name=rel_name))
- if rel_desc[0].isalpha() and not rel_desc[0].isupper():
- errs.append(error("SA03", reference_name=rel_name))
- else:
- errs.append(error("SA04", reference_name=rel_name))
+ if not doc.is_mod:
+ if not doc.see_also:
+ errs.append(error("SA01"))
+ else:
+ for rel_name, rel_desc in doc.see_also.items():
+ if rel_desc:
+ if not rel_desc.endswith("."):
+ errs.append(error("SA02", reference_name=rel_name))
+ if rel_desc[0].isalpha() and not rel_desc[0].isupper():
+ errs.append(error("SA03", reference_name=rel_name))
+ else:
+ errs.append(error("SA04", reference_name=rel_name))
- if not doc.examples:
- errs.append(error("EX01"))
+ if not doc.examples:
+ errs.append(error("EX01"))
errs = [err for err in errs if err[0] not in ignore_validation_comments]
diff --git a/numpydoc/xref.py b/numpydoc/xref.py
index a0cc8a5d..f1b9d79f 100644
--- a/numpydoc/xref.py
+++ b/numpydoc/xref.py
@@ -16,11 +16,7 @@
QUALIFIED_NAME_RE = re.compile(
# e.g int, numpy.array, ~numpy.array, .class_in_current_module
- r"^"
- r"[~\.]?"
- r"[a-zA-Z_]\w*"
- r"(?:\.[a-zA-Z_]\w*)*"
- r"$"
+ r"^" r"[~\.]?" r"[a-zA-Z_]\w*" r"(?:\.[a-zA-Z_]\w*)*" r"$"
)
CONTAINER_SPLIT_RE = re.compile(
diff --git a/pyproject.toml b/pyproject.toml
index 374008b5..b8911d85 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,28 +6,28 @@ requires = ['setuptools>=61.2']
name = 'numpydoc'
description = 'Sphinx extension to support docstrings in Numpy format'
readme = 'README.rst'
-requires-python = '>=3.8'
+requires-python = '>=3.10'
dynamic = ['version']
keywords = [
'sphinx',
'numpy',
]
classifiers = [
- 'Development Status :: 4 - Beta',
+ 'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
+ 'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
- 'Topic :: Documentation',
+ 'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.8',
- 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
+ 'Topic :: Documentation',
]
dependencies = [
- 'sphinx>=5',
- 'Jinja2>=2.10',
- 'tabulate>=0.8.10',
+ 'sphinx>=6',
"tomli>=1.1.0;python_version<'3.11'",
]
@@ -42,16 +42,19 @@ file = 'LICENSE.txt'
Homepage = 'https://numpydoc.readthedocs.io'
Source = 'https://github.com/numpy/numpydoc/'
-[project.optional-dependencies]
-developer = [
+[dependency-groups]
+dev = [
'pre-commit>=3.3',
"tomli; python_version < '3.11'",
+ { include-group = "doc" },
+ { include-group = "test" }
]
doc = [
'numpy>=1.22',
'matplotlib>=3.5',
'pydata-sphinx-theme>=0.13.3',
'sphinx>=7',
+ 'intersphinx_registry',
]
test = [
'pytest',
@@ -60,7 +63,99 @@ test = [
]
[project.scripts]
-validate-docstrings = 'numpydoc.hooks.validate_docstrings:main'
+numpydoc = 'numpydoc.cli:main'
+
+[tool.changelist]
+title_template = "{version}"
+# Profiles that are excluded from the contributor list.
+ignored_user_logins = ["dependabot[bot]", "pre-commit-ci[bot]", "web-flow"]
+
+[tool.ruff.lint]
+extend-select = [
+ "B", # flake8-bugbear
+ "I", # isort
+ "ARG", # flake8-unused-arguments
+ "C4", # flake8-comprehensions
+ "EM", # flake8-errmsg
+ "ICN", # flake8-import-conventions
+ "G", # flake8-logging-format
+ "PGH", # pygrep-hooks
+ "PIE", # flake8-pie
+ "PL", # pylint
+ "PT", # flake8-pytest-style
+ "PTH", # flake8-use-pathlib
+ "RET", # flake8-return
+ "RUF", # Ruff-specific
+ "SIM", # flake8-simplify
+ "T20", # flake8-print
+ "UP", # pyupgrade
+ "YTT", # flake8-2020
+ "EXE", # flake8-executable
+ "NPY", # NumPy specific rules
+ "PD", # pandas-vet
+ "FURB", # refurb
+ "PYI", # flake8-pyi
+]
+ignore = [
+ "PLR09", # Too many <...>
+ "PLR2004", # Magic value used in comparison
+ "PLC0415", # Imports not at top of file (we often nest intentionally)
+ "ISC001", # Conflicts with formatter
+ "ARG001", # FIXME: consider removing this and the following rules from this list
+ "ARG002",
+ "B004",
+ "B007",
+ "B023",
+ "B028",
+ "B034",
+ "C408",
+ "E402",
+ "E741",
+ "EM101",
+ "EM102",
+ "EXE001",
+ "F401",
+ "F811",
+ "F821",
+ "F841",
+ "PIE810",
+ "PLW0603",
+ "PLW2901",
+ "PLW3301",
+ "PT006",
+ "PT007",
+ "PT011",
+ "PT012",
+ "PT013",
+ "PTH100",
+ "PTH118",
+ "PTH120",
+ "PTH123",
+ "PYI024",
+ "RET503",
+ "RET504",
+ "RET505",
+ "RET506",
+ "RET507",
+ "RET508",
+ "RUF005",
+ "RUF012",
+ "RUF013",
+ "SIM102",
+ "SIM105",
+ "SIM108",
+ "SIM115",
+ "T201",
+ "UP006",
+ "UP031",
+ "UP035",
+]
+
+[tool.ruff.lint.per-file-ignores]
+"doc/example.py" = ["ARG001", "F401", "I001"]
+
+[tool.ruff.format]
+docstring-code-format = true
[tool.setuptools]
include-package-data = false
@@ -82,6 +177,6 @@ numpydoc = [
[tool.pytest.ini_options]
addopts = '''
---showlocals --doctest-modules -ra --cov-report= --cov=numpydoc
+--showlocals --doctest-modules --cov-report= --cov=numpydoc
--junit-xml=junit-results.xml --ignore=doc/ --ignore=tools/'''
junit_family = 'xunit2'
diff --git a/requirements/default.txt b/requirements/default.txt
deleted file mode 100644
index 1b80561c..00000000
--- a/requirements/default.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# Generated via tools/generate_requirements.py and pre-commit hook.
-# Do not edit this file; modify pyproject.toml instead.
-sphinx>=5
-Jinja2>=2.10
-tabulate>=0.8.10
-tomli>=1.1.0;python_version<'3.11'
diff --git a/requirements/developer.txt b/requirements/developer.txt
deleted file mode 100644
index dbeefe57..00000000
--- a/requirements/developer.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# Generated via tools/generate_requirements.py and pre-commit hook.
-# Do not edit this file; modify pyproject.toml instead.
-pre-commit>=3.3
-tomli; python_version < '3.11'
diff --git a/requirements/doc.txt b/requirements/doc.txt
deleted file mode 100644
index f3d9b058..00000000
--- a/requirements/doc.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# Generated via tools/generate_requirements.py and pre-commit hook.
-# Do not edit this file; modify pyproject.toml instead.
-numpy>=1.22
-matplotlib>=3.5
-pydata-sphinx-theme>=0.13.3
-sphinx>=7
diff --git a/requirements/test.txt b/requirements/test.txt
deleted file mode 100644
index 2a9e670f..00000000
--- a/requirements/test.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-# Generated via tools/generate_requirements.py and pre-commit hook.
-# Do not edit this file; modify pyproject.toml instead.
-pytest
-pytest-cov
-matplotlib
diff --git a/tools/generate_requirements.py b/tools/generate_requirements.py
deleted file mode 100755
index 388d2ccc..00000000
--- a/tools/generate_requirements.py
+++ /dev/null
@@ -1,39 +0,0 @@
-#!/usr/bin/env python
-"""Generate requirements/*.txt files from pyproject.toml."""
-
-import sys
-from pathlib import Path
-
-try: # standard module since Python 3.11
- import tomllib as toml
-except ImportError:
- try: # available for older Python via pip
- import tomli as toml
- except ImportError:
- sys.exit("Please install `tomli` first: `pip install tomli`")
-
-script_pth = Path(__file__)
-repo_dir = script_pth.parent.parent
-script_relpth = script_pth.relative_to(repo_dir)
-header = [
- f"# Generated via {script_relpth.as_posix()} and pre-commit hook.",
- "# Do not edit this file; modify pyproject.toml instead.",
-]
-
-
-def generate_requirement_file(name: str, req_list: list[str]) -> None:
- req_fname = repo_dir / "requirements" / f"{name}.txt"
- req_fname.write_text("\n".join(header + req_list) + "\n")
-
-
-def main() -> None:
- pyproject = toml.loads((repo_dir / "pyproject.toml").read_text())
-
- generate_requirement_file("default", pyproject["project"]["dependencies"])
-
- for key, opt_list in pyproject["project"]["optional-dependencies"].items():
- generate_requirement_file(key, opt_list)
-
-
-if __name__ == "__main__":
- main()