Skip to content

Commit c058f44

Browse files
committed
Merge branch '5.0' into bolt-handshake-v2
2 parents 60d77c8 + 72c9bf8 commit c058f44

File tree

107 files changed

+6027
-673
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+6027
-673
lines changed

.gitattributes

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# configure github not to display generated files
22
/src/neo4j/_sync/** linguist-generated=true
3-
/tests/unit/sync_/** linguist-generated=true
4-
/tests/integration/sync_/** linguist-generated=true
3+
/tests/unit/sync/** linguist-generated=true
4+
/tests/integration/sync/** linguist-generated=true
55
/testkitbackend/_sync/** linguist-generated=true

.pre-commit-config.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ repos:
1919
- batch
2020
- id: trailing-whitespace
2121
args: [ --markdown-linebreak-ext=md ]
22-
- repo: https://github.com/pycqa/isort
23-
rev: 5.12.0
22+
- repo: local
2423
hooks:
2524
- id: isort
25+
name: isort
26+
entry: isort
27+
types_or: [ python, pyi ]
28+
language: system
2629
- repo: https://github.com/sphinx-contrib/sphinx-lint
2730
rev: e83a1a42a73284d301c05baaffc176042ffbcf82
2831
hooks:
@@ -45,7 +48,7 @@ repos:
4548
name: mypy static type check
4649
entry: mypy
4750
args: [ --show-error-codes, src, tests, testkitbackend, benchkit ]
48-
'types_or': [ python, pyi ]
51+
types_or: [ python, pyi ]
4952
language: system
5053
pass_filenames: false
5154
require_serial: true

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
See also https://github.com/neo4j/neo4j-python-driver/wiki for a full changelog.
44

55
## NEXT RELEASE
6-
- No breaking or major changes.
6+
- Since the types of `Relationship`s are tied to the `Graph` object they belong to, fixing `pickle` support for graph types means that `Relationship`s with the same name will have a different type after `deepcopy`ing or pickling and unpickling them or their graph.
7+
For more details, see https://github.com/neo4j/neo4j-python-driver/pull/1133
78

89

910
## Version 5.27

benchkit/app.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from __future__ import annotations
1818

19+
import sys
1920
import typing as t
2021
from contextlib import contextmanager
2122
from multiprocessing import Semaphore
@@ -43,7 +44,10 @@
4344
from .workloads import Workload
4445

4546

46-
T_App: te.TypeAlias = "Sanic[Config, BenchKitContext]"
47+
if sys.version_info < (3, 8):
48+
T_App: te.TypeAlias = "Sanic"
49+
else:
50+
T_App: te.TypeAlias = "Sanic[Config, BenchKitContext]"
4751

4852

4953
def create_app() -> T_App:

benchkit/workloads.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def parse(cls, query: t.Any) -> te.Self:
543543
@dataclass
544544
class _WorkloadConfig:
545545
database: str | None
546-
routing: t.Literal["r", "w"]
546+
routing: te.Literal["r", "w"]
547547

548548
@classmethod
549549
def parse(cls, data: t.Any) -> te.Self:
@@ -553,7 +553,7 @@ def parse(cls, data: t.Any) -> te.Self:
553553
if not isinstance(database, str):
554554
raise TypeError("Workload database must be a string")
555555

556-
routing: t.Literal["r", "w"] = "w"
556+
routing: te.Literal["r", "w"] = "w"
557557
if "routing" in data:
558558
raw_routing = data["routing"]
559559
if not isinstance(routing, str):

bin/dist-functions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,6 @@ function setup
121121
{
122122
ARGS="$*"
123123
rm -rf ${DIST} 2> /dev/null
124-
set_metadata_and_setup "neo4j-driver" "True" ${ARGS} --sdist # Legacy package; can be removed in 2.0
124+
set_metadata_and_setup "neo4j-driver" "True" ${ARGS} # Legacy package; can be removed in 2.0
125125
set_metadata_and_setup "neo4j" "False" ${ARGS}
126126
}

docs/source/api.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ Closing a driver will immediately shut down all connections in the pool.
260260
:param database\_:
261261
Database to execute the query against.
262262

263-
None (default) uses the database configured on the server side.
263+
:data:`None` (default) uses the database configured on the server
264+
side.
264265

265266
.. Note::
266267
It is recommended to always specify the database explicitly
@@ -1034,7 +1035,7 @@ Specifically, the following applies:
10341035
all queries within that session are executed with the explicit database
10351036
name 'movies' supplied. Any change to the user’s home database is
10361037
reflected only in sessions created after such change takes effect. This
1037-
behavior requires additional network communication. In clustered
1038+
behavior may require additional network communication. In clustered
10381039
environments, it is strongly recommended to avoid a single point of
10391040
failure. For instance, by ensuring that the connection URI resolves to
10401041
multiple endpoints. For older Bolt protocol versions the behavior is the

docs/source/async_api.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ Closing a driver will immediately shut down all connections in the pool.
247247
:param database\_:
248248
Database to execute the query against.
249249

250-
None (default) uses the database configured on the server side.
250+
:data:`None` (default) uses the database configured on the server
251+
side.
251252

252253
.. Note::
253254
It is recommended to always specify the database explicitly

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ use_parentheses = true
122122

123123
[tool.pytest.ini_options]
124124
mock_use_standalone_module = true
125-
asyncio_mode = "auto"
125+
asyncio_mode = "strict"
126126

127127

128128
[tool.mypy]

requirements-dev.txt

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,31 @@
22
-e .[pandas,numpy,pyarrow]
33

44
# needed for packaging
5-
build
5+
build>=1.1.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
66

77
# auto-generate sync driver from async code
8-
unasync>=0.5.0
8+
unasync==0.5.0
99
# pre-commit hooks and tools
10-
pre-commit>=2.15.0
11-
isort>=5.10.0
12-
mypy>=0.971
13-
typing-extensions>=4.3.0
14-
types-pytz>=2022.1.2
15-
ruff>=0.6.4
10+
pre-commit>=2.21.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
11+
isort>=5.11.5 # TODO: 6.0 - bump when support for Python 3.7 is dropped
12+
mypy>=1.4.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
13+
typing-extensions>=4.7.1
14+
types-pytz>=2023.3.1.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
15+
ruff>=0.8.2
1616

1717
# needed for running tests
18-
coverage[toml]>=5.5
18+
coverage[toml]>=7.2.7 # TODO: 6.0 - bump when support for Python 3.7 is dropped
1919
freezegun>=1.5.1
20-
mock>=4.0.3
21-
pytest>=6.2.5
22-
pytest-asyncio>=0.16.0
23-
pytest-benchmark>=3.4.1
24-
pytest-cov>=3.0.0
25-
pytest-mock>=3.6.1
26-
tox>=4.0.0
27-
teamcity-messages>=1.32
20+
mock>=5.1.0
21+
pytest>=7.4.4 # TODO: 6.0 - bump when support for Python 3.7 is dropped
22+
pytest-asyncio~=0.21.2 # TODO: 6.0 - bump when support for Python 3.7 is dropped
23+
pytest-benchmark>=4.0.0
24+
pytest-cov>=4.1.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
25+
pytest-mock>=3.11.1 # TODO: 6.0 - bump when support for Python 3.7 is dropped
26+
tox>=4.8.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
2827

2928
# needed for building docs
30-
sphinx
29+
Sphinx>=5.3.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped
3130

3231
# needed for BenchKit
33-
sanic>=23.12.1 ; python_version >= '3.8.0'
32+
sanic>=23.3.0 # TODO: 6.0 - bump when support for Python 3.7 is dropped

setup.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import pathlib
2222
import sys
23-
import warnings
2423
from contextlib import contextmanager
2524

2625
import tomlkit
@@ -37,13 +36,6 @@
3736
)
3837

3938

40-
if deprecated:
41-
warnings.warn(
42-
f"`{package}` is deprecated, please install `neo4j` instead.",
43-
DeprecationWarning,
44-
stacklevel=0,
45-
)
46-
4739
readme_path = THIS_DIR / "README.rst"
4840
readme = readme_path.read_text(encoding="utf-8")
4941

src/neo4j/_async/driver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,8 @@ async def example(driver: neo4j.AsyncDriver) -> int:
799799
:param database_:
800800
Database to execute the query against.
801801
802-
None (default) uses the database configured on the server side.
802+
:data:`None` (default) uses the database configured on the server
803+
side.
803804
804805
.. Note::
805806
It is recommended to always specify the database explicitly

src/neo4j/_async/home_db_cache.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [https://neo4j.com]
3+
#
4+
# This file is part of Neo4j.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# https://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
19+
from __future__ import annotations
20+
21+
import math
22+
import typing as t
23+
from time import monotonic
24+
25+
from .._async_compat.concurrency import AsyncCooperativeLock
26+
27+
28+
if t.TYPE_CHECKING:
29+
import typing_extensions as te
30+
31+
TKey: te.TypeAlias = t.Union[
32+
str,
33+
t.Tuple[t.Tuple[str, t.Hashable], ...],
34+
t.Tuple[None],
35+
]
36+
TVal: te.TypeAlias = t.Tuple[float, str]
37+
38+
39+
class AsyncHomeDbCache:
40+
_ttl: float
41+
_enabled: bool
42+
_max_size: int | None
43+
44+
def __init__(
45+
self,
46+
enabled: bool = True,
47+
ttl: float = float("inf"),
48+
max_size: int | None = None,
49+
) -> None:
50+
if math.isnan(ttl) or ttl <= 0:
51+
raise ValueError(f"home db cache ttl must be greater 0, got {ttl}")
52+
self._enabled = enabled
53+
self._ttl = ttl
54+
self._cache: dict[TKey, TVal] = {}
55+
self._lock = AsyncCooperativeLock()
56+
self._oldest_entry = monotonic()
57+
if max_size is not None and max_size <= 0:
58+
raise ValueError(
59+
f"home db cache max_size must be greater 0 or None, "
60+
f"got {max_size}"
61+
)
62+
self._max_size = max_size
63+
self._truncate_size = (
64+
min(max_size, int(0.01 * max_size * math.log(max_size)))
65+
if max_size is not None
66+
else None
67+
)
68+
69+
def compute_key(
70+
self,
71+
imp_user: str | None,
72+
auth: dict | None,
73+
) -> TKey:
74+
if not self._enabled:
75+
return (None,)
76+
if imp_user is not None:
77+
return imp_user
78+
if auth is not None:
79+
return _consolidate_auth_token(auth)
80+
return (None,)
81+
82+
def get(self, key: TKey) -> str | None:
83+
if not self._enabled:
84+
return None
85+
with self._lock:
86+
self._clean(monotonic())
87+
val = self._cache.get(key)
88+
if val is None:
89+
return None
90+
return val[1]
91+
92+
def set(self, key: TKey, value: str | None) -> None:
93+
if not self._enabled:
94+
return
95+
with self._lock:
96+
now = monotonic()
97+
self._clean(now)
98+
if value is None:
99+
self._cache.pop(key, None)
100+
else:
101+
self._cache[key] = (now, value)
102+
103+
def clear(self) -> None:
104+
if not self._enabled:
105+
return
106+
with self._lock:
107+
self._cache = {}
108+
self._oldest_entry = monotonic()
109+
110+
def _clean(self, now: float | None = None) -> None:
111+
now = monotonic() if now is None else now
112+
if now - self._oldest_entry > self._ttl:
113+
self._cache = {
114+
k: v
115+
for k, v in self._cache.items()
116+
if now - v[0] < self._ttl * 0.9
117+
}
118+
self._oldest_entry = min(
119+
(v[0] for v in self._cache.values()), default=now
120+
)
121+
if self._max_size and len(self._cache) > self._max_size:
122+
self._cache = dict(
123+
sorted(
124+
self._cache.items(),
125+
key=lambda item: item[1][0],
126+
reverse=True,
127+
)[: self._truncate_size]
128+
)
129+
130+
def __len__(self) -> int:
131+
return len(self._cache)
132+
133+
@property
134+
def enabled(self) -> bool:
135+
return self._enabled
136+
137+
138+
def _consolidate_auth_token(auth: dict) -> tuple | str:
139+
if auth.get("scheme") == "basic" and isinstance(
140+
auth.get("principal"), str
141+
):
142+
return auth["principal"]
143+
return _hashable_dict(auth)
144+
145+
146+
def _hashable_dict(d: dict) -> tuple:
147+
return tuple(
148+
(k, _hashable_dict(v) if isinstance(v, dict) else v)
149+
for k, v in sorted(d.items())
150+
)

src/neo4j/_async/io/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"""
2323

2424
__all__ = [
25-
"AcquireAuth",
25+
"AcquisitionAuth",
26+
"AcquisitionDatabase",
2627
"AsyncBolt",
2728
"AsyncBoltPool",
2829
"AsyncNeo4jPool",
@@ -43,7 +44,8 @@
4344
ConnectionErrorHandler,
4445
)
4546
from ._pool import (
46-
AcquireAuth,
47+
AcquisitionAuth,
48+
AcquisitionDatabase,
4749
AsyncBoltPool,
4850
AsyncNeo4jPool,
4951
)

0 commit comments

Comments
 (0)