Skip to content

Commit 24370b8

Browse files
committed
Track generated sync code
1 parent d642f4e commit 24370b8

36 files changed

+8123
-0
lines changed

neo4j/_sync/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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.

neo4j/_sync/driver.py

Lines changed: 380 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,380 @@
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+
import asyncio
20+
21+
from .._async_compat.util import Util
22+
from ..addressing import Address
23+
from ..api import (
24+
READ_ACCESS,
25+
TRUST_ALL_CERTIFICATES,
26+
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
27+
)
28+
from ..conf import (
29+
Config,
30+
PoolConfig,
31+
SessionConfig,
32+
WorkspaceConfig,
33+
)
34+
from ..meta import experimental
35+
36+
37+
class GraphDatabase:
38+
"""Accessor for :class:`neo4j.Driver` construction.
39+
"""
40+
41+
@classmethod
42+
@Util.experimental_async(
43+
"neo4j is in experimental phase. It might be removed or changed "
44+
"at any time (including patch releases)."
45+
)
46+
def driver(cls, uri, *, auth=None, **config):
47+
"""Create a driver.
48+
49+
:param uri: the connection URI for the driver, see :ref:`uri-ref` for available URIs.
50+
:param auth: the authentication details, see :ref:`auth-ref` for available authentication details.
51+
:param config: driver configuration key-word arguments, see :ref:`driver-configuration-ref` for available key-word arguments.
52+
53+
:rtype: Neo4jDriver or BoltDriver
54+
"""
55+
56+
from ..api import (
57+
DRIVER_BOLT,
58+
DRIVER_NEO4j,
59+
parse_neo4j_uri,
60+
parse_routing_context,
61+
SECURITY_TYPE_NOT_SECURE,
62+
SECURITY_TYPE_SECURE,
63+
SECURITY_TYPE_SELF_SIGNED_CERTIFICATE,
64+
URI_SCHEME_BOLT,
65+
URI_SCHEME_BOLT_SECURE,
66+
URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE,
67+
URI_SCHEME_NEO4J,
68+
URI_SCHEME_NEO4J_SECURE,
69+
URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE,
70+
)
71+
72+
driver_type, security_type, parsed = parse_neo4j_uri(uri)
73+
74+
if "trust" in config.keys():
75+
if config.get("trust") not in [TRUST_ALL_CERTIFICATES, TRUST_SYSTEM_CA_SIGNED_CERTIFICATES]:
76+
from neo4j.exceptions import ConfigurationError
77+
raise ConfigurationError("The config setting `trust` values are {!r}".format(
78+
[
79+
TRUST_ALL_CERTIFICATES,
80+
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES,
81+
]
82+
))
83+
84+
if security_type in [SECURITY_TYPE_SELF_SIGNED_CERTIFICATE, SECURITY_TYPE_SECURE] and ("encrypted" in config.keys() or "trust" in config.keys()):
85+
from neo4j.exceptions import ConfigurationError
86+
raise ConfigurationError("The config settings 'encrypted' and 'trust' can only be used with the URI schemes {!r}. Use the other URI schemes {!r} for setting encryption settings.".format(
87+
[
88+
URI_SCHEME_BOLT,
89+
URI_SCHEME_NEO4J,
90+
],
91+
[
92+
URI_SCHEME_BOLT_SELF_SIGNED_CERTIFICATE,
93+
URI_SCHEME_BOLT_SECURE,
94+
URI_SCHEME_NEO4J_SELF_SIGNED_CERTIFICATE,
95+
URI_SCHEME_NEO4J_SECURE,
96+
]
97+
))
98+
99+
if security_type == SECURITY_TYPE_SECURE:
100+
config["encrypted"] = True
101+
elif security_type == SECURITY_TYPE_SELF_SIGNED_CERTIFICATE:
102+
config["encrypted"] = True
103+
config["trust"] = TRUST_ALL_CERTIFICATES
104+
105+
if driver_type == DRIVER_BOLT:
106+
return cls.bolt_driver(parsed.netloc, auth=auth, **config)
107+
elif driver_type == DRIVER_NEO4j:
108+
routing_context = parse_routing_context(parsed.query)
109+
return cls.neo4j_driver(parsed.netloc, auth=auth, routing_context=routing_context, **config)
110+
111+
@classmethod
112+
def bolt_driver(cls, target, *, auth=None, **config):
113+
""" Create a driver for direct Bolt server access that uses
114+
socket I/O and thread-based concurrency.
115+
"""
116+
from .._exceptions import (
117+
BoltHandshakeError,
118+
BoltSecurityError,
119+
)
120+
121+
try:
122+
return BoltDriver.open(target, auth=auth, **config)
123+
except (BoltHandshakeError, BoltSecurityError) as error:
124+
from neo4j.exceptions import ServiceUnavailable
125+
raise ServiceUnavailable(str(error)) from error
126+
127+
@classmethod
128+
def neo4j_driver(cls, *targets, auth=None, routing_context=None, **config):
129+
""" Create a driver for routing-capable Neo4j service access
130+
that uses socket I/O and thread-based concurrency.
131+
"""
132+
from neo4j._exceptions import (
133+
BoltHandshakeError,
134+
BoltSecurityError,
135+
)
136+
137+
try:
138+
return Neo4jDriver.open(*targets, auth=auth, routing_context=routing_context, **config)
139+
except (BoltHandshakeError, BoltSecurityError) as error:
140+
from neo4j.exceptions import ServiceUnavailable
141+
raise ServiceUnavailable(str(error)) from error
142+
143+
144+
class _Direct:
145+
146+
default_host = "localhost"
147+
default_port = 7687
148+
149+
default_target = ":"
150+
151+
def __init__(self, address):
152+
self._address = address
153+
154+
@property
155+
def address(self):
156+
return self._address
157+
158+
@classmethod
159+
def parse_target(cls, target):
160+
""" Parse a target string to produce an address.
161+
"""
162+
if not target:
163+
target = cls.default_target
164+
address = Address.parse(target, default_host=cls.default_host,
165+
default_port=cls.default_port)
166+
return address
167+
168+
169+
class _Routing:
170+
171+
default_host = "localhost"
172+
default_port = 7687
173+
174+
default_targets = ": :17601 :17687"
175+
176+
def __init__(self, initial_addresses):
177+
self._initial_addresses = initial_addresses
178+
179+
@property
180+
def initial_addresses(self):
181+
return self._initial_addresses
182+
183+
@classmethod
184+
def parse_targets(cls, *targets):
185+
""" Parse a sequence of target strings to produce an address
186+
list.
187+
"""
188+
targets = " ".join(targets)
189+
if not targets:
190+
targets = cls.default_targets
191+
addresses = Address.parse_list(targets, default_host=cls.default_host, default_port=cls.default_port)
192+
return addresses
193+
194+
195+
class Driver:
196+
""" Base class for all types of :class:`neo4j.Driver`, instances of
197+
which are used as the primary access point to Neo4j.
198+
"""
199+
200+
#: Connection pool
201+
_pool = None
202+
203+
def __init__(self, pool):
204+
assert pool is not None
205+
self._pool = pool
206+
207+
def __enter__(self):
208+
return self
209+
210+
def __exit__(self, exc_type, exc_value, traceback):
211+
self.close()
212+
213+
def __del__(self):
214+
if not asyncio.iscoroutinefunction(self.close):
215+
self.close()
216+
217+
@property
218+
def encrypted(self):
219+
return bool(self._pool.pool_config.encrypted)
220+
221+
def session(self, **config):
222+
"""Create a session, see :ref:`session-construction-ref`
223+
224+
:param config: session configuration key-word arguments,
225+
see :ref:`session-configuration-ref` for available key-word
226+
arguments.
227+
228+
:returns: new :class:`neo4j.Session` object
229+
"""
230+
raise NotImplementedError
231+
232+
def close(self):
233+
""" Shut down, closing any open connections in the pool.
234+
"""
235+
self._pool.close()
236+
237+
@experimental("The configuration may change in the future.")
238+
def verify_connectivity(self, **config):
239+
""" This verifies if the driver can connect to a remote server or a cluster
240+
by establishing a network connection with the remote and possibly exchanging
241+
a few data before closing the connection. It throws exception if fails to connect.
242+
243+
Use the exception to further understand the cause of the connectivity problem.
244+
245+
Note: Even if this method throws an exception, the driver still need to be closed via close() to free up all resources.
246+
"""
247+
raise NotImplementedError
248+
249+
@experimental("Feature support query, based on Bolt Protocol Version and Neo4j Server Version will change in the future.")
250+
def supports_multi_db(self):
251+
""" Check if the server or cluster supports multi-databases.
252+
253+
:return: Returns true if the server or cluster the driver connects to supports multi-databases, otherwise false.
254+
:rtype: bool
255+
"""
256+
with self.session() as session:
257+
session._connect(READ_ACCESS)
258+
return session._connection.supports_multiple_databases
259+
260+
261+
class BoltDriver(_Direct, Driver):
262+
""":class:`.BoltDriver` is instantiated for ``bolt`` URIs and
263+
addresses a single database machine. This may be a standalone server or
264+
could be a specific member of a cluster.
265+
266+
Connections established by a :class:`.BoltDriver` are always made to
267+
the exact host and port detailed in the URI.
268+
269+
This class is not supposed to be instantiated externally. Use
270+
:meth:`GraphDatabase.driver` instead.
271+
"""
272+
273+
@classmethod
274+
def open(cls, target, *, auth=None, **config):
275+
"""
276+
:param target:
277+
:param auth:
278+
:param config: The values that can be specified are found in :class: `neo4j.PoolConfig` and :class: `neo4j.WorkspaceConfig`
279+
280+
:return:
281+
:rtype: :class: `neo4j.BoltDriver`
282+
"""
283+
from .io import BoltPool
284+
address = cls.parse_target(target)
285+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
286+
pool = BoltPool.open(address, auth=auth, pool_config=pool_config, workspace_config=default_workspace_config)
287+
return cls(pool, default_workspace_config)
288+
289+
def __init__(self, pool, default_workspace_config):
290+
_Direct.__init__(self, pool.address)
291+
Driver.__init__(self, pool)
292+
self._default_workspace_config = default_workspace_config
293+
294+
def session(self, **config):
295+
"""
296+
:param config: The values that can be specified are found in :class: `neo4j.SessionConfig`
297+
298+
:return:
299+
:rtype: :class: `neo4j.Session`
300+
"""
301+
from .work import Session
302+
session_config = SessionConfig(self._default_workspace_config, config)
303+
SessionConfig.consume(config) # Consume the config
304+
return Session(self._pool, session_config)
305+
306+
@experimental("The configuration may change in the future.")
307+
def verify_connectivity(self, **config):
308+
server_agent = None
309+
config["fetch_size"] = -1
310+
with self.session(**config) as session:
311+
result = session.run("RETURN 1 AS x")
312+
value = result.single().value()
313+
summary = result.consume()
314+
server_agent = summary.server.agent
315+
return server_agent
316+
317+
318+
class Neo4jDriver(_Routing, Driver):
319+
""":class:`.Neo4jDriver` is instantiated for ``neo4j`` URIs. The
320+
routing behaviour works in tandem with Neo4j's `Causal Clustering
321+
<https://neo4j.com/docs/operations-manual/current/clustering/>`_
322+
feature by directing read and write behaviour to appropriate
323+
cluster members.
324+
325+
This class is not supposed to be instantiated externally. Use
326+
:meth:`GraphDatabase.driver` instead.
327+
"""
328+
329+
@classmethod
330+
def open(cls, *targets, auth=None, routing_context=None, **config):
331+
from .io import Neo4jPool
332+
addresses = cls.parse_targets(*targets)
333+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
334+
pool = Neo4jPool.open(*addresses, auth=auth, routing_context=routing_context, pool_config=pool_config, workspace_config=default_workspace_config)
335+
return cls(pool, default_workspace_config)
336+
337+
def __init__(self, pool, default_workspace_config):
338+
_Routing.__init__(self, pool.get_default_database_initial_router_addresses())
339+
Driver.__init__(self, pool)
340+
self._default_workspace_config = default_workspace_config
341+
342+
def session(self, **config):
343+
from .work import Session
344+
session_config = SessionConfig(self._default_workspace_config, config)
345+
SessionConfig.consume(config) # Consume the config
346+
return Session(self._pool, session_config)
347+
348+
@experimental("The configuration may change in the future.")
349+
def verify_connectivity(self, **config):
350+
"""
351+
:raise ServiceUnavailable: raised if the server does not support routing or if routing support is broken.
352+
"""
353+
# TODO: Improve and update Stub Test Server to be able to test.
354+
return self._verify_routing_connectivity()
355+
356+
def _verify_routing_connectivity(self):
357+
from ..exceptions import (
358+
Neo4jError,
359+
ServiceUnavailable,
360+
SessionExpired,
361+
)
362+
363+
table = self._pool.get_routing_table_for_default_database()
364+
routing_info = {}
365+
for ix in list(table.routers):
366+
try:
367+
routing_info[ix] = self._pool.fetch_routing_info(
368+
address=table.routers[0],
369+
database=self._default_workspace_config.database,
370+
imp_user=self._default_workspace_config.impersonated_user,
371+
bookmarks=None,
372+
timeout=self._default_workspace_config
373+
.connection_acquisition_timeout
374+
)
375+
except (ServiceUnavailable, SessionExpired, Neo4jError):
376+
routing_info[ix] = None
377+
for key, val in routing_info.items():
378+
if val is not None:
379+
return routing_info
380+
raise ServiceUnavailable("Could not connect to any routing servers.")

0 commit comments

Comments
 (0)