Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
name: Install Python
with:
python-version: "3.9"
python-version: "3.14"
cache: "pip"

- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/perf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ jobs:
name: Install Pythons
with:
python-version: |
3.9
3.10
3.11
3.12
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
matrix:
os: [Ubuntu, Windows, macOS]
python_version:
["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.13t", "3.14t", "3.15t", "pypy3.9", "pypy3.10", "pypy3.11"]
["3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.13t", "3.14t", "3.15t", "pypy3.10", "pypy3.11"]
include:
- os: Ubuntu
python_version: "3.11.0"
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Changelog
*unreleased*
~~~~~~~~~~~~

Removals:

* Drop support for EOL Python 3.9. (:pull:`1263`)

Features:

* Add a public :class:`~packaging.ranges.VersionRange` API and
Expand Down
2 changes: 1 addition & 1 deletion asv.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
"PYTHONHASHSEED": ["0"]
}
},
"pythons": ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
"pythons": ["3.10", "3.11", "3.12", "3.13", "3.14"]
}
1 change: 0 additions & 1 deletion docs/development/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ each supported Python version and run the tests. For example:
$ nox -s tests
...
nox > Ran multiple sessions:
nox > * tests-3.9: success
nox > * tests-3.10: success
nox > * tests-3.11: success
nox > * tests-3.12: success
Expand Down
3 changes: 1 addition & 2 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
"3.13t",
"3.14t",
"3.15t",
"pypy3.9",
"pypy3.10",
"pypy3.11",
],
Expand Down Expand Up @@ -675,7 +674,7 @@ def _bump(session: nox.Session, *, version: str, file: Path, kind: str) -> None:
@contextlib.contextmanager
def _replace_file(
original_path: Path,
) -> Generator[tuple[IO[str], IO[str]], None, None]:
) -> Generator[tuple[IO[str], IO[str]]]:
# Create a temporary file.
fh, replacement_path = tempfile.mkstemp()

Expand Down
5 changes: 2 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ description = "Core utilities for Python packages"
dynamic = ["version"]
license = "Apache-2.0 OR BSD-2-Clause"
readme = "README.rst"
requires-python = ">=3.9"
requires-python = ">=3.10"
authors = [{name = "Donald Stufft", email = "donald@stufft.io"}]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"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 @@ -110,7 +109,7 @@ markers = ["property: property-based tests (opt-in)"]
strict = true
enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"]
warn_unused_ignores = true
python_version = "3.9"
python_version = "3.10"
files = ["src", "tests", "noxfile.py"]
native_parser = true

Expand Down
2 changes: 1 addition & 1 deletion src/packaging/_manylinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@


@contextlib.contextmanager
def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
def _parse_elf(path: str) -> Generator[ELFFile | None]:
try:
with open(path, "rb") as f:
yield ELFFile(f)
Expand Down
4 changes: 2 additions & 2 deletions src/packaging/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,9 @@ def serialize(self) -> str:


MarkerLogical = Literal["and", "or"]
MarkerVar = Union[Variable, Value]
MarkerVar = Variable | Value
MarkerItem = tuple[MarkerVar, Op, MarkerVar]
MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
MarkerAtom = MarkerItem | Sequence["MarkerAtom"]
MarkerList = list[Union["MarkerList", MarkerAtom, MarkerLogical]]


Expand Down
5 changes: 2 additions & 3 deletions src/packaging/_ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@

if TYPE_CHECKING:
from collections.abc import Callable, Iterable, Iterator, Sequence
from typing import Union

# Total-order key for comparing two boundaries (boundary-vs-boundary only).
# The post slot may be ``_BOUNDARY_INF`` for an AFTER_POSTS boundary.
_BoundaryOrderSuffix = tuple[int, int, int, Union[int, float], int, int]
_BoundaryOrderSuffix = tuple[int, int, int, int | float, int, int]
_BoundaryOrderKey = tuple[int, tuple[int, ...], _BoundaryOrderSuffix, float]

__all__ = [
Expand Down Expand Up @@ -172,7 +171,7 @@ def __repr__(self) -> str:


if TYPE_CHECKING:
_VersionOrBoundary = Union[Version, BoundaryVersion, None]
_VersionOrBoundary = Version | BoundaryVersion | None


@functools.total_ordering
Expand Down
2 changes: 1 addition & 1 deletion src/packaging/_tokenizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def raise_syntax_error(
@contextlib.contextmanager
def enclosing_tokens(
self, open_token: str, close_token: str, *, around: str
) -> Generator[None, None, None]:
) -> Generator[None]:
if self.check(open_token):
open_position = self.position
self.read()
Expand Down
4 changes: 2 additions & 2 deletions src/packaging/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def finalize(self, msg: str) -> None:
raise ExceptionGroup(msg, self.errors)

@contextlib.contextmanager
def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector, None, None]:
def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector]:
"""
Calls finalize if no uncollected errors were present.

Expand All @@ -72,7 +72,7 @@ def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector, None, None]:
self.finalize(msg)

@contextlib.contextmanager
def collect(self, *err_cls: type[Exception]) -> typing.Generator[None, None, None]:
def collect(self, *err_cls: type[Exception]) -> typing.Generator[None]:
"""
Context manager to collect errors into the error list.

Expand Down
1 change: 0 additions & 1 deletion src/packaging/licenses/_spdx.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from __future__ import annotations

from typing import TypedDict
Expand Down
5 changes: 3 additions & 2 deletions src/packaging/markers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import os
import platform
import sys
from collections.abc import Callable
from collections.abc import Set as AbstractSet
from typing import TYPE_CHECKING, Callable, Literal, TypedDict, Union, cast
from typing import TYPE_CHECKING, Literal, TypedDict, cast

from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
from ._parser import parse_marker as _parse_marker
Expand All @@ -36,7 +37,7 @@ def __dir__() -> list[str]:
return __all__


Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
Operator = Callable[[str, str | AbstractSet[str]], bool]
EvaluateContext = Literal["metadata", "lock_file", "requirement"]
"""A ``typing.Literal`` enumerating valid marker evaluation contexts.

Expand Down
3 changes: 2 additions & 1 deletion src/packaging/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import typing
from typing import (
Any,
Callable,
Generic,
Literal,
TypedDict,
Expand All @@ -21,6 +20,8 @@
from .errors import ExceptionGroup, _ErrorCollector

if typing.TYPE_CHECKING:
from collections.abc import Callable

from .licenses import NormalizedLicenseExpression
from .version import Version

Expand Down
3 changes: 1 addition & 2 deletions src/packaging/pylock.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import dataclasses
import logging
import re
from collections.abc import Mapping, Sequence
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import (
TYPE_CHECKING,
Any,
Callable,
Protocol,
TypeVar,
cast,
Expand Down
3 changes: 1 addition & 2 deletions src/packaging/ranges.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
TYPE_CHECKING,
Any,
TypeVar,
Union,
)

from ._ranges import (
Expand Down Expand Up @@ -58,7 +57,7 @@
__all__ = ["VersionRange"]

T = TypeVar("T")
UnparsedVersion = Union[Version, str]
UnparsedVersion = Version | str
UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)

#: The most ``!=`` exclusion fragments (``!=V`` points or ``!=P.*`` prefixes)
Expand Down
13 changes: 3 additions & 10 deletions src/packaging/specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@
from typing import (
TYPE_CHECKING,
Any,
Callable,
Final,
TypeVar,
Union,
)

from ._ranges import (
Expand All @@ -37,13 +35,8 @@
from .version import Version

if TYPE_CHECKING:
import sys
from collections.abc import Iterable, Iterator, Sequence

if sys.version_info >= (3, 10):
from typing import TypeGuard
else:
from typing_extensions import TypeGuard
from collections.abc import Callable, Iterable, Iterator, Sequence
from typing import TypeGuard

from . import ranges
from ._ranges import Interval
Expand Down Expand Up @@ -75,7 +68,7 @@ def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:


T = TypeVar("T")
UnparsedVersion = Union[Version, str]
UnparsedVersion = Version | str
UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)


Expand Down
4 changes: 2 additions & 2 deletions src/packaging/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from __future__ import annotations

import re
from typing import NewType, Union, cast
from typing import NewType, cast

from .tags import InvalidTag, Tag, UnsortedTagsError, parse_tag
from .version import InvalidVersion, Version, _TrimmedRelease
Expand All @@ -28,7 +28,7 @@ def __dir__() -> list[str]:
return __all__


BuildTag = Union[tuple[()], tuple[int, str]]
BuildTag = tuple[()] | tuple[int, str]
"""
A wheel build tag: an empty tuple, or a ``(build number, build tag suffix)`` pair.

Expand Down
16 changes: 7 additions & 9 deletions src/packaging/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,13 @@
import re
import sys
import typing
from collections.abc import Callable
from typing import (
Any,
Callable,
Literal,
NamedTuple,
SupportsInt,
TypedDict,
Union,
)

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -66,14 +65,14 @@ def __dir__() -> list[str]:
return __all__


LocalType = tuple[Union[int, str], ...]
LocalType = tuple[int | str, ...]

CmpLocalType = tuple[tuple[int, str], ...]
CmpSuffix = tuple[int, int, int, int, int, int]
CmpKey = Union[
tuple[int, tuple[int, ...], CmpSuffix],
tuple[int, tuple[int, ...], CmpSuffix, CmpLocalType],
]
CmpKey = (
tuple[int, tuple[int, ...], CmpSuffix]
| tuple[int, tuple[int, ...], CmpSuffix, CmpLocalType]
)
VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]


Expand Down Expand Up @@ -431,8 +430,7 @@ def __init__(self, version: str) -> None:
# propagate to the caller.
if "" in version.split("."):
raise InvalidVersion(f"Invalid version: {version!r}") from None
# TODO: remove "no cover" when Python 3.9 is dropped.
raise # pragma: no cover
raise

self._epoch = 0
self._pre = None
Expand Down
2 changes: 1 addition & 1 deletion tasks/pickle_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def verify(path: pathlib.Path, expected_version: str | None = None) -> int:
return 1

for i, (loaded, expected) in enumerate(
zip(loaded_list, expected_list) # type: ignore[call-overload]
zip(loaded_list, expected_list, strict=False) # type: ignore[call-overload]
):
if type(loaded) is not expected_cls:
print(
Expand Down
7 changes: 1 addition & 6 deletions tests/test_dependency_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,7 @@
from packaging.requirements import InvalidRequirement, Requirement

if TYPE_CHECKING:
import sys

if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
from typing import TypeAlias

GroupsTable: TypeAlias = "dict[str, list[str | dict[str, str]]]"

Expand Down
2 changes: 1 addition & 1 deletion tests/test_manylinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@


@pytest.fixture(autouse=True)
def clear_lru_cache() -> Generator[None, None, None]:
def clear_lru_cache() -> Generator[None]:
yield
_get_glibc_version.cache_clear()
_get_manylinux_module.cache_clear()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_musllinux.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class Proc(typing.NamedTuple):


@pytest.fixture(autouse=True)
def clear_lru_cache() -> Generator[None, None, None]:
def clear_lru_cache() -> Generator[None]:
yield
_get_musl_version.cache_clear()

Expand Down
2 changes: 1 addition & 1 deletion tests/test_specifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3090,7 +3090,7 @@ def test_range_bounds_hashable_and_equal(self) -> None:
"""Range bounds are hashable and support equality."""
a = Specifier(">1.0")._to_ranges()
b = Specifier(">1.0")._to_ranges()
for (al, au), (bl, bu) in zip(a, b):
for (al, au), (bl, bu) in zip(a, b, strict=True):
hash(al)
hash(au)
assert al == bl
Expand Down
Loading