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/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()
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
diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py
index c122515a..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"]))
+ 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):
+ 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):
+ 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 +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
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]]:
"""
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',