Skip to content
Merged
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.7, 3.8, and 3.9 support has been dropped.


## Version 5.28
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ install the pre-commit hooks as described below instead. They will take care of
updating the code if necessary.

Setting up the development environment:
* Install Python 3.8+
* Install Python 3.10+
* Install the requirements
```bash
$ python3 -m pip install -U pip
Expand Down
9 changes: 3 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,10 @@ breaking API changes.

See also: https://neo4j.com/developer/kb/neo4j-supported-versions/

+ Python 3.13 supported (since driver version 5.26.0).
+ Python 3.12 supported (since driver version 5.14.0).
+ Python 3.11 supported (since driver version 5.3.0).
+ Python 3.13 supported.
+ Python 3.12 supported.
+ Python 3.11 supported.
+ Python 3.10 supported.
+ Python 3.9 supported.
+ Python 3.8 supported.
+ Python 3.7 supported.


Installation
Expand Down
2 changes: 1 addition & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
@@ -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.7 - 3.12
The versions of Python supported by this driver are CPython 3.10 - 3.13

## 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.
Expand Down
6 changes: 1 addition & 5 deletions benchkit/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from __future__ import annotations

import sys
import typing as t
from contextlib import contextmanager
from multiprocessing import Semaphore
Expand Down Expand Up @@ -44,10 +43,7 @@
from .workloads import Workload


if sys.version_info < (3, 8):
T_App: te.TypeAlias = "Sanic"
else:
T_App: te.TypeAlias = "Sanic[Config, BenchKitContext]"
T_App: te.TypeAlias = "Sanic[Config, BenchKitContext]"


def create_app() -> T_App:
Expand Down
3 changes: 2 additions & 1 deletion benchkit/workloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import enum
import typing as t
from dataclasses import dataclass
from typing import Iterator

import typing_extensions as te


if t.TYPE_CHECKING:
from collections.abc import Iterator

from neo4j import (
AsyncDriver,
AsyncManagedTransaction,
Expand Down
17 changes: 9 additions & 8 deletions bin/make-unasync
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ class CustomRule(unasync.Rule):
# it's not pretty, but it works
# typing.Awaitable[...] -> typing.Union[...]
self.token_replacements["Awaitable"] = "Union"
self.token_replacements["aiter"] = "iter"
self.token_replacements["anext"] = "next"

def _unasync_tokens(self, tokens):
# copy from unasync to fix handling of multiline strings
Expand Down Expand Up @@ -300,16 +302,15 @@ def apply_isort(paths):

def apply_changes(paths):
def files_equal(path1, path2):
with open(path1, "rb") as f1:
with open(path2, "rb") as f2:
with open(path1, "rb") as f1, open(path2, "rb") as f2:
data1 = f1.read(1024)
data2 = f2.read(1024)
while data1 or data2:
if data1 != data2:
changed_paths[path1] = path2
return False
data1 = f1.read(1024)
data2 = f2.read(1024)
while data1 or data2:
if data1 != data2:
changed_paths[path1] = path2
return False
data1 = f1.read(1024)
data2 = f2.read(1024)
return True

changed_paths = {}
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Sphinx Documentation
====================

Building the docs requires Python 3.8+
Building the docs requires Python 3.10+

In project root
```
Expand Down
5 changes: 0 additions & 5 deletions docs/source/async_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ Async API Documentation

.. versionadded:: 5.0

.. warning::
There are known issue with Python 3.8 and the async driver where it
gradually slows down. Generally, it's recommended to use the latest
supported version of Python for best performance, stability, and security.

******************
AsyncGraphDatabase
******************
Expand Down
9 changes: 3 additions & 6 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@ See https://neo4j.com/developer/kb/neo4j-supported-versions/ for a driver-server

Python versions supported:

* Python 3.13 (added in driver version 5.26.0)
* Python 3.12 (added in driver version 5.14.0)
* Python 3.11 (added in driver version 5.3.0)
* Python 3.13
* Python 3.12
* Python 3.11
* Python 3.10
* Python 3.9
* Python 3.8
* Python 3.7


******
Expand Down
16 changes: 7 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,14 @@ authors = [
{name = "Neo4j, Inc.", email = "[email protected]"},
]
dependencies = ["pytz"]
requires-python = ">=3.7"
requires-python = ">=3.10"
keywords = ["neo4j", "graph", "database"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: AsyncIO",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -65,9 +62,7 @@ pyarrow = ["pyarrow >= 1.0.0"]

[build-system]
requires = [
"setuptools == 68.0.0; python_version <= '3.7'", # dropped support for Python 3.7 in 68.1.0
"setuptools == 75.3.0; python_version == '3.8'", # dropped support for Python 3.8 in 75.4.0
"setuptools == 75.6.0; python_version >= '3.9'",
"setuptools == 75.6.0",
# TODO: 6.0 - can be removed once `setup.py` is simplified
"tomlkit == 0.12.5", # dropped support (at least CI testing) for Python 3.7 in 0.13.0
]
Expand Down Expand Up @@ -123,6 +118,7 @@ use_parentheses = true
[tool.pytest.ini_options]
mock_use_standalone_module = true
asyncio_mode = "strict"
asyncio_default_fixture_loop_scope="function"


[tool.mypy]
Expand All @@ -148,8 +144,7 @@ extend-exclude = [
preview = true # to get CPY lints
extend-ignore = [
"RUF002", # allow ’ (RIGHT SINGLE QUOTATION MARK) to be used as an apostrophe (e.g. "it’s")
"SIM117", # TODO: when Python 3.10+ is the minimum,
# we can start to use multi-item `with` statements

# pydocstyle
"D1", # disable check for undocumented items (way too noisy)
"D203", # `one-blank-line-before-class`
Expand Down Expand Up @@ -199,6 +194,9 @@ extend-ignore = [
# needs fixing in ruff to work with typing.Protocol
# https://github.com/astral-sh/ruff/issues/13307
"FURB180",

# rule is deprected and suggests not recommended practice
"UP038",
]
select = [
# ruff
Expand Down
34 changes: 17 additions & 17 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,31 @@
-e .[pandas,numpy,pyarrow]

# needed for packaging
build>=1.1.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
build>=1.2.2

# auto-generate sync driver from async code
unasync==0.5.0
# pre-commit hooks and tools
pre-commit>=2.21.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
isort>=5.11.5 # TODO: 6.0 - bump when support for Python 3.7 is dropped
mypy>=1.4.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
typing-extensions>=4.7.1
types-pytz>=2023.3.1.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
ruff>=0.8.2
pre-commit>=4.2.0
isort>=6.0.1
mypy>=1.15.0
typing-extensions>=4.13.2
types-pytz>=2025.2.0.20250326
ruff>=0.11.6

# needed for running tests
coverage[toml]>=7.2.7 # TODO: 6.0 - bump when support for Python 3.7 is dropped
coverage[toml]>=7.8.0
freezegun>=1.5.1
mock>=5.1.0
pytest>=7.4.4 # TODO: 6.0 - bump when support for Python 3.7 is dropped
pytest-asyncio~=0.21.2 # TODO: 6.0 - bump when support for Python 3.7 is dropped
pytest-benchmark>=4.0.0
pytest-cov>=4.1.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
pytest-mock>=3.11.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
tox>=4.8.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
mock>=5.2.0
pytest>=8.3.5
pytest-asyncio>=0.26.0
pytest-benchmark>=5.1.0
pytest-cov>=6.1.1
pytest-mock>=3.14.0
tox>=4.25.0

# needed for building docs
Sphinx>=5.3.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
Sphinx>=8.1.3

# needed for BenchKit
sanic>=23.3.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
sanic>=25.3.0
2 changes: 1 addition & 1 deletion src/neo4j/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def __getattr__(name) -> _t.Any:
raise AttributeError(f"module {__name__} has no attribute {name}")


def __dir__() -> _t.List[str]:
def __dir__() -> list[str]:
return __all__


Expand Down
27 changes: 12 additions & 15 deletions src/neo4j/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ class NotificationMinimumSeverity(str, Enum):


if t.TYPE_CHECKING:
T_NotificationMinimumSeverity = t.Union[
NotificationMinimumSeverity,
te.Literal[
T_NotificationMinimumSeverity = (
NotificationMinimumSeverity
| te.Literal[
"OFF",
"WARNING",
"INFORMATION",
],
]
]
)
__all__.append("T_NotificationMinimumSeverity")


Expand Down Expand Up @@ -213,10 +213,10 @@ class NotificationDisabledClassification(str, Enum):


if t.TYPE_CHECKING:
T_NotificationDisabledCategory = t.Union[
NotificationDisabledCategory,
NotificationDisabledClassification,
te.Literal[
T_NotificationDisabledCategory = (
NotificationDisabledCategory
| NotificationDisabledClassification
| te.Literal[
"HINT",
"UNRECOGNIZED",
"UNSUPPORTED",
Expand All @@ -226,8 +226,8 @@ class NotificationDisabledClassification(str, Enum):
"SECURITY",
"TOPOLOGY",
"SCHEMA",
],
]
]
)
__all__.append("T_NotificationDisabledCategory")


Expand Down Expand Up @@ -343,8 +343,5 @@ class TelemetryAPI(int, Enum):


if t.TYPE_CHECKING:
T_RoutingControl = t.Union[
RoutingControl,
te.Literal["r", "w"],
]
T_RoutingControl = RoutingControl | te.Literal["r", "w"]
__all__.append("T_RoutingControl")
2 changes: 1 addition & 1 deletion src/neo4j/_async/_debug/_concurrency_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async def inner(*args, **kwargs):
tbs = deepcopy(self.__tracebacks)
if acquired:
try:
item = await iter_.__anext__()
item = await anext(iter_)
except StopAsyncIteration:
return
finally:
Expand Down
4 changes: 2 additions & 2 deletions src/neo4j/_async/bookmark_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
)


TBmSupplier = t.Callable[[], t.Union[Bookmarks, t.Awaitable[Bookmarks]]]
TBmConsumer = t.Callable[[Bookmarks], t.Union[None, t.Awaitable[None]]]
TBmSupplier = t.Callable[[], Bookmarks | t.Awaitable[Bookmarks]]
TBmConsumer = t.Callable[[Bookmarks], None | t.Awaitable[None]]


def _bookmarks_to_set(
Expand Down
8 changes: 2 additions & 6 deletions src/neo4j/_async/home_db_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,8 @@
if t.TYPE_CHECKING:
import typing_extensions as te

TKey: te.TypeAlias = t.Union[
str,
t.Tuple[t.Tuple[str, t.Hashable], ...],
t.Tuple[None],
]
TVal: te.TypeAlias = t.Tuple[float, str]
TKey: te.TypeAlias = str | tuple[tuple[str, t.Hashable], ...] | tuple[None]
TVal: te.TypeAlias = tuple[float, str]


class AsyncHomeDbCache:
Expand Down
3 changes: 2 additions & 1 deletion src/neo4j/_async/io/_bolt3.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ async def route(
await self.send_all()
await self.fetch_all()
return [
dict(zip(metadata.get("fields", ()), values)) for values in records
dict(zip(metadata.get("fields", ()), values, strict=True))
for values in records
]

def run(
Expand Down
3 changes: 2 additions & 1 deletion src/neo4j/_async/io/_bolt4.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ async def route(
await self.send_all()
await self.fetch_all()
return [
dict(zip(metadata.get("fields", ()), values)) for values in records
dict(zip(metadata.get("fields", ()), values, strict=True))
for values in records
]

def run(
Expand Down
Loading