23
23
from logging import getLogger
24
24
from time import monotonic
25
25
26
- from ..._async_compat .network import AsyncBoltSocket
27
26
from ..._async_compat .util import AsyncUtil
28
27
from ..._auth_management import to_auth_dict
29
28
from ..._codec .hydration import (
51
50
SessionExpired ,
52
51
)
53
52
from ..config import AsyncPoolConfig
53
+ from ._bolt_socket import AsyncBoltSocket
54
54
from ._common import (
55
55
AsyncInbox ,
56
56
AsyncOutbox ,
59
59
60
60
61
61
if t .TYPE_CHECKING :
62
+ import typing_extensions as te
63
+
62
64
from ..._api import TelemetryAPI
63
65
64
66
@@ -134,6 +136,8 @@ class AsyncBolt:
134
136
# results for it.
135
137
most_recent_qid = None
136
138
139
+ SKIP_REGISTRATION = False
140
+
137
141
def __init__ (
138
142
self ,
139
143
unresolved_address ,
@@ -257,101 +261,36 @@ def assert_notification_filtering_support(self):
257
261
f"{ self .server_info .agent !r} "
258
262
)
259
263
260
- # [bolt-version-bump] search tag when changing bolt version support
261
- @classmethod
262
- def protocol_handlers (cls , protocol_version = None ):
263
- """
264
- Return a dictionary of available Bolt protocol handlers.
265
-
266
- The handlers are keyed by version tuple. If an explicit protocol
267
- version is provided, the dictionary will contain either zero or one
268
- items, depending on whether that version is supported. If no protocol
269
- version is provided, all available versions will be returned.
270
-
271
- :param protocol_version: tuple identifying a specific protocol
272
- version (e.g. (3, 5)) or None
273
- :returns: dictionary of version tuple to handler class for all
274
- relevant and supported protocol versions
275
- :raise TypeError: if protocol version is not passed in a tuple
276
- """
277
- # Carry out Bolt subclass imports locally to avoid circular dependency
278
- # issues.
279
- from ._bolt3 import AsyncBolt3
280
- from ._bolt4 import (
281
- AsyncBolt4x1 ,
282
- AsyncBolt4x2 ,
283
- AsyncBolt4x3 ,
284
- AsyncBolt4x4 ,
285
- )
286
- from ._bolt5 import (
287
- AsyncBolt5x0 ,
288
- AsyncBolt5x1 ,
289
- AsyncBolt5x2 ,
290
- AsyncBolt5x3 ,
291
- AsyncBolt5x4 ,
292
- AsyncBolt5x5 ,
293
- AsyncBolt5x6 ,
294
- AsyncBolt5x7 ,
295
- AsyncBolt5x8 ,
296
- )
297
-
298
- handlers = {
299
- AsyncBolt3 .PROTOCOL_VERSION : AsyncBolt3 ,
300
- # 4.0 unsupported because no space left in the handshake
301
- AsyncBolt4x1 .PROTOCOL_VERSION : AsyncBolt4x1 ,
302
- AsyncBolt4x2 .PROTOCOL_VERSION : AsyncBolt4x2 ,
303
- AsyncBolt4x3 .PROTOCOL_VERSION : AsyncBolt4x3 ,
304
- AsyncBolt4x4 .PROTOCOL_VERSION : AsyncBolt4x4 ,
305
- AsyncBolt5x0 .PROTOCOL_VERSION : AsyncBolt5x0 ,
306
- AsyncBolt5x1 .PROTOCOL_VERSION : AsyncBolt5x1 ,
307
- AsyncBolt5x2 .PROTOCOL_VERSION : AsyncBolt5x2 ,
308
- AsyncBolt5x3 .PROTOCOL_VERSION : AsyncBolt5x3 ,
309
- AsyncBolt5x4 .PROTOCOL_VERSION : AsyncBolt5x4 ,
310
- AsyncBolt5x5 .PROTOCOL_VERSION : AsyncBolt5x5 ,
311
- AsyncBolt5x6 .PROTOCOL_VERSION : AsyncBolt5x6 ,
312
- AsyncBolt5x7 .PROTOCOL_VERSION : AsyncBolt5x7 ,
313
- AsyncBolt5x8 .PROTOCOL_VERSION : AsyncBolt5x8 ,
314
- }
264
+ protocol_handlers : t .ClassVar [dict [Version , type [AsyncBolt ]]] = {}
315
265
266
+ def __init_subclass__ (cls : type [te .Self ], ** kwargs : t .Any ) -> None :
267
+ if cls .SKIP_REGISTRATION :
268
+ super ().__init_subclass__ (** kwargs )
269
+ return
270
+ protocol_version = cls .PROTOCOL_VERSION
316
271
if protocol_version is None :
317
- return handlers
318
-
319
- if not isinstance (protocol_version , tuple ):
320
- raise TypeError ("Protocol version must be specified as a tuple" )
321
-
322
- if protocol_version in handlers :
323
- return {protocol_version : handlers [protocol_version ]}
324
-
325
- return {}
326
-
327
- @classmethod
328
- def version_list (cls , versions ):
329
- """
330
- Return a list of supported protocol versions in order of preference.
331
-
332
- The number of protocol versions (or ranges) returned is limited to 4.
333
- """
334
- # In fact, 4.3 is the fist version to support ranges. However, the
335
- # range support got backported to 4.2. But even if the server is too
336
- # old to have the backport, negotiating BOLT 4.1 is no problem as it's
337
- # equivalent to 4.2
338
- first_with_range_support = Version (4 , 2 )
339
- result = []
340
- for version in versions :
341
- if (
342
- result
343
- and version >= first_with_range_support
344
- and result [- 1 ][0 ] == version [0 ]
345
- and result [- 1 ][1 ][1 ] == version [1 ] + 1
346
- ):
347
- # can use range to encompass this version
348
- result [- 1 ][1 ][1 ] = version [1 ]
349
- continue
350
- result .append (Version (version [0 ], [version [1 ], version [1 ]]))
351
- if len (result ) == 4 :
352
- break
353
- return result
272
+ raise ValueError (
273
+ "AsyncBolt subclasses must define PROTOCOL_VERSION"
274
+ )
275
+ if not (
276
+ isinstance (protocol_version , Version )
277
+ and len (protocol_version ) == 2
278
+ and all (isinstance (i , int ) for i in protocol_version )
279
+ ):
280
+ raise TypeError (
281
+ "PROTOCOL_VERSION must be a 2-tuple of integers, not "
282
+ f"{ protocol_version !r} "
283
+ )
284
+ if protocol_version in AsyncBolt .protocol_handlers :
285
+ cls_conflict = AsyncBolt .protocol_handlers [protocol_version ]
286
+ raise TypeError (
287
+ f"Multiple classes for the same protocol version "
288
+ f"{ protocol_version } : { cls } , { cls_conflict } "
289
+ )
290
+ cls .protocol_handlers [protocol_version ] = cls
291
+ super ().__init_subclass__ (** kwargs )
354
292
293
+ # [bolt-version-bump] search tag when changing bolt version support
355
294
@classmethod
356
295
def get_handshake (cls ):
357
296
"""
@@ -360,12 +299,9 @@ def get_handshake(cls):
360
299
The length is 16 bytes as specified in the Bolt version negotiation.
361
300
:returns: bytes
362
301
"""
363
- supported_versions = sorted (
364
- cls . protocol_handlers (). keys (), reverse = True
302
+ return (
303
+ b" \x00 \x00 \x01 \xff \x00 \x08 \x08 \x05 \x00 \x02 \x04 \x04 \x00 \x00 \x00 \x03 "
365
304
)
366
- offered_versions = cls .version_list (supported_versions )
367
- versions_bytes = (v .to_bytes () for v in offered_versions )
368
- return b"" .join (versions_bytes ).ljust (16 , b"\x00 " )
369
305
370
306
@classmethod
371
307
async def ping (cls , address , * , deadline = None , pool_config = None ):
@@ -400,7 +336,6 @@ async def ping(cls, address, *, deadline=None, pool_config=None):
400
336
await AsyncBoltSocket .close_socket (s )
401
337
return protocol_version
402
338
403
- # [bolt-version-bump] search tag when changing bolt version support
404
339
@classmethod
405
340
async def open (
406
341
cls ,
@@ -441,71 +376,17 @@ async def open(
441
376
)
442
377
443
378
pool_config .protocol_version = protocol_version
444
-
445
- # Carry out Bolt subclass imports locally to avoid circular dependency
446
- # issues.
447
-
448
- # avoid new lines after imports for better readability and conciseness
449
- # fmt: off
450
- if protocol_version == (5 , 8 ):
451
- from ._bolt5 import AsyncBolt5x8
452
- bolt_cls = AsyncBolt5x8
453
- elif protocol_version == (5 , 7 ):
454
- from ._bolt5 import AsyncBolt5x7
455
- bolt_cls = AsyncBolt5x7
456
- elif protocol_version == (5 , 6 ):
457
- from ._bolt5 import AsyncBolt5x6
458
- bolt_cls = AsyncBolt5x6
459
- elif protocol_version == (5 , 5 ):
460
- from ._bolt5 import AsyncBolt5x5
461
- bolt_cls = AsyncBolt5x5
462
- elif protocol_version == (5 , 4 ):
463
- from ._bolt5 import AsyncBolt5x4
464
- bolt_cls = AsyncBolt5x4
465
- elif protocol_version == (5 , 3 ):
466
- from ._bolt5 import AsyncBolt5x3
467
- bolt_cls = AsyncBolt5x3
468
- elif protocol_version == (5 , 2 ):
469
- from ._bolt5 import AsyncBolt5x2
470
- bolt_cls = AsyncBolt5x2
471
- elif protocol_version == (5 , 1 ):
472
- from ._bolt5 import AsyncBolt5x1
473
- bolt_cls = AsyncBolt5x1
474
- elif protocol_version == (5 , 0 ):
475
- from ._bolt5 import AsyncBolt5x0
476
- bolt_cls = AsyncBolt5x0
477
- elif protocol_version == (4 , 4 ):
478
- from ._bolt4 import AsyncBolt4x4
479
- bolt_cls = AsyncBolt4x4
480
- elif protocol_version == (4 , 3 ):
481
- from ._bolt4 import AsyncBolt4x3
482
- bolt_cls = AsyncBolt4x3
483
- elif protocol_version == (4 , 2 ):
484
- from ._bolt4 import AsyncBolt4x2
485
- bolt_cls = AsyncBolt4x2
486
- elif protocol_version == (4 , 1 ):
487
- from ._bolt4 import AsyncBolt4x1
488
- bolt_cls = AsyncBolt4x1
489
- # Implementation for 4.0 exists, but there was no space left in the
490
- # handshake to offer this version to the server. Hence, the server
491
- # should never request us to speak bolt 4.0.
492
- # elif protocol_version == (4, 0):
493
- # from ._bolt4 import AsyncBolt4x0
494
- # bolt_cls = AsyncBolt4x0
495
- elif protocol_version == (3 , 0 ):
496
- from ._bolt3 import AsyncBolt3
497
- bolt_cls = AsyncBolt3
498
- # fmt: on
499
- else :
379
+ protocol_handlers = AsyncBolt .protocol_handlers
380
+ bolt_cls = protocol_handlers .get (protocol_version )
381
+ if bolt_cls is None :
500
382
log .debug ("[#%04X] C: <CLOSE>" , s .getsockname ()[1 ])
501
383
await AsyncBoltSocket .close_socket (s )
502
384
503
- supported_versions = cls .protocol_handlers ().keys ()
504
385
# TODO: 6.0 - raise public DriverError subclass instead
505
386
raise BoltHandshakeError (
506
387
"The neo4j server does not support communication with this "
507
388
"driver. This driver has support for Bolt protocols "
508
- f"{ tuple (map (str , supported_versions ))} ." ,
389
+ f"{ tuple (map (str , AsyncBolt . protocol_handlers . keys () ))} ." ,
509
390
address = address ,
510
391
request_data = handshake ,
511
392
response_data = data ,
0 commit comments