Skip to content

Commit

Permalink
Drop support for Python 3.9 (#12633)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Jul 22, 2024
1 parent 8c6d234 commit 9e3f452
Show file tree
Hide file tree
Showing 73 changed files with 221 additions and 297 deletions.
1 change: 0 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ jobs:
fail-fast: false
matrix:
python:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
Expand Down
6 changes: 3 additions & 3 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
target-version = "py39" # Pin Ruff to Python 3.9
target-version = "py310" # Pin Ruff to Python 3.10
line-length = 95
output-format = "full"

Expand Down Expand Up @@ -419,8 +419,8 @@ select = [
]

# these tests need old ``typing`` generic aliases
"tests/test_util/test_util_typing.py" = ["UP006", "UP035"]
"tests/test_util/typing_test_data.py" = ["FA100", "UP006", "UP035"]
"tests/test_util/test_util_typing.py" = ["UP006", "UP007", "UP035"]
"tests/test_util/typing_test_data.py" = ["FA100", "UP006", "UP007", "UP035"]

"utils/*" = [
"T201", # whitelist ``print`` for stdout messages
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Release 8.0.0 (in development)
Dependencies
------------

* #12633: Drop Python 3.9 support.

Incompatible changes
--------------------

Expand Down
8 changes: 4 additions & 4 deletions doc/internals/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -174,19 +174,19 @@ of targets and allows testing against multiple different Python environments:

tox -av

* To run unit tests for a specific Python version, such as Python 3.10::
* To run unit tests for a specific Python version, such as Python 3.12::

tox -e py310
tox -e py312

* To run unit tests for a specific Python version and turn on deprecation
warnings so they're shown in the test output::

PYTHONWARNINGS=error tox -e py310
PYTHONWARNINGS=error tox -e py312

* Arguments to ``pytest`` can be passed via ``tox``, e.g., in order to run a
particular test::

tox -e py310 tests/test_module.py::test_new_feature
tox -e py312 tests/test_module.py::test_new_feature

You can also test by installing dependencies in your local environment::

Expand Down
2 changes: 1 addition & 1 deletion doc/tutorial/deploying.rst
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ After you have published your sources on GitLab, create a file named
pages:
stage: deploy
image: python:3.9-slim
image: python:3.12-slim
before_script:
- apt-get update && apt-get install make --no-install-recommends -y
- python -m pip install sphinx furo
Expand Down
4 changes: 2 additions & 2 deletions doc/usage/extensions/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,10 @@ a comma-separated list of group names.

* ``pyversion``, a string option, can be used to specify the required Python
version for the example to be tested. For instance, in the following case
the example will be tested only for Python versions greater than 3.10::
the example will be tested only for Python versions greater than 3.12::

.. doctest::
:pyversion: > 3.10
:pyversion: > 3.12

The following operands are supported:

Expand Down
8 changes: 4 additions & 4 deletions doc/usage/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,18 @@ Install either ``python3x-sphinx`` using :command:`port`:

::

$ sudo port install py39-sphinx
$ sudo port install py312-sphinx

To set up the executable paths, use the ``port select`` command:

::

$ sudo port select --set python python39
$ sudo port select --set sphinx py39-sphinx
$ sudo port select --set python python312
$ sudo port select --set sphinx py312-sphinx

For more information, refer to the `package overview`__.

__ https://www.macports.org/ports.php?by=library&substr=py39-sphinx
__ https://www.macports.org/ports.php?by=library&substr=py312-sphinx

Windows
~~~~~~~
Expand Down
7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ urls.Download = "https://pypi.org/project/Sphinx/"
urls.Homepage = "https://www.sphinx-doc.org/"
urls."Issue tracker" = "https://github.com/sphinx-doc/sphinx/issues"
license.text = "BSD-2-Clause"
requires-python = ">=3.9"
requires-python = ">=3.10"

# Classifiers list: https://pypi.org/classifiers/
classifiers = [
Expand All @@ -30,7 +30,6 @@ classifiers = [
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -71,7 +70,6 @@ dependencies = [
"imagesize>=1.3",
"requests>=2.30.0",
"packaging>=23.0",
"importlib-metadata>=6.0; python_version < '3.10'",
"tomli>=2; python_version < '3.11'",
"colorama>=0.4.6; sys_platform == 'win32'",
]
Expand All @@ -91,7 +89,6 @@ lint = [
"types-Pillow==10.2.0.20240520",
"types-Pygments==2.18.0.20240506",
"types-requests>=2.30.0", # align with requests
"importlib-metadata>=6.0", # for mypy (Python<=3.9)
"tomli>=2", # for mypy (Python<=3.10)
"pytest>=6.0",
]
Expand Down Expand Up @@ -211,7 +208,7 @@ exclude = [
]
check_untyped_defs = true
disallow_incomplete_defs = true
python_version = "3.9"
python_version = "3.10"
show_column_numbers = true
show_error_context = true
strict_optional = true
Expand Down
2 changes: 1 addition & 1 deletion sphinx/_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def format_help(self) -> str:
]

if commands := list(_load_subcommand_descriptions()):
command_max_length = min(max(map(len, next(zip(*commands), ()))), 22)
command_max_length = min(max(map(len, next(zip(*commands, strict=True), ()))), 22)
help_fragments += [
'\n',
bold(underline(__('Commands:'))),
Expand Down
4 changes: 2 additions & 2 deletions sphinx/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import pickle
import sys
from collections import deque
from collections.abc import Collection, Sequence # NoQA: TCH003
from collections.abc import Callable, Collection, Sequence # NoQA: TCH003
from io import StringIO
from os import path
from typing import IO, TYPE_CHECKING, Any, Callable, Literal
from typing import IO, TYPE_CHECKING, Any, Literal

from docutils.nodes import TextElement # NoQA: TCH002
from docutils.parsers.rst import Directive, roles
Expand Down
6 changes: 3 additions & 3 deletions sphinx/builders/html/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ def _stable_hash(obj: Any) -> str:
"""
if isinstance(obj, dict):
obj = sorted(map(_stable_hash, obj.items()))
if isinstance(obj, (list, tuple, set, frozenset)):
if isinstance(obj, list | tuple | set | frozenset):
obj = sorted(map(_stable_hash, obj))
elif isinstance(obj, (type, types.FunctionType)):
elif isinstance(obj, type | types.FunctionType):
# The default repr() of functions includes the ID, which is not ideal.
# We use the fully qualified name instead.
obj = f'{obj.__module__}.{obj.__qualname__}'
Expand Down Expand Up @@ -734,7 +734,7 @@ def write_genindex(self) -> None:
'genindex-split.html')
self.handle_page('genindex-all', genindexcontext,
'genindex.html')
for (key, entries), count in zip(genindex, indexcounts):
for (key, entries), count in zip(genindex, indexcounts, strict=True):
ctx = {'key': key, 'entries': entries, 'count': count,
'genindexentries': genindex}
self.handle_page('genindex-' + key, ctx,
Expand Down
2 changes: 1 addition & 1 deletion sphinx/builders/latex/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ def depart_caption(self, node: nodes.caption) -> None:
self.unrestrict(node)

def visit_title(self, node: nodes.title) -> None:
if isinstance(node.parent, (nodes.section, nodes.table)):
if isinstance(node.parent, nodes.section | nodes.table):
self.restrict(node)

def depart_title(self, node: nodes.title) -> None:
Expand Down
4 changes: 2 additions & 2 deletions sphinx/builders/linkcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
from sphinx.util.nodes import get_node_line

if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Any, Callable
from collections.abc import Callable, Iterator
from typing import Any

from requests import Response

Expand Down
4 changes: 2 additions & 2 deletions sphinx/cmd/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys
import time
from os import path
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any

# try to import readline, unix specific enhancement
try:
Expand Down Expand Up @@ -36,7 +36,7 @@
from sphinx.util.template import SphinxRenderer

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Callable, Sequence

EXTENSIONS = {
'autodoc': __('automatically insert docstrings from modules'),
Expand Down
12 changes: 6 additions & 6 deletions sphinx/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import types
import warnings
from os import getenv, path
from typing import TYPE_CHECKING, Any, Literal, NamedTuple, Union
from typing import TYPE_CHECKING, Any, Literal, NamedTuple

from sphinx.deprecation import RemovedInSphinx90Warning
from sphinx.errors import ConfigError, ExtensionError
Expand Down Expand Up @@ -66,7 +66,7 @@ def is_serializable(obj: object, *, _seen: frozenset[int] = frozenset()) -> bool
is_serializable(key, _seen=seen) and is_serializable(value, _seen=seen)
for key, value in obj.items()
)
elif isinstance(obj, (list, tuple, set, frozenset)):
elif isinstance(obj, list | tuple | set | frozenset):
seen = _seen | {id(obj)}
return all(is_serializable(item, _seen=seen) for item in obj)

Expand All @@ -87,13 +87,13 @@ def __init__(self, *candidates: str | bool | None) -> None:
self.candidates = candidates

def match(self, value: str | list | tuple) -> bool:
if isinstance(value, (list, tuple)):
if isinstance(value, list | tuple):
return all(item in self.candidates for item in value)
else:
return value in self.candidates


_OptValidTypes = Union[tuple[()], tuple[type, ...], frozenset[type], ENUM]
_OptValidTypes = tuple[()] | tuple[type, ...] | frozenset[type] | ENUM


class _Opt:
Expand Down Expand Up @@ -549,7 +549,7 @@ def _validate_valid_types(
) -> tuple[()] | tuple[type, ...] | frozenset[type] | ENUM:
if not valid_types:
return ()
if isinstance(valid_types, (frozenset, ENUM)):
if isinstance(valid_types, frozenset | ENUM):
return valid_types
if isinstance(valid_types, type):
return frozenset((valid_types,))
Expand Down Expand Up @@ -584,7 +584,7 @@ def convert_source_suffix(app: Sphinx, config: Config) -> None:
config.source_suffix = {source_suffix: 'restructuredtext'}
logger.info(__("Converting `source_suffix = %r` to `source_suffix = %r`."),
source_suffix, config.source_suffix)
elif isinstance(source_suffix, (list, tuple)):
elif isinstance(source_suffix, list | tuple):
# if list, considers as all of them are default filetype
config.source_suffix = dict.fromkeys(source_suffix, 'restructuredtext')
logger.info(__("Converting `source_suffix = %r` to `source_suffix = %r`."),
Expand Down
5 changes: 3 additions & 2 deletions sphinx/domains/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

import copy
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Optional, cast
from collections.abc import Callable
from typing import TYPE_CHECKING, Any, NamedTuple, cast

from docutils.nodes import Element, Node, system_message

Expand Down Expand Up @@ -153,7 +154,7 @@ def generate(self, docnames: Iterable[str] | None = None,
raise NotImplementedError


TitleGetter = Callable[[Node], Optional[str]]
TitleGetter = Callable[[Node], str | None]


class Domain:
Expand Down
3 changes: 1 addition & 2 deletions sphinx/domains/c/_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@
)

if TYPE_CHECKING:

from docutils.nodes import Element, Node, TextElement

from sphinx.domains.c._symbol import Symbol
from sphinx.environment import BuildEnvironment

DeclarationType = Union[
DeclarationType = Union[ # NoQA: UP007
"ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator",
"ASTType", "ASTTypeWithInit", "ASTMacro",
]
Expand Down
7 changes: 4 additions & 3 deletions sphinx/domains/c/_parser.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any

from sphinx.domains.c._ast import (
ASTAlignofExpr,
Expand Down Expand Up @@ -53,7 +53,6 @@
ASTTypeWithInit,
ASTUnaryOpExpr,
ASTUnion,
DeclarationType,
)
from sphinx.domains.c._ids import (
_expression_assignment_ops,
Expand All @@ -80,7 +79,9 @@
)

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Callable, Sequence

from sphinx.domains.c._ast import DeclarationType


class DefinitionParser(BaseParser):
Expand Down
1 change: 0 additions & 1 deletion sphinx/domains/cpp/_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
)

if TYPE_CHECKING:

from docutils.nodes import Element, TextElement

from sphinx.addnodes import desc_signature
Expand Down
4 changes: 2 additions & 2 deletions sphinx/domains/cpp/_parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

import re
from typing import TYPE_CHECKING, Any, Callable
from typing import TYPE_CHECKING, Any

from sphinx.domains.cpp._ast import (
ASTAlignofExpr,
Expand Down Expand Up @@ -127,7 +127,7 @@
)

if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Callable, Sequence

logger = logging.getLogger(__name__)

Expand Down
4 changes: 2 additions & 2 deletions sphinx/domains/cpp/_symbol.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any, Callable, NoReturn
from typing import TYPE_CHECKING, Any, NoReturn

from sphinx.domains.cpp._ast import (
ASTDeclaration,
Expand All @@ -17,7 +17,7 @@
from sphinx.util import logging

if TYPE_CHECKING:
from collections.abc import Iterator
from collections.abc import Callable, Iterator

from sphinx.environment import BuildEnvironment

Expand Down
2 changes: 1 addition & 1 deletion sphinx/domains/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_equation_number_for(self, labelid: str) -> int | None:
def process_doc(self, env: BuildEnvironment, docname: str,
document: nodes.document) -> None:
def math_node(node: Node) -> bool:
return isinstance(node, (nodes.math, nodes.math_block))
return isinstance(node, nodes.math | nodes.math_block)

self.data['has_equations'][docname] = any(document.findall(math_node))

Expand Down
1 change: 0 additions & 1 deletion sphinx/domains/python/_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
)

if TYPE_CHECKING:

from docutils.nodes import Node
from docutils.parsers.rst.states import Inliner

Expand Down
Loading

0 comments on commit 9e3f452

Please sign in to comment.