From ca0c0f7ee9362e4142059d0c75fe96ec21e29d94 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Mon, 28 Jul 2025 00:27:12 +1000 Subject: [PATCH 1/7] build(pyproject.toml): drop support for python 3.9 (require 3.10+) In upgrading via #592 and #622, requires stable use of __qualname__, not supported in dataclasses in python3.9 (solves #637). --- .github/workflows/test.yml | 2 +- README.rst | 2 +- doc/install.rst | 2 +- pyproject.toml | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b463ee42..2f450a9b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [Ubuntu] - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13"] sphinx-version: ["sphinx==6.0", "sphinx==6.2", "sphinx==7.0", "sphinx>=7.3"] include: diff --git a/README.rst b/README.rst index 354a79d9..fdd35942 100644 --- a/README.rst +++ b/README.rst @@ -17,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.9+ and sphinx 6+. +numpydoc requires Python 3.10+ and sphinx 6+. For usage information, please refer to the `documentation `_. diff --git a/doc/install.rst b/doc/install.rst index 2471b765..5d61010f 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -5,7 +5,7 @@ Getting started Installation ============ -This extension requires Python 3.9+, sphinx 6+ and is available from: +This extension requires Python 3.10+, sphinx 6+ and is available from: * `numpydoc on PyPI `_ * `numpydoc on GitHub `_ diff --git a/pyproject.toml b/pyproject.toml index b6acaa4c..c8158115 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = ['setuptools>=61.2'] name = 'numpydoc' description = 'Sphinx extension to support docstrings in Numpy format' readme = 'README.rst' -requires-python = '>=3.9' +requires-python = '>=3.10' dynamic = ['version'] keywords = [ 'sphinx', @@ -20,7 +20,6 @@ classifiers = [ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', From d94d5b2914a0824a7d4b99aa0a03c6249fa36d07 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:14:56 +1000 Subject: [PATCH 2/7] style: Replace isinstance calls that use tuples to the `|` operator as per ruff UP038 --- numpydoc/docscrape.py | 2 +- numpydoc/hooks/validate_docstrings.py | 8 ++++---- numpydoc/numpydoc.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 54863d5f..9cf5c5a9 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -701,7 +701,7 @@ def properties(self): 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) diff --git a/numpydoc/hooks/validate_docstrings.py b/numpydoc/hooks/validate_docstrings.py index a718e4ef..70823f10 100644 --- a/numpydoc/hooks/validate_docstrings.py +++ b/numpydoc/hooks/validate_docstrings.py @@ -63,7 +63,7 @@ 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: @@ -236,7 +236,7 @@ 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(node) @@ -395,8 +395,8 @@ def process_file(filepath: os.PathLike, config: dict) -> "list[list[str]]": def run_hook( files: List[str], *, - config: Union[Dict[str, Any], None] = None, - ignore: Union[List[str], None] = None, + config: Dict[str, Any] | None = None, + ignore: List[str] | None = None, ) -> int: """ Run the numpydoc validation hook. diff --git a/numpydoc/numpydoc.py b/numpydoc/numpydoc.py index 9c1bb0ed..b1431519 100644 --- a/numpydoc/numpydoc.py +++ b/numpydoc/numpydoc.py @@ -88,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 From 64fbebbca97865355e1746b2931d94ac28fd93a1 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:18:06 +1000 Subject: [PATCH 3/7] refactor(cli.py): exchange union use for '|' as per ruff UP007 --- numpydoc/cli.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/numpydoc/cli.py b/numpydoc/cli.py index 53335fdf..30daa3f5 100644 --- a/numpydoc/cli.py +++ b/numpydoc/cli.py @@ -4,14 +4,14 @@ import ast from collections.abc import Sequence from pathlib import Path -from typing import List, Union +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: Union[List[str], None] = None) -> int: +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 []))) @@ -117,7 +117,7 @@ def _parse_config(s): return ap -def main(argv: Union[Sequence[str], None] = None) -> int: +def main(argv: Sequence[str] | None = None) -> int: """CLI for numpydoc.""" ap = get_parser() From 9899bcdfa47b1fd3704d986bb2feda73f0f03b53 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:24:33 +1000 Subject: [PATCH 4/7] refactor(test_docscrape): explicitly declare strict=True for testing zips --- numpydoc/tests/test_docscrape.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index c122515a..3eacda36 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -187,7 +187,7 @@ 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"], strict=True)) arg, arg_type, desc = doc["Parameters"][1] assert arg_type == "(N, N) ndarray" @@ -242,7 +242,7 @@ 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") @@ -253,7 +253,7 @@ 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") @@ -374,7 +374,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 From 2627fb4ac54938543999fca589b7b069156efd55 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:27:59 +1000 Subject: [PATCH 5/7] test(test_docscrape.py): use strict and include 4th parameter check for the docscrape test --- numpydoc/tests/test_docscrape.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 3eacda36..cdda1c40 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -187,7 +187,7 @@ 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"], strict=True)) + 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" From 152ea6e3106673c63474e52a4b9b1973452c8763 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:31:45 +1000 Subject: [PATCH 6/7] refactor(validate.py): remove `Optional[X]` for use of `X | None` ruff UP007, UP045 --- numpydoc/validate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/numpydoc/validate.py b/numpydoc/validate.py index d0debfa2..18373b77 100644 --- a/numpydoc/validate.py +++ b/numpydoc/validate.py @@ -17,7 +17,7 @@ import textwrap import tokenize from copy import deepcopy -from typing import Any, Dict, List, Optional, Set +from typing import Any, Dict, List, Set from .docscrape import get_doc_object @@ -124,7 +124,7 @@ def _unwrap(obj): # 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]]: """ From f3ecdf31ce995e9353544f46e3fdb4db65584261 Mon Sep 17 00:00:00 2001 From: Matt Gebert Date: Sat, 2 Aug 2025 13:34:05 +1000 Subject: [PATCH 7/7] refactor(test_docscrape.py): ruff formatting --- numpydoc/tests/test_docscrape.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index cdda1c40..dbfc5c14 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -187,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", "dtype"], strict=True)) + 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" @@ -242,7 +244,9 @@ def test_yields(): ("b", "int", "bananas."), ("", "int", "unknowns."), ] - for (arg, arg_type, desc), (arg_, arg_type_, end) in zip(section, truth, strict=True): + 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") @@ -253,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, strict=True): + 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")