Close #976.
# 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
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:
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"
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:
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"}
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:
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"}
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
``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
^^^^^^^^^^^^^
.. __: 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``
- 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 <https://endoflife.date/python>` Python 3.8::
+
+ git grep -E -e '\b3\.8\b' -e '\b(cp)?38\b' -e '\b3, 8\b'
+
+Examples:
+
+- `PR #977 <https://github.com/psycopg/psycopg/pull/977>`_
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
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.
"""
return super().get(block=block, timeout=timeout)
-class AQueue(_AGQueue[T]):
+class AQueue(asyncio.Queue[T]):
pass
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
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]
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
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
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()`
"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",
"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'",
]
"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",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Python Modules",
]
-requires-python = ">= 3.8"
+requires-python = ">= 3.9"
[[project.authors]]
name = "Daniele Varrazzo"
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
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
return t.get_name() if t else "<no task>"
-class Queue(_GQueue[T]):
+class Queue(queue.Queue[T]):
"""
A Queue subclass with an interruptible get() method.
"""
return False
-class AQueue(_AGQueue[T]):
+class AQueue(asyncio.Queue[T]):
pass
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
"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",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries :: Python Modules",
]
-requires-python = ">= 3.8"
+requires-python = ">= 3.9"
dependencies = [
"typing-extensions >= 4.6",
]
$ 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
$ 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
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.
"""
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
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 {
#!/usr/bin/env python
-"""Bump the version number of the project.
-"""
+"""Bump the version number of the project."""
from __future__ import annotations