Skip to content

Commit bed5142

Browse files
committed
Track generated sync code
1 parent 9aff556 commit bed5142

36 files changed

+8087
-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: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
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 async is in experimental phase. It might be removed or change "
44+
"it's API 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+
:return: :ref:`neo4j-driver-ref` or :ref:`bolt-driver-ref`
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 which are
197+
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, see :ref:`session-configuration-ref` for available key-word arguments.
225+
226+
:returns: new :class:`neo4j.AsyncSession` object
227+
"""
228+
raise NotImplementedError
229+
230+
def close(self):
231+
""" Shut down, closing any open connections in the pool.
232+
"""
233+
self._pool.close()
234+
235+
@experimental("The configuration may change in the future.")
236+
def verify_connectivity(self, **config):
237+
""" This verifies if the driver can connect to a remote server or a cluster
238+
by establishing a network connection with the remote and possibly exchanging
239+
a few data before closing the connection. It throws exception if fails to connect.
240+
241+
Use the exception to further understand the cause of the connectivity problem.
242+
243+
Note: Even if this method throws an exception, the driver still need to be closed via close() to free up all resources.
244+
"""
245+
raise NotImplementedError
246+
247+
@experimental("Feature support query, based on Bolt Protocol Version and Neo4j Server Version will change in the future.")
248+
def supports_multi_db(self):
249+
""" Check if the server or cluster supports multi-databases.
250+
251+
:return: Returns true if the server or cluster the driver connects to supports multi-databases, otherwise false.
252+
:rtype: bool
253+
"""
254+
with self.session() as session:
255+
session._connect(READ_ACCESS)
256+
return session._connection.supports_multiple_databases
257+
258+
259+
class BoltDriver(_Direct, Driver):
260+
""" A :class:`.BoltDriver` is created from a ``bolt`` URI and addresses
261+
a single database machine. This may be a standalone server or could be a
262+
specific member of a cluster.
263+
264+
Connections established by a :class:`.BoltDriver` are always made to the
265+
exact host and port detailed in the URI.
266+
"""
267+
268+
@classmethod
269+
def open(cls, target, *, auth=None, **config):
270+
"""
271+
:param target:
272+
:param auth:
273+
:param config: The values that can be specified are found in :class: `neo4j.PoolConfig` and :class: `neo4j.WorkspaceConfig`
274+
275+
:return:
276+
:rtype: :class: `neo4j.BoltDriver`
277+
"""
278+
from .io import BoltPool
279+
address = cls.parse_target(target)
280+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
281+
pool = BoltPool.open(address, auth=auth, pool_config=pool_config, workspace_config=default_workspace_config)
282+
return cls(pool, default_workspace_config)
283+
284+
def __init__(self, pool, default_workspace_config):
285+
_Direct.__init__(self, pool.address)
286+
Driver.__init__(self, pool)
287+
self._default_workspace_config = default_workspace_config
288+
289+
def session(self, **config):
290+
"""
291+
:param config: The values that can be specified are found in :class: `neo4j.AsyncSessionConfig`
292+
293+
:return:
294+
:rtype: :class: `neo4j.AsyncSession`
295+
"""
296+
from .work import Session
297+
session_config = SessionConfig(self._default_workspace_config, config)
298+
SessionConfig.consume(config) # Consume the config
299+
return Session(self._pool, session_config)
300+
301+
@experimental("The configuration may change in the future.")
302+
def verify_connectivity(self, **config):
303+
server_agent = None
304+
config["fetch_size"] = -1
305+
with self.session(**config) as session:
306+
result = session.run("RETURN 1 AS x")
307+
value = result.single().value()
308+
summary = result.consume()
309+
server_agent = summary.server.agent
310+
return server_agent
311+
312+
313+
class Neo4jDriver(_Routing, Driver):
314+
""" A :class:`.Neo4jDriver` is created from a ``neo4j`` URI. The
315+
routing behaviour works in tandem with Neo4j's `Causal Clustering
316+
<https://neo4j.com/docs/operations-manual/current/clustering/>`_
317+
feature by directing read and write behaviour to appropriate
318+
cluster members.
319+
"""
320+
321+
@classmethod
322+
def open(cls, *targets, auth=None, routing_context=None, **config):
323+
from .io import Neo4jPool
324+
addresses = cls.parse_targets(*targets)
325+
pool_config, default_workspace_config = Config.consume_chain(config, PoolConfig, WorkspaceConfig)
326+
pool = Neo4jPool.open(*addresses, auth=auth, routing_context=routing_context, pool_config=pool_config, workspace_config=default_workspace_config)
327+
return cls(pool, default_workspace_config)
328+
329+
def __init__(self, pool, default_workspace_config):
330+
_Routing.__init__(self, pool.get_default_database_initial_router_addresses())
331+
Driver.__init__(self, pool)
332+
self._default_workspace_config = default_workspace_config
333+
334+
def session(self, **config):
335+
from .work import Session
336+
session_config = SessionConfig(self._default_workspace_config, config)
337+
SessionConfig.consume(config) # Consume the config
338+
return Session(self._pool, session_config)
339+
340+
@experimental("The configuration may change in the future.")
341+
def verify_connectivity(self, **config):
342+
"""
343+
:raise ServiceUnavailable: raised if the server does not support routing or if routing support is broken.
344+
"""
345+
# TODO: Improve and update Stub Test Server to be able to test.
346+
return self._verify_routing_connectivity()
347+
348+
def _verify_routing_connectivity(self):
349+
from ..exceptions import (
350+
Neo4jError,
351+
ServiceUnavailable,
352+
SessionExpired,
353+
)
354+
355+
table = self._pool.get_routing_table_for_default_database()
356+
routing_info = {}
357+
for ix in list(table.routers):
358+
try:
359+
routing_info[ix] = self._pool.fetch_routing_info(
360+
address=table.routers[0],
361+
database=self._default_workspace_config.database,
362+
imp_user=self._default_workspace_config.impersonated_user,
363+
bookmarks=None,
364+
timeout=self._default_workspace_config
365+
.connection_acquisition_timeout
366+
)
367+
except (ServiceUnavailable, SessionExpired, Neo4jError):
368+
routing_info[ix] = None
369+
for key, val in routing_info.items():
370+
if val is not None:
371+
return routing_info
372+
raise ServiceUnavailable("Could not connect to any routing servers.")

0 commit comments

Comments
 (0)