Skip to content

Commit 10c1009

Browse files
authored
Version bumping for 5.0 (#627)
* Version bumping for 5.0 * drop support for Python 3.6 * add support for Python 3.10 * Redact staticmethods and classmethods from Config.keys() In Python 3.10 callable(x) for x being a staticmethod is True (it was False previously). So there is a mismatch. But instead of make class- and staticmethods being listed under keys() as well in Python 3.10 it seems right to not list them at all.
1 parent 6c785c7 commit 10c1009

14 files changed

+315
-33
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Neo4j Driver Change Log
22

3+
## Version 5.0
4+
5+
- Python 3.10 support added
6+
- Python 3.6 support has been dropped.
7+
8+
39
## Version 4.4
410

511
- Python 3.5 support has been dropped.

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ This repository contains the official Neo4j driver for Python.
66
Each driver release (from 4.0 upwards) is built specifically to work with a corresponding Neo4j release, i.e. that with the same `major.minor` version number.
77
These drivers will also be compatible with the previous Neo4j release, although new server features will not be available.
88

9+
+ Python 3.10 supported.
910
+ Python 3.9 supported.
1011
+ Python 3.8 supported.
1112
+ Python 3.7 supported.
12-
+ Python 3.6 supported.
1313

1414
Python 2.7 support has been dropped as of the Neo4j 4.0 release.
1515

TESTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Neo4j Driver Testing
22

33
To run driver tests, [Tox](https://tox.readthedocs.io) is required as well as at least one version of Python.
4-
The versions of Python supported by this driver are CPython 3.6, 3.7, 3.8, and 3.9.
4+
The versions of Python supported by this driver are CPython 3.7, 3.8, 3.9, and 3.10.
55

66

77
## Unit Tests & Stub Tests

docs/source/index.rst

+2-7
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,15 @@ The Official Neo4j Driver for Python.
66

77
Neo4j versions supported:
88

9+
* Neo4j 5.0
910
* Neo4j 4.4
10-
* Neo4j 4.3
11-
* Neo4j 3.5
1211

1312
Python versions supported:
1413

14+
* Python 3.10
1515
* Python 3.9
1616
* Python 3.8
1717
* Python 3.7
18-
* Python 3.6
19-
20-
.. note::
21-
22-
The `Python Driver 1.7`_ supports older versions of python, **Neo4j 4.1** will work in fallback mode with that driver.
2318

2419

2520
******

neo4j/_driver.py

+281
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# Copyright (c) "Neo4j"
2+
# Neo4j Sweden AB [http://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+
# http://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 .addressing import Address
20+
from .api import READ_ACCESS
21+
from .conf import (
22+
Config,
23+
PoolConfig,
24+
SessionConfig,
25+
WorkspaceConfig,
26+
)
27+
from .meta import experimental
28+
from .work.simple import Session
29+
30+
31+
class Direct:
32+
33+
default_host = "localhost"
34+
default_port = 7687
35+
36+
default_target = ":"
37+
38+
def __init__(self, address):
39+
self._address = address
40+
41+
@property
42+
def address(self):
43+
return self._address
44+
45+
@classmethod
46+
def parse_target(cls, target):
47+
""" Parse a target string to produce an address.
48+
"""
49+
if not target:
50+
target = cls.default_target
51+
address = Address.parse(target, default_host=cls.default_host,
52+
default_port=cls.default_port)
53+
return address
54+
55+
56+
class Routing:
57+
58+
default_host = "localhost"
59+
default_port = 7687
60+
61+
default_targets = ": :17601 :17687"
62+
63+
def __init__(self, initial_addresses):
64+
self._initial_addresses = initial_addresses
65+
66+
@property
67+
def initial_addresses(self):
68+
return self._initial_addresses
69+
70+
@classmethod
71+
def parse_targets(cls, *targets):
72+
""" Parse a sequence of target strings to produce an address
73+
list.
74+
"""
75+
targets = " ".join(targets)
76+
if not targets:
77+
targets = cls.default_targets
78+
addresses = Address.parse_list(targets, default_host=cls.default_host, default_port=cls.default_port)
79+
return addresses
80+
81+
82+
class Driver:
83+
""" Base class for all types of :class:`neo4j.Driver`, instances of which are
84+
used as the primary access point to Neo4j.
85+
"""
86+
87+
#: Connection pool
88+
_pool = None
89+
90+
def __init__(self, pool):
91+
assert pool is not None
92+
self._pool = pool
93+
94+
def __del__(self):
95+
self.close()
96+
97+
def __enter__(self):
98+
return self
99+
100+
def __exit__(self, exc_type, exc_value, traceback):
101+
self.close()
102+
103+
@property
104+
def encrypted(self):
105+
return bool(self._pool.pool_config.encrypted)
106+
107+
def session(self, **config):
108+
"""Create a session, see :ref:`session-construction-ref`
109+
110+
:param config: session configuration key-word arguments, see :ref:`session-configuration-ref` for available key-word arguments.
111+
112+
:returns: new :class:`neo4j.Session` object
113+
"""
114+
raise NotImplementedError
115+
116+
@experimental("The pipeline API is experimental and may be removed or changed in a future release")
117+
def pipeline(self, **config):
118+
""" Create a pipeline.
119+
"""
120+
raise NotImplementedError
121+
122+
def close(self):
123+
""" Shut down, closing any open connections in the pool.
124+
"""
125+
self._pool.close()
126+
127+
@experimental("The configuration may change in the future.")
128+
def verify_connectivity(self, **config):
129+
""" This verifies if the driver can connect to a remote server or a cluster
130+
by establishing a network connection with the remote and possibly exchanging
131+
a few data before closing the connection. It throws exception if fails to connect.
132+
133+
Use the exception to further understand the cause of the connectivity problem.
134+
135+
Note: Even if this method throws an exception, the driver still need to be closed via close() to free up all resources.
136+
"""
137+
raise NotImplementedError
138+
139+
@experimental("Feature support query, based on Bolt Protocol Version and Neo4j Server Version will change in the future.")
140+
def supports_multi_db(self):
141+
""" Check if the server or cluster supports multi-databases.
142+
143+
:return: Returns true if the server or cluster the driver connects to supports multi-databases, otherwise false.
144+
:rtype: bool
145+
"""
146+
with self.session() as session:
147+
session._connect(READ_ACCESS)
148+
return session._connection.supports_multiple_databases
149+
150+
151+
class BoltDriver(Direct, Driver):
152+
""" A :class:`.BoltDriver` is created from a ``bolt`` URI and addresses
153+
a single database machine. This may be a standalone server or could be a
154+
specific member of a cluster.
155+
156+
Connections established by a :class:`.BoltDriver` are always made to the
157+
exact host and port detailed in the URI.
158+
"""
159+
160+
@classmethod
161+
def open(cls, target, *, auth=None, **config):
162+
"""
163+
:param target:
164+
:param auth:
165+
:param config: The values that can be specified are found in :class: `neo4j.PoolConfig` and :class: `neo4j.WorkspaceConfig`
166+
167+
:return:
168+
:rtype: :class: `neo4j.BoltDriver`
169+
"""
170+
from neo4j.io import BoltPool
171+
address = cls.parse_target(target)
172+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
173+
pool = BoltPool.open(address, auth=auth, pool_config=pool_config, workspace_config=default_workspace_config)
174+
return cls(pool, default_workspace_config)
175+
176+
def __init__(self, pool, default_workspace_config):
177+
Direct.__init__(self, pool.address)
178+
Driver.__init__(self, pool)
179+
self._default_workspace_config = default_workspace_config
180+
181+
def session(self, **config):
182+
"""
183+
:param config: The values that can be specified are found in :class: `neo4j.SessionConfig`
184+
185+
:return:
186+
:rtype: :class: `neo4j.Session`
187+
"""
188+
from neo4j.work.simple import Session
189+
session_config = SessionConfig(self._default_workspace_config, config)
190+
SessionConfig.consume(config) # Consume the config
191+
return Session(self._pool, session_config)
192+
193+
def pipeline(self, **config):
194+
from neo4j.work.pipelining import (
195+
Pipeline,
196+
PipelineConfig,
197+
)
198+
pipeline_config = PipelineConfig(self._default_workspace_config, config)
199+
PipelineConfig.consume(config) # Consume the config
200+
return Pipeline(self._pool, pipeline_config)
201+
202+
@experimental("The configuration may change in the future.")
203+
def verify_connectivity(self, **config):
204+
server_agent = None
205+
config["fetch_size"] = -1
206+
with self.session(**config) as session:
207+
result = session.run("RETURN 1 AS x")
208+
value = result.single().value()
209+
summary = result.consume()
210+
server_agent = summary.server.agent
211+
return server_agent
212+
213+
214+
class Neo4jDriver(Routing, Driver):
215+
""" A :class:`.Neo4jDriver` is created from a ``neo4j`` URI. The
216+
routing behaviour works in tandem with Neo4j's `Causal Clustering
217+
<https://neo4j.com/docs/operations-manual/current/clustering/>`_
218+
feature by directing read and write behaviour to appropriate
219+
cluster members.
220+
"""
221+
222+
@classmethod
223+
def open(cls, *targets, auth=None, routing_context=None, **config):
224+
from neo4j.io import Neo4jPool
225+
addresses = cls.parse_targets(*targets)
226+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
227+
pool = Neo4jPool.open(*addresses, auth=auth, routing_context=routing_context, pool_config=pool_config, workspace_config=default_workspace_config)
228+
return cls(pool, default_workspace_config)
229+
230+
def __init__(self, pool, default_workspace_config):
231+
Routing.__init__(self, pool.get_default_database_initial_router_addresses())
232+
Driver.__init__(self, pool)
233+
self._default_workspace_config = default_workspace_config
234+
235+
def session(self, **config):
236+
session_config = SessionConfig(self._default_workspace_config, config)
237+
SessionConfig.consume(config) # Consume the config
238+
return Session(self._pool, session_config)
239+
240+
def pipeline(self, **config):
241+
from neo4j.work.pipelining import (
242+
Pipeline,
243+
PipelineConfig,
244+
)
245+
pipeline_config = PipelineConfig(self._default_workspace_config, config)
246+
PipelineConfig.consume(config) # Consume the config
247+
return Pipeline(self._pool, pipeline_config)
248+
249+
@experimental("The configuration may change in the future.")
250+
def verify_connectivity(self, **config):
251+
"""
252+
:raise ServiceUnavailable: raised if the server does not support routing or if routing support is broken.
253+
"""
254+
# TODO: Improve and update Stub Test Server to be able to test.
255+
return self._verify_routing_connectivity()
256+
257+
def _verify_routing_connectivity(self):
258+
from neo4j.exceptions import (
259+
Neo4jError,
260+
ServiceUnavailable,
261+
SessionExpired,
262+
)
263+
264+
table = self._pool.get_routing_table_for_default_database()
265+
routing_info = {}
266+
for ix in list(table.routers):
267+
try:
268+
routing_info[ix] = self._pool.fetch_routing_info(
269+
address=table.routers[0],
270+
database=self._default_workspace_config.database,
271+
imp_user=self._default_workspace_config.impersonated_user,
272+
bookmarks=None,
273+
timeout=self._default_workspace_config
274+
.connection_acquisition_timeout
275+
)
276+
except (ServiceUnavailable, SessionExpired, Neo4jError):
277+
routing_info[ix] = None
278+
for key, val in routing_info.items():
279+
if val is not None:
280+
return routing_info
281+
raise ServiceUnavailable("Could not connect to any routing servers.")

neo4j/conf.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def __new__(mcs, name, bases, attributes):
7171
for k, v in attributes.items():
7272
if isinstance(v, DeprecatedAlias):
7373
deprecated_aliases[k] = v.new
74-
elif not k.startswith("_") and not callable(v):
74+
elif not (k.startswith("_")
75+
or callable(v)
76+
or isinstance(v, (staticmethod, classmethod))):
7577
fields.append(k)
7678

7779
def keys(_):
@@ -232,18 +234,19 @@ def get_ssl_context(self):
232234
# TLS 1.1 - Released in 2006, published as RFC 4346. (Disabled)
233235
# TLS 1.2 - Released in 2008, published as RFC 5246.
234236

235-
# https://docs.python.org/3.6/library/ssl.html#ssl.PROTOCOL_TLS_CLIENT
237+
# https://docs.python.org/3.7/library/ssl.html#ssl.PROTOCOL_TLS_CLIENT
236238
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
237239

238240
# For recommended security options see
239-
# https://docs.python.org/3.6/library/ssl.html#protocol-versions
241+
# https://docs.python.org/3.7/library/ssl.html#protocol-versions
240242
ssl_context.options |= ssl.OP_NO_TLSv1 # Python 3.2
241243
ssl_context.options |= ssl.OP_NO_TLSv1_1 # Python 3.4
242244

243245

244246
if self.trust == TRUST_ALL_CERTIFICATES:
245247
ssl_context.check_hostname = False
246-
ssl_context.verify_mode = ssl.CERT_NONE # https://docs.python.org/3.5/library/ssl.html#ssl.CERT_NONE
248+
# https://docs.python.org/3.7/library/ssl.html#ssl.CERT_NONE
249+
ssl_context.verify_mode = ssl.CERT_NONE
247250

248251
# Must be load_default_certs, not set_default_verify_paths to work
249252
# on Windows with system CAs.

neo4j/meta.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
# Can be automatically overridden in builds
2626
package = "neo4j"
27-
version = "4.4.dev0"
27+
version = "5.0.dev0"
2828

2929

3030
def get_user_agent():

0 commit comments

Comments
 (0)