diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 230bfdf2..80f724f1 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -23,6 +23,8 @@ jobs: tox-factor: 'py312' - semver: '3.13' tox-factor: 'py313' + - semver: '3.14' + tox-factor: 'py314' steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b5a40b2..ed21ff5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog. ## NEXT RELEASE -- No breaking or major changes. +- Python 3.14 support added. ## Version 6.0 diff --git a/README.rst b/README.rst index 8c44ae60..215439d3 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ Driver upgrades within a major version will never contain breaking API changes. For version compatibility with Neo4j server, please refer to: https://neo4j.com/developer/kb/neo4j-supported-versions/ ++ Python 3.14 supported. + Python 3.13 supported. + Python 3.12 supported. + Python 3.11 supported. diff --git a/TESTING.md b/TESTING.md index db07ead9..bc4d5114 100644 --- a/TESTING.md +++ b/TESTING.md @@ -1,6 +1,6 @@ # Neo4j Driver Testing To run driver tests, [Tox](https://tox.readthedocs.io) is required as well as at least one version of Python. -The versions of Python supported by this driver are CPython 3.10 - 3.13 +The versions of Python supported by this driver are CPython 3.10 - 3.14 ## Testing with TestKit TestKit is the shared test suite used by all official (and some community contributed) Neo4j drivers to ensure consistent and correct behavior across all drivers. diff --git a/docs/source/index.rst b/docs/source/index.rst index abffa3af..ac13f381 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,6 +19,7 @@ See https://neo4j.com/developer/kb/neo4j-supported-versions/ for a driver-server Python versions supported: +* Python 3.14 * Python 3.13 * Python 3.12 * Python 3.11 diff --git a/pyproject.toml b/pyproject.toml index d7e34bb5..9d3a1338 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,7 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Topic :: Database", "Topic :: Software Development", "Typing :: Typed", diff --git a/src/neo4j/_async/io/_bolt.py b/src/neo4j/_async/io/_bolt.py index 3c2d9e73..62d4d206 100644 --- a/src/neo4j/_async/io/_bolt.py +++ b/src/neo4j/_async/io/_bolt.py @@ -18,6 +18,7 @@ import abc import asyncio +import inspect from collections import deque from logging import getLogger from time import monotonic @@ -198,7 +199,7 @@ def __init__( ) def __del__(self): - if not asyncio.iscoroutinefunction(self.close): + if not inspect.iscoroutinefunction(self.close): self.close() @abc.abstractmethod diff --git a/src/neo4j/_async/io/_common.py b/src/neo4j/_async/io/_common.py index bcd02c33..dbffb9f9 100644 --- a/src/neo4j/_async/io/_common.py +++ b/src/neo4j/_async/io/_common.py @@ -15,6 +15,7 @@ import asyncio +import inspect import logging from contextlib import suppress from struct import pack as struct_pack @@ -191,7 +192,7 @@ def inner(*args, **kwargs): try: func(*args, **kwargs) except (Neo4jError, ServiceUnavailable, SessionExpired) as exc: - assert not asyncio.iscoroutinefunction(self.__on_error) + assert not inspect.iscoroutinefunction(self.__on_error) self.__on_error(exc) raise @@ -212,7 +213,7 @@ async def inner(*args, **kwargs): return inner - if asyncio.iscoroutinefunction(connection_attr): + if inspect.iscoroutinefunction(connection_attr): return outer_async(connection_attr) return outer(connection_attr) diff --git a/src/neo4j/_async_compat/util.py b/src/neo4j/_async_compat/util.py index e59ceda1..c7225032 100644 --- a/src/neo4j/_async_compat/util.py +++ b/src/neo4j/_async_compat/util.py @@ -67,7 +67,7 @@ async def callback(cb, *args, **kwargs): @staticmethod def shielded(coro_function): - assert asyncio.iscoroutinefunction(coro_function) + assert inspect.iscoroutinefunction(coro_function) @wraps(coro_function) async def shielded_function(*args, **kwargs): diff --git a/src/neo4j/_sync/io/_bolt.py b/src/neo4j/_sync/io/_bolt.py index 134c0924..076216d3 100644 --- a/src/neo4j/_sync/io/_bolt.py +++ b/src/neo4j/_sync/io/_bolt.py @@ -18,6 +18,7 @@ import abc import asyncio +import inspect from collections import deque from logging import getLogger from time import monotonic @@ -198,7 +199,7 @@ def __init__( ) def __del__(self): - if not asyncio.iscoroutinefunction(self.close): + if not inspect.iscoroutinefunction(self.close): self.close() @abc.abstractmethod diff --git a/src/neo4j/_sync/io/_common.py b/src/neo4j/_sync/io/_common.py index cdf49af0..245cc850 100644 --- a/src/neo4j/_sync/io/_common.py +++ b/src/neo4j/_sync/io/_common.py @@ -15,6 +15,7 @@ import asyncio +import inspect import logging from contextlib import suppress from struct import pack as struct_pack @@ -191,7 +192,7 @@ def inner(*args, **kwargs): try: func(*args, **kwargs) except (Neo4jError, ServiceUnavailable, SessionExpired) as exc: - assert not asyncio.iscoroutinefunction(self.__on_error) + assert not inspect.iscoroutinefunction(self.__on_error) self.__on_error(exc) raise @@ -212,7 +213,7 @@ def inner(*args, **kwargs): return inner - if asyncio.iscoroutinefunction(connection_attr): + if inspect.iscoroutinefunction(connection_attr): return outer_async(connection_attr) return outer(connection_attr) diff --git a/src/neo4j/_warnings.py b/src/neo4j/_warnings.py index 4bebeb6a..7affca02 100644 --- a/src/neo4j/_warnings.py +++ b/src/neo4j/_warnings.py @@ -16,9 +16,8 @@ from __future__ import annotations -import asyncio +import inspect from functools import wraps -from inspect import isclass from warnings import warn from . import _typing as t @@ -98,7 +97,7 @@ def _make_warning_decorator( warning_func: _WarningFunc, ) -> t.Callable[[_FuncT], _FuncT]: def decorator(f): - if asyncio.iscoroutinefunction(f): + if inspect.iscoroutinefunction(f): @wraps(f) async def inner(*args, **kwargs): @@ -107,7 +106,8 @@ async def inner(*args, **kwargs): inner._without_warning = f return inner - if isclass(f): + + if inspect.isclass(f): if hasattr(f, "__init__"): original_init = f.__init__ @@ -125,6 +125,7 @@ def _without_warning(cls, *args, **kwargs): f._without_warning = classmethod(_without_warning) return f raise TypeError("Cannot decorate class without __init__") + else: @wraps(f) diff --git a/testkit/Dockerfile b/testkit/Dockerfile index 9727c1be..d38ccc4f 100644 --- a/testkit/Dockerfile +++ b/testkit/Dockerfile @@ -56,7 +56,7 @@ ENV PIP_NO_CACHE_DIR=1 FROM base AS base-py-arg # Install all supported Python versions -ARG PYTHON_VERSIONS="3.13 3.12 3.11 3.10" +ARG PYTHON_VERSIONS="3.14 3.13 3.12 3.11 3.10" FROM base AS base-py-arg-single-python diff --git a/tests/conftest.py b/tests/conftest.py index b56aefaa..16bf1ecb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -17,6 +17,7 @@ from __future__ import annotations import asyncio +import inspect import sys from functools import wraps @@ -186,7 +187,7 @@ def neo4j_session(neo4j_driver): @pytest_asyncio.fixture def aio_benchmark(benchmark, event_loop): def _wrapper(func, *args, **kwargs): - if asyncio.iscoroutinefunction(func): + if inspect.iscoroutinefunction(func): @benchmark def _(): diff --git a/tox.ini b/tox.ini index f31fae52..606bec65 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{310,311,312,313}-{unit,integration,performance} +envlist = py{310,311,312,313,314}-{unit,integration,performance} [testenv] passenv = TEST_*