Skip to content

Commit 6243400

Browse files
authored
Merge branch '5.0' into improve-logging-helper
2 parents 6e113b7 + 3d7826f commit 6243400

38 files changed

+546
-679
lines changed

neo4j/_async/io/_pool.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -241,31 +241,24 @@ async def deactivate(self, address):
241241
connections = self.connections[address]
242242
except KeyError: # already removed from the connection pool
243243
return
244-
for conn in list(connections):
245-
if not conn.in_use:
246-
connections.remove(conn)
247-
try:
248-
await conn.close()
249-
except OSError:
250-
pass
251-
if not connections:
252-
await self.remove(address)
244+
closable_connections = [
245+
conn for conn in connections if not conn.in_use
246+
]
247+
# First remove all connections in question, then try to close them.
248+
# If closing of a connection fails, we will end up in this method
249+
# again.
250+
for conn in closable_connections:
251+
connections.remove(conn)
252+
for conn in closable_connections:
253+
await conn.close()
254+
if not self.connections[address]:
255+
del self.connections[address]
253256

254257
def on_write_failure(self, address):
255258
raise WriteServiceUnavailable(
256259
"No write service available for pool {}".format(self)
257260
)
258261

259-
async def remove(self, address):
260-
""" Remove an address from the connection pool, if present, closing
261-
all connections to that address.
262-
"""
263-
async with self.lock:
264-
for connection in self.connections.pop(address, ()):
265-
try:
266-
await connection.close()
267-
except OSError:
268-
pass
269262

270263
async def close(self):
271264
""" Close all connections and empty the pool.
@@ -274,7 +267,8 @@ async def close(self):
274267
try:
275268
async with self.lock:
276269
for address in list(self.connections):
277-
await self.remove(address)
270+
for connection in self.connections.pop(address, ()):
271+
await connection.close()
278272
except TypeError:
279273
pass
280274

neo4j/_sync/io/_pool.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -241,31 +241,24 @@ def deactivate(self, address):
241241
connections = self.connections[address]
242242
except KeyError: # already removed from the connection pool
243243
return
244-
for conn in list(connections):
245-
if not conn.in_use:
246-
connections.remove(conn)
247-
try:
248-
conn.close()
249-
except OSError:
250-
pass
251-
if not connections:
252-
self.remove(address)
244+
closable_connections = [
245+
conn for conn in connections if not conn.in_use
246+
]
247+
# First remove all connections in question, then try to close them.
248+
# If closing of a connection fails, we will end up in this method
249+
# again.
250+
for conn in closable_connections:
251+
connections.remove(conn)
252+
for conn in closable_connections:
253+
conn.close()
254+
if not self.connections[address]:
255+
del self.connections[address]
253256

254257
def on_write_failure(self, address):
255258
raise WriteServiceUnavailable(
256259
"No write service available for pool {}".format(self)
257260
)
258261

259-
def remove(self, address):
260-
""" Remove an address from the connection pool, if present, closing
261-
all connections to that address.
262-
"""
263-
with self.lock:
264-
for connection in self.connections.pop(address, ()):
265-
try:
266-
connection.close()
267-
except OSError:
268-
pass
269262

270263
def close(self):
271264
""" Close all connections and empty the pool.
@@ -274,7 +267,8 @@ def close(self):
274267
try:
275268
with self.lock:
276269
for address in list(self.connections):
277-
self.remove(address)
270+
for connection in self.connections.pop(address, ()):
271+
connection.close()
278272
except TypeError:
279273
pass
280274

testkit/Dockerfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,16 @@ ENV PYENV_ROOT /.pyenv
4242
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
4343

4444
# Set minimum supported Python version
45-
RUN pyenv install 3.7.12
45+
RUN pyenv install 3.7:latest
46+
RUN pyenv install 3.8:latest
47+
RUN pyenv install 3.9:latest
48+
RUN pyenv install 3.10:latest
4649
RUN pyenv rehash
47-
RUN pyenv global 3.7.12
50+
RUN pyenv global $(pyenv versions --bare --skip-aliases)
4851

4952
# Install Latest pip for each environment
5053
# https://pip.pypa.io/en/stable/news/
5154
RUN python -m pip install --upgrade pip
5255

5356
# Install Python Testing Tools
54-
RUN python -m pip install coverage tox
57+
RUN python -m pip install coverage tox tox-factor

testkit/integration.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,13 @@
1818
# limitations under the License.
1919

2020

21+
import subprocess
22+
23+
24+
def run(args):
25+
subprocess.run(
26+
args, universal_newlines=True, stderr=subprocess.STDOUT, check=True)
27+
28+
2129
if __name__ == "__main__":
22-
pass
30+
run(["python", "-m", "tox", "-f", "integration"])

testkit/unittests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ def run(args):
2727

2828

2929
if __name__ == "__main__":
30-
run(["python", "-m", "tox", "-c", "tox-unit.ini"])
30+
run(["python", "-m", "tox", "-f", "unit"])

tests/conftest.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
import asyncio
20+
from functools import wraps
21+
from os import environ
22+
import warnings
23+
24+
import pytest
25+
import pytest_asyncio
26+
27+
from neo4j import (
28+
AsyncGraphDatabase,
29+
ExperimentalWarning,
30+
GraphDatabase,
31+
)
32+
from neo4j._exceptions import BoltHandshakeError
33+
from neo4j._sync.io import Bolt
34+
from neo4j.exceptions import ServiceUnavailable
35+
36+
from . import env
37+
38+
39+
# from neo4j.debug import watch
40+
#
41+
# watch("neo4j")
42+
43+
44+
@pytest.fixture(scope="session")
45+
def uri():
46+
return env.NEO4J_SERVER_URI
47+
48+
49+
@pytest.fixture(scope="session")
50+
def bolt_uri(uri):
51+
if env.NEO4J_SCHEME != "bolt":
52+
pytest.skip("Test requires bolt scheme")
53+
return uri
54+
55+
56+
@pytest.fixture(scope="session")
57+
def _forced_bolt_uri():
58+
return f"bolt://{env.NEO4J_HOST}:{env.NEO4J_PORT}"
59+
60+
61+
@pytest.fixture(scope="session")
62+
def neo4j_uri():
63+
if env.NEO4J_SCHEME != "neo4j":
64+
pytest.skip("Test requires neo4j scheme")
65+
return uri
66+
67+
68+
@pytest.fixture(scope="session")
69+
def _forced_neo4j_uri():
70+
return f"neo4j://{env.NEO4J_HOST}:{env.NEO4J_PORT}"
71+
72+
73+
@pytest.fixture(scope="session")
74+
def auth():
75+
return env.NEO4J_USER, env.NEO4J_PASS
76+
77+
78+
@pytest.fixture
79+
def driver(uri, auth):
80+
with GraphDatabase.driver(uri, auth=auth) as driver:
81+
yield driver
82+
83+
84+
@pytest.fixture
85+
def bolt_driver(bolt_uri, auth):
86+
with GraphDatabase.driver(bolt_uri, auth=auth) as driver:
87+
yield driver
88+
89+
90+
@pytest.fixture
91+
def neo4j_driver(neo4j_uri, auth):
92+
with GraphDatabase.driver(neo4j_uri, auth=auth) as driver:
93+
yield driver
94+
95+
96+
@wraps(AsyncGraphDatabase.driver)
97+
def get_async_driver_no_warning(*args, **kwargs):
98+
# with warnings.catch_warnings():
99+
# warnings.filterwarnings("ignore", "neo4j async", ExperimentalWarning)
100+
with pytest.warns(ExperimentalWarning, match="neo4j async"):
101+
return AsyncGraphDatabase.driver(*args, **kwargs)
102+
103+
104+
@pytest_asyncio.fixture
105+
async def async_driver(uri, auth):
106+
async with get_async_driver_no_warning(uri, auth=auth) as driver:
107+
yield driver
108+
109+
110+
@pytest_asyncio.fixture
111+
async def async_bolt_driver(bolt_uri, auth):
112+
async with get_async_driver_no_warning(bolt_uri, auth=auth) as driver:
113+
yield driver
114+
115+
116+
@pytest_asyncio.fixture
117+
async def async_neo4j_driver(neo4j_uri, auth):
118+
async with get_async_driver_no_warning(neo4j_uri, auth=auth) as driver:
119+
yield driver
120+
121+
122+
@pytest.fixture
123+
def _forced_bolt_driver(_forced_bolt_uri):
124+
with GraphDatabase.driver(_forced_bolt_uri, auth=auth) as driver:
125+
yield driver
126+
127+
128+
@pytest.fixture
129+
def _forced_neo4j_driver(_forced_neo4j_uri):
130+
with GraphDatabase.driver(_forced_neo4j_uri, auth=auth) as driver:
131+
yield driver
132+
133+
134+
@pytest.fixture(scope="session")
135+
def server_info(_forced_bolt_driver):
136+
return _forced_bolt_driver.get_server_info()
137+
138+
139+
@pytest.fixture(scope="session")
140+
def bolt_protocol_version(server_info):
141+
return server_info.protocol_version
142+
143+
144+
def mark_requires_min_bolt_version(version="3.5"):
145+
return pytest.mark.skipif(
146+
env.NEO4J_VERSION < version,
147+
reason=f"requires server version '{version}' or higher, "
148+
f"found '{env.NEO4J_VERSION}'"
149+
)
150+
151+
152+
def mark_requires_edition(edition):
153+
return pytest.mark.skipif(
154+
env.NEO4J_EDITION != edition,
155+
reason=f"requires server edition '{edition}', "
156+
f"found '{env.NEO4J_EDITION}'"
157+
)
158+
159+
160+
@pytest.fixture
161+
def session(driver):
162+
with driver.session() as session:
163+
yield session
164+
165+
166+
@pytest.fixture
167+
def bolt_session(bolt_driver):
168+
with bolt_driver.session() as session:
169+
yield session
170+
171+
172+
@pytest.fixture
173+
def neo4j_session(neo4j_driver):
174+
with neo4j_driver.session() as session:
175+
yield session
176+
177+
178+
# async support for pytest-benchmark
179+
# https://github.com/ionelmc/pytest-benchmark/issues/66
180+
@pytest_asyncio.fixture
181+
async def aio_benchmark(benchmark, event_loop):
182+
def _wrapper(func, *args, **kwargs):
183+
if asyncio.iscoroutinefunction(func):
184+
@benchmark
185+
def _():
186+
return event_loop.run_until_complete(func(*args, **kwargs))
187+
else:
188+
benchmark(func, *args, **kwargs)
189+
190+
return _wrapper

0 commit comments

Comments
 (0)