Skip to content

Commit 9ba244a

Browse files
committed
Basic routing functionality
1 parent 1614f78 commit 9ba244a

39 files changed

+791
-301
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ docs/build
1313
dist
1414
*.egg-info
1515
build
16+
17+
*/run/*
18+
19+
neo4j-community-*
20+
neo4j-enterprise-*

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +0,0 @@
1-
[submodule "neokit"]
2-
path = neokit
3-
url = https://github.com/neo-technology/neokit.git

examples/test_examples.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,13 @@
3232
# (* "good reason" is defined as knowing what you are doing)
3333

3434

35-
auth_token = basic_auth("neo4j", "neo4j")
35+
auth_token = basic_auth("neotest", "neotest")
36+
37+
38+
# Deliberately shadow the built-in print function to
39+
# mute noise from example code.
40+
def print(*args, **kwargs):
41+
pass
3642

3743

3844
class FreshDatabaseTestCase(ServerTestCase):
@@ -48,7 +54,7 @@ class MinimalWorkingExampleTestCase(FreshDatabaseTestCase):
4854

4955
def test_minimal_working_example(self):
5056
# tag::minimal-example[]
51-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"))
57+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"))
5258
session = driver.session()
5359

5460
session.run("CREATE (a:Person {name:'Arthur', title:'King'})")
@@ -65,33 +71,33 @@ class ExamplesTestCase(FreshDatabaseTestCase):
6571

6672
def test_construct_driver(self):
6773
# tag::construct-driver[]
68-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"))
74+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"))
6975
# end::construct-driver[]
7076
return driver
7177

7278
def test_configuration(self):
7379
# tag::configuration[]
74-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), max_pool_size=10)
80+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"), max_pool_size=10)
7581
# end::configuration[]
7682
return driver
7783

7884
@skipUnless(SSL_AVAILABLE, "Bolt over TLS is not supported by this version of Python")
7985
def test_tls_require_encryption(self):
8086
# tag::tls-require-encryption[]
81-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True)
87+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"), encrypted=True)
8288
# end::tls-require-encryption[]
8389

8490
@skipUnless(SSL_AVAILABLE, "Bolt over TLS is not supported by this version of Python")
8591
def test_tls_trust_on_first_use(self):
8692
# tag::tls-trust-on-first-use[]
87-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True, trust=TRUST_ON_FIRST_USE)
93+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"), encrypted=True, trust=TRUST_ON_FIRST_USE)
8894
# end::tls-trust-on-first-use[]
8995
assert driver
9096

9197
@skip("testing verified certificates not yet supported ")
9298
def test_tls_signed(self):
9399
# tag::tls-signed[]
94-
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True, trust=TRUST_SIGNED_CERTIFICATES)
100+
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neotest", "neotest"), encrypted=True, trust=TRUST_SIGNED_CERTIFICATES)
95101
# end::tls-signed[]
96102
assert driver
97103

neo4j/util.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919
# limitations under the License.
2020

2121

22+
try:
23+
from collections.abc import MutableSet
24+
except ImportError:
25+
from collections import MutableSet, OrderedDict
26+
else:
27+
from collections import OrderedDict
2228
import logging
2329
from sys import stdout
2430

@@ -55,6 +61,13 @@ def __init__(self, logger_name):
5561
self.logger = logging.getLogger(self.logger_name)
5662
self.formatter = ColourFormatter("%(asctime)s %(message)s")
5763

64+
def __enter__(self):
65+
self.watch()
66+
return self
67+
68+
def __exit__(self, exc_type, exc_val, exc_tb):
69+
self.stop()
70+
5871
def watch(self, level=logging.INFO, out=stdout):
5972
self.stop()
6073
handler = logging.StreamHandler(out)
@@ -81,3 +94,58 @@ def watch(logger_name, level=logging.INFO, out=stdout):
8194
watcher = Watcher(logger_name)
8295
watcher.watch(level, out)
8396
return watcher
97+
98+
99+
class RoundRobinSet(MutableSet):
100+
101+
def __init__(self, elements=()):
102+
self._elements = OrderedDict.fromkeys(elements)
103+
self._current = None
104+
105+
def __repr__(self):
106+
return "{%s}" % ", ".join(map(repr, self._elements))
107+
108+
def __contains__(self, element):
109+
return element in self._elements
110+
111+
def __next__(self):
112+
current = None
113+
if self._elements:
114+
if self._current is None:
115+
self._current = 0
116+
else:
117+
self._current = (self._current + 1) % len(self._elements)
118+
current = list(self._elements.keys())[self._current]
119+
return current
120+
121+
def __iter__(self):
122+
return iter(self._elements)
123+
124+
def __len__(self):
125+
return len(self._elements)
126+
127+
def add(self, element):
128+
self._elements[element] = None
129+
130+
def clear(self):
131+
self._elements.clear()
132+
133+
def discard(self, element):
134+
try:
135+
del self._elements[element]
136+
except KeyError:
137+
pass
138+
139+
def remove(self, element):
140+
try:
141+
del self._elements[element]
142+
except KeyError:
143+
raise ValueError(element)
144+
145+
def update(self, elements=()):
146+
self._elements.update(OrderedDict.fromkeys(elements))
147+
148+
def replace(self, elements=()):
149+
e = self._elements
150+
e.clear()
151+
e.update(OrderedDict.fromkeys(elements))

neo4j/v1/bolt.py

Lines changed: 60 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from select import select
3838
from socket import create_connection, SHUT_RDWR, error as SocketError
3939
from struct import pack as struct_pack, unpack as struct_unpack, unpack_from as struct_unpack_from
40+
from threading import Lock
4041

4142
from .constants import DEFAULT_USER_AGENT, KNOWN_HOSTS, MAGIC_PREAMBLE, TRUST_DEFAULT, TRUST_ON_FIRST_USE
4243
from .compat import hex2
@@ -200,19 +201,23 @@ def on_ignored(self, metadata=None):
200201

201202

202203
class Connection(object):
203-
""" Server connection through which all protocol messages
204-
are sent and received. This class is designed for protocol
205-
version 1.
204+
""" Server connection for Bolt protocol v1.
205+
206+
A :class:`.Connection` should be constructed following a
207+
successful Bolt handshake and takes the socket over which
208+
the handshake was carried out.
206209
207210
.. note:: logs at INFO level
208211
"""
209212

210213
def __init__(self, sock, **config):
211-
self.defunct = False
214+
self.address = sock.getpeername()
212215
self.channel = ChunkChannel(sock)
213216
self.packer = Packer(self.channel)
214217
self.responses = deque()
218+
self.in_use = False
215219
self.closed = False
220+
self.defunct = False
216221

217222
# Determine the user agent and ensure it is a Unicode value
218223
user_agent = config.get("user_agent", DEFAULT_USER_AGENT)
@@ -245,13 +250,6 @@ def on_failure(metadata):
245250
def __del__(self):
246251
self.close()
247252

248-
@property
249-
def healthy(self):
250-
""" Return ``True`` if this connection is healthy, ``False`` if
251-
unhealthy and ``None`` if closed.
252-
"""
253-
return None if self.closed else not self.defunct
254-
255253
def append(self, signature, fields=(), response=None):
256254
""" Add a message to the outgoing queue.
257255
@@ -362,6 +360,57 @@ def close(self):
362360
self.closed = True
363361

364362

363+
class ConnectionPool(object):
364+
""" A collection of connections to one or more server addresses.
365+
"""
366+
367+
def __init__(self, connector):
368+
self.connector = connector
369+
self.connections = {}
370+
self.lock = Lock()
371+
372+
def acquire(self, address):
373+
""" Acquire a connection to a given address from the pool.
374+
This method is thread safe.
375+
"""
376+
with self.lock:
377+
try:
378+
connections = self.connections[address]
379+
except KeyError:
380+
connections = self.connections[address] = deque()
381+
for connection in list(connections):
382+
if connection.closed or connection.defunct:
383+
connections.remove(connection)
384+
continue
385+
if not connection.in_use:
386+
connection.in_use = True
387+
return connection
388+
connection = self.connector(address)
389+
connection.in_use = True
390+
connections.append(connection)
391+
return connection
392+
393+
def release(self, connection):
394+
""" Release a connection back into the pool.
395+
This method is thread safe.
396+
"""
397+
with self.lock:
398+
connection.in_use = False
399+
400+
def close(self):
401+
""" Close all connections and empty the pool.
402+
This method is thread safe.
403+
"""
404+
with self.lock:
405+
for _, connections in self.connections.items():
406+
for connection in connections:
407+
try:
408+
connection.close()
409+
except IOError:
410+
pass
411+
self.connections.clear()
412+
413+
365414
class CertificateStore(object):
366415

367416
def match_or_trust(self, host, der_encoded_certificate):

neo4j/v1/constants.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@
3434

3535
ENCRYPTION_OFF = 0
3636
ENCRYPTION_ON = 1
37-
ENCRYPTION_NON_LOCAL = 2
38-
ENCRYPTION_DEFAULT = ENCRYPTION_NON_LOCAL if SSL_AVAILABLE else ENCRYPTION_OFF
39-
40-
TRUST_ON_FIRST_USE = 0
41-
TRUST_SIGNED_CERTIFICATES = 1
42-
TRUST_DEFAULT = TRUST_ON_FIRST_USE
37+
ENCRYPTION_DEFAULT = ENCRYPTION_ON if SSL_AVAILABLE else ENCRYPTION_OFF
38+
39+
TRUST_ON_FIRST_USE = 0 # Deprecated
40+
TRUST_SIGNED_CERTIFICATES = 1 # Deprecated
41+
TRUST_ALL_CERTIFICATES = 2
42+
TRUST_CUSTOM_CA_SIGNED_CERTIFICATES = 3
43+
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES = 4
44+
TRUST_DEFAULT = TRUST_ALL_CERTIFICATES
45+
46+
READ_ACCESS = "READ"
47+
WRITE_ACCESS = "WRITE"
48+
ACCESS_DEFAULT = WRITE_ACCESS

neo4j/v1/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ def __init__(self, data):
4343
setattr(self, key, value)
4444

4545

46+
class TransactionError(Exception):
47+
""" Raised when an error occurs while using a transaction.
48+
"""
49+
50+
4651
class ResultError(Exception):
4752
""" Raised when an error occurs while consuming a result.
4853
"""
54+
55+
56+
class ServiceUnavailable(Exception):
57+
""" Raised when no database service is available.
58+
"""

0 commit comments

Comments
 (0)