From: stankudrow Date: Mon, 23 Dec 2024 16:52:26 +0000 (+0300) Subject: chore: drop Python 3.8 support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F977%2Fhead;p=thirdparty%2Fpsycopg.git chore: drop Python 3.8 support Close #976. --- diff --git a/.github/workflows/3rd-party-tests.yml b/.github/workflows/3rd-party-tests.yml index fe78ac414..cab0c4019 100644 --- a/.github/workflows/3rd-party-tests.yml +++ b/.github/workflows/3rd-party-tests.yml @@ -137,7 +137,7 @@ jobs: # Current logic is test oldest in lts and newest in main - django_label: lts pip_django: "'Django>=4.2,<4.3'" - python-version: "3.8" + python-version: "3.9" env: DEPS: ./psycopg ./psycopg_pool diff --git a/.github/workflows/packages-bin.yml b/.github/workflows/packages-bin.yml index 6102be107..be3e14644 100644 --- a/.github/workflows/packages-bin.yml +++ b/.github/workflows/packages-bin.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: arch: [x86_64, i686, ppc64le, aarch64] - pyver: [cp38, cp39, cp310, cp311, cp312, cp313] + pyver: [cp39, cp310, cp311, cp312, cp313] platform: [manylinux, musllinux] steps: @@ -161,7 +161,7 @@ jobs: matrix: # These archs require an Apple M1 runner: [arm64, universal2] arch: [x86_64] - pyver: [cp38, cp39, cp310, cp311, cp312, cp313] + pyver: [cp39, cp310, cp311, cp312, cp313] env: PG_VERSION: "17" @@ -215,7 +215,7 @@ jobs: matrix: # Might want to add win32, untested at the moment. arch: [win_amd64] - pyver: [cp38, cp39, cp310, cp311, cp312, cp313] + pyver: [cp39, cp310, cp311, cp312, cp313] defaults: run: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f69fa8dcb..c21493c86 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,32 +26,30 @@ jobs: matrix: include: # Test different combinations of Python, Postgres, libpq. - - {impl: python, python: "3.8", postgres: "postgres:17", libpq: oldest} - - {impl: python, python: "3.9", postgres: "postgres:16", libpq: master} - - {impl: python, python: "3.10", postgres: "postgres:15"} - - {impl: python, python: "3.11", postgres: "postgres:14"} - - {impl: python, python: "3.12", postgres: "postgres:13", libpq: newest} + - {impl: python, python: "3.9", postgres: "postgres:17", libpq: oldest} + - {impl: python, python: "3.10", postgres: "postgres:16", libpq: master} + - {impl: python, python: "3.11", postgres: "postgres:15"} + - {impl: python, python: "3.12", postgres: "postgres:14", libpq: newest} - {impl: python, python: "3.13", postgres: "postgres:12"} - - {impl: c, python: "3.8", postgres: "postgres:12", libpq: master} - - {impl: c, python: "3.9", postgres: "postgres:13"} - - {impl: c, python: "3.10", postgres: "postgres:14"} + - {impl: c, python: "3.9", postgres: "postgres:12", libpq: master} + - {impl: c, python: "3.10", postgres: "postgres:13"} - {impl: c, python: "3.11", postgres: "postgres:15", libpq: oldest} - {impl: c, python: "3.12", postgres: "postgres:16", libpq: newest} - {impl: c, python: "3.13", postgres: "postgres:17"} - - {impl: python, python: "3.8", ext: gevent, postgres: "postgres:17"} - - {impl: c, python: "3.12", ext: gevent, postgres: "postgres:14"} + - {impl: python, python: "3.9", ext: gevent, postgres: "postgres:17"} - {impl: python, python: "3.9", ext: dns, postgres: "postgres:14"} - {impl: python, python: "3.9", ext: postgis, postgres: "postgis/postgis"} - {impl: python, python: "3.10", ext: numpy, postgres: "postgres:14"} - {impl: c, python: "3.11", ext: numpy, postgres: "postgres:15"} + - {impl: c, python: "3.12", ext: gevent, postgres: "postgres:14"} # Test with minimum dependencies versions # WARNING: when bumping min version, make sure that the dependencies # # in tests/constraints.txt are updated and that binary packages # are available for such version. - - {impl: c, python: "3.8", ext: min, postgres: "postgres:15"} + - {impl: c, python: "3.9", ext: min, postgres: "postgres:15"} # Test memory alignment - {impl: c, python: "3.12", ext: align, postgres: "postgres:16"} @@ -220,9 +218,7 @@ jobs: fail-fast: false matrix: include: - - {impl: python, python: "3.8"} - {impl: python, python: "3.9"} - - {impl: c, python: "3.8"} - {impl: c, python: "3.9"} env: @@ -281,13 +277,11 @@ jobs: fail-fast: false matrix: include: - - {impl: python, python: "3.8"} - {impl: python, python: "3.9"} - {impl: python, python: "3.10"} - {impl: python, python: "3.11"} - {impl: python, python: "3.12"} - {impl: python, python: "3.13"} - - {impl: c, python: "3.8"} - {impl: c, python: "3.9"} - {impl: c, python: "3.10"} - {impl: c, python: "3.11"} diff --git a/docs/basic/install.rst b/docs/basic/install.rst index c239cb8a5..084b7b9b7 100644 --- a/docs/basic/install.rst +++ b/docs/basic/install.rst @@ -22,10 +22,11 @@ Supported systems The Psycopg version documented here has *official and tested* support for: -- Python: from version 3.8 to 3.13 +- Python: from version 3.9 to 3.13 - - Python 3.6 supported before Psycopg 3.1 + - Python 3.8 supported before Psycopg 3.3 - Python 3.7 supported before Psycopg 3.2 + - Python 3.6 supported before Psycopg 3.1 - PyPy: from version 3.9 to 3.10 diff --git a/docs/news.rst b/docs/news.rst index bedb457e2..6028da269 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -7,16 +7,25 @@ ``psycopg`` release notes ========================= -Current release +Future releases --------------- -Psycopg 3.2.4 -^^^^^^^^^^^^^ +Python 3.3.0 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Drop support for Python 3.8. + + +Psycopg 3.2.4 (unreleased) +^^^^^^^^^^^^^^^^^^^^^^^^^^ - Make sure that the notifies callback is called during the use of the `~Connection.notifies()` generator (:ticket:`#972`). +Current release +--------------- + Psycopg 3.2.3 ^^^^^^^^^^^^^ diff --git a/docs/release.rst b/docs/release.rst index d6a44f7ff..c379934d0 100644 --- a/docs/release.rst +++ b/docs/release.rst @@ -32,7 +32,7 @@ How to make a psycopg release .. __: https://github.com/psycopg/psycopg/actions/workflows/packages-bin.yml .. __: https://github.com/psycopg/psycopg/actions/workflows/packages-pool.yml -- Delete the ``wheelhouse`` directory there is one. +- Delete the ``wheelhouse`` directory if there is one. - Build m1 packages by running ``./tools/build/run_build_macos_arm64.sh BRANCH``. On successful completion it will save built packages in ``wheelhouse`` @@ -110,3 +110,25 @@ When a new Python major version is released - Update the list of versions in ``tools/build/build_macos_arm64.sh`` to include the new version. Look for both the ``python_versions`` variable and the ``CIBW_BUILD`` environment variable. + + +When dropping end-of-life Python versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Update project metadata, ``requires-python`` and (maybe) package dependencies + in ``pyproject.toml`` files of the corresponding ``psycopg`` directories. + +- Update GitHub Actions workflow files in the ``.github/workflows/`` directory, + e.g., ``tests.yml``, ``.3rd-party-tests.yml``, ``packages-bin.yml``. + +- Bump versions in the ``tests/constraints.txt`` file if it is necessary. + +- You may grep throughout the project for occurrences of a version to be dropped. + However, favouring smaller pull requests is convenient and easy to review. + An example for grepping `end-of-life ` Python 3.8:: + + git grep -E -e '\b3\.8\b' -e '\b(cp)?38\b' -e '\b3, 8\b' + +Examples: + +- `PR #977 `_ diff --git a/psycopg/psycopg/_acompat.py b/psycopg/psycopg/_acompat.py index d7290889d..83aeba36d 100644 --- a/psycopg/psycopg/_acompat.py +++ b/psycopg/psycopg/_acompat.py @@ -13,7 +13,7 @@ from __future__ import annotations import queue import asyncio import threading -from typing import Any, Callable, Coroutine, TYPE_CHECKING +from typing import Any, Callable, Coroutine from ._compat import TypeAlias, TypeVar @@ -21,26 +21,8 @@ Worker: TypeAlias = threading.Thread AWorker: TypeAlias = "asyncio.Task[None]" T = TypeVar("T") -# Hack required on Python 3.8 because subclassing Queue[T] fails at runtime. -# https://stackoverflow.com/questions/45414066/mypy-how-to-define-a-generic-subclass -if TYPE_CHECKING: - _GQueue: TypeAlias = queue.Queue - _AGQueue: TypeAlias = asyncio.Queue -else: - - class FakeGenericMeta(type): - def __getitem__(self, item): - return self - - class _GQueue(queue.Queue, metaclass=FakeGenericMeta): - pass - - class _AGQueue(asyncio.Queue, metaclass=FakeGenericMeta): - pass - - -class Queue(_GQueue[T]): +class Queue(queue.Queue[T]): """ A Queue subclass with an interruptible get() method. """ @@ -52,7 +34,7 @@ class Queue(_GQueue[T]): return super().get(block=block, timeout=timeout) -class AQueue(_AGQueue[T]): +class AQueue(asyncio.Queue[T]): pass diff --git a/psycopg/psycopg/abc.py b/psycopg/psycopg/abc.py index b1e403183..470a23855 100644 --- a/psycopg/psycopg/abc.py +++ b/psycopg/psycopg/abc.py @@ -7,8 +7,7 @@ Protocol objects representing different implementations of the same classes. from __future__ import annotations from typing import Any, Callable, Generator, Mapping -from typing import Protocol, Sequence, TYPE_CHECKING -from typing import Dict, Union # drop with Python 3.8 +from typing import Protocol, Sequence, TYPE_CHECKING, Union from . import pq from ._enums import PyFormat as PyFormat @@ -33,7 +32,7 @@ ConnectionType = TypeVar("ConnectionType", bound="BaseConnection[Any]") PipelineCommand: TypeAlias = Callable[[], None] DumperKey: TypeAlias = Union[type, "tuple[DumperKey, ...]"] ConnParam: TypeAlias = Union[str, int, None] -ConnDict: TypeAlias = Dict[str, ConnParam] +ConnDict: TypeAlias = dict[str, ConnParam] ConnMapping: TypeAlias = Mapping[str, ConnParam] diff --git a/psycopg/psycopg/pq/abc.py b/psycopg/psycopg/pq/abc.py index 8e91fd2f5..56cfe2762 100644 --- a/psycopg/psycopg/pq/abc.py +++ b/psycopg/psycopg/pq/abc.py @@ -6,8 +6,7 @@ Protocol objects to represent objects exposed by different pq implementations. from __future__ import annotations -from typing import Any, Callable, Protocol, Sequence, TYPE_CHECKING -from typing import Union # drop with Python 3.8 +from typing import Any, Callable, Protocol, Sequence, TYPE_CHECKING, Union from ._enums import Format, Trace from .._compat import Self, TypeAlias diff --git a/psycopg/psycopg/rows.py b/psycopg/psycopg/rows.py index fe97b7d56..61c3d610a 100644 --- a/psycopg/psycopg/rows.py +++ b/psycopg/psycopg/rows.py @@ -9,7 +9,6 @@ from __future__ import annotations import functools from typing import Any, Callable, NamedTuple, NoReturn from typing import TYPE_CHECKING, Protocol, Sequence -from typing import Dict, Tuple # drop with Python 3.8 from collections import namedtuple from . import pq @@ -83,13 +82,13 @@ class BaseRowFactory(Protocol[Row]): def __call__(self, __cursor: BaseCursor[Any, Any]) -> RowMaker[Row]: ... -TupleRow: TypeAlias = Tuple[Any, ...] +TupleRow: TypeAlias = tuple[Any, ...] """ An alias for the type returned by `tuple_row()` (i.e. a tuple of any content). """ -DictRow: TypeAlias = Dict[str, Any] +DictRow: TypeAlias = dict[str, Any] """ An alias for the type returned by `dict_row()` diff --git a/psycopg/pyproject.toml b/psycopg/pyproject.toml index efa2eddb8..9550c1a2e 100644 --- a/psycopg/pyproject.toml +++ b/psycopg/pyproject.toml @@ -13,8 +13,7 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -27,9 +26,8 @@ classifiers = [ "Topic :: Software Development", "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" dependencies = [ - "backports.zoneinfo >= 0.2.0; python_version < '3.9'", "typing-extensions >= 4.6; python_version < '3.13'", "tzdata; sys_platform == 'win32'", ] diff --git a/psycopg_c/pyproject.toml b/psycopg_c/pyproject.toml index 2c9cf1fc5..aa24b989e 100644 --- a/psycopg_c/pyproject.toml +++ b/psycopg_c/pyproject.toml @@ -34,8 +34,7 @@ classifiers = [ "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Cython", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -47,7 +46,7 @@ classifiers = [ "Topic :: Software Development", "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" [[project.authors]] name = "Daniele Varrazzo" diff --git a/psycopg_pool/psycopg_pool/_acompat.py b/psycopg_pool/psycopg_pool/_acompat.py index 9081d3ecd..6f111ebf7 100644 --- a/psycopg_pool/psycopg_pool/_acompat.py +++ b/psycopg_pool/psycopg_pool/_acompat.py @@ -15,7 +15,7 @@ import queue import asyncio import logging import threading -from typing import Any, Callable, Coroutine, TYPE_CHECKING +from typing import Any, Callable, Coroutine from ._compat import TypeAlias, TypeVar @@ -32,24 +32,6 @@ sleep = time.sleep Worker: TypeAlias = threading.Thread AWorker: TypeAlias = "asyncio.Task[None]" -# Hack required on Python 3.8 because subclassing Queue[T] fails at runtime. -# https://stackoverflow.com/questions/45414066/mypy-how-to-define-a-generic-subclass -if TYPE_CHECKING: - _GQueue: TypeAlias = queue.Queue - _AGQueue: TypeAlias = asyncio.Queue - -else: - - class FakeGenericMeta(type): - def __getitem__(self, item): - return self - - class _GQueue(queue.Queue, metaclass=FakeGenericMeta): - pass - - class _AGQueue(asyncio.Queue, metaclass=FakeGenericMeta): - pass - def current_thread_name() -> str: return threading.current_thread().name @@ -60,7 +42,7 @@ def current_task_name() -> str: return t.get_name() if t else "" -class Queue(_GQueue[T]): +class Queue(queue.Queue[T]): """ A Queue subclass with an interruptible get() method. """ @@ -102,7 +84,7 @@ class ACondition(asyncio.Condition): return False -class AQueue(_AGQueue[T]): +class AQueue(asyncio.Queue[T]): pass diff --git a/psycopg_pool/psycopg_pool/abc.py b/psycopg_pool/psycopg_pool/abc.py index 1c13cad1d..d79f82f87 100644 --- a/psycopg_pool/psycopg_pool/abc.py +++ b/psycopg_pool/psycopg_pool/abc.py @@ -6,8 +6,7 @@ Types used in the psycopg_pool package from __future__ import annotations -from typing import Awaitable, Callable, TYPE_CHECKING -from typing import Union # drop with Python 3.8 +from typing import Awaitable, Callable, TYPE_CHECKING, Union from ._compat import TypeAlias, TypeVar diff --git a/psycopg_pool/pyproject.toml b/psycopg_pool/pyproject.toml index ef7437269..a1c83857d 100644 --- a/psycopg_pool/pyproject.toml +++ b/psycopg_pool/pyproject.toml @@ -13,8 +13,7 @@ classifiers = [ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", @@ -27,7 +26,7 @@ classifiers = [ "Topic :: Software Development", "Topic :: Software Development :: Libraries :: Python Modules", ] -requires-python = ">= 3.8" +requires-python = ">= 3.9" dependencies = [ "typing-extensions >= 4.6", ] diff --git a/tests/README.rst b/tests/README.rst index 172b52d61..9c1dc90d4 100644 --- a/tests/README.rst +++ b/tests/README.rst @@ -21,7 +21,7 @@ Test options $ pytest ========================= test session starts ========================= - platform linux -- Python 3.8.5, pytest-6.0.2, py-1.10.0, pluggy-0.13.1 + platform linux -- Python 3.9, pytest-6.0.2, py-1.10.0, pluggy-0.13.1 Using --randomly-seed=2416596601 libpq available: 130002 libpq wrapper implementation: c @@ -80,7 +80,7 @@ a set of env vars working for your setup:: $ docker run -ti --rm --volume `pwd`:/src --workdir /src \ -e PSYCOPG_TEST_DSN -e PGHOST=172.17.0.1 -e PGUSER=`whoami` \ - python:3.8 bash + python:3.9 bash # pip install -e "./psycopg[test]" ./psycopg_pool ./psycopg_c # pytest diff --git a/tests/acompat.py b/tests/acompat.py index 868b682ec..0605b5a6e 100644 --- a/tests/acompat.py +++ b/tests/acompat.py @@ -118,7 +118,7 @@ class AEvent(asyncio.Event): await asyncio.wait_for(self.wait(), timeout) -class Queue(queue.Queue): # type: ignore[type-arg] # can be dropped after Python 3.8 +class Queue(queue.Queue): # type: ignore[type-arg] """ A Queue subclass with an interruptible get() method. """ diff --git a/tests/constraints.txt b/tests/constraints.txt index 5341d2a75..89ecac84d 100644 --- a/tests/constraints.txt +++ b/tests/constraints.txt @@ -35,8 +35,13 @@ Cython == 3.0.0 tomli == 2.0.1 # Undeclared extras to "unblock" extra features - -shapely == 1.7.0 -# Warning: binary package only available up to python <= 3.8. -# Bump to a higher version when min supported python version increases past it. -numpy == 1.18.0 +# +# Warning: the versions specified for these packages are the oldest versions +# offering a binary package for the oldest Python version we support. +# +# When the minimum supported Python version is increased, these dependencies +# might need to be updated. +# +# Grep help: the current minimum supported version is Python 3.9. +shapely == 1.8.0 +numpy == 1.20.0 diff --git a/tools/build/build_macos_arm64.sh b/tools/build/build_macos_arm64.sh index e4e3b56a9..a4a889a9d 100755 --- a/tools/build/build_macos_arm64.sh +++ b/tools/build/build_macos_arm64.sh @@ -9,7 +9,7 @@ set -euo pipefail -python_versions="3.8.19 3.9.19 3.10.14 3.11.9 3.12.5 3.13.0" +python_versions="3.9.19 3.10.14 3.11.9 3.12.5 3.13.0" pg_version=17 function log { diff --git a/tools/bump_version.py b/tools/bump_version.py index 37dae36d3..8c6c5a60b 100755 --- a/tools/bump_version.py +++ b/tools/bump_version.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -"""Bump the version number of the project. -""" +"""Bump the version number of the project.""" from __future__ import annotations