2626from warnings import warn
2727
2828from neo4j .bolt import ProtocolError , ServiceUnavailable
29- from neo4j .compat import urlparse
29+ from neo4j .compat import unicode , urlparse
3030from neo4j .exceptions import CypherError , TransientError
3131
3232from .exceptions import DriverError , SessionError , SessionExpired , TransactionError
4242RETRY_DELAY_JITTER_FACTOR = 0.2
4343
4444
45+ def last_bookmark (b0 , b1 ):
46+ """ Return the latest of two bookmarks by looking for the maximum
47+ integer value following the last colon in the bookmark string.
48+ """
49+ n = [None , None ]
50+ _ , _ , n [0 ] = b0 .rpartition (":" )
51+ _ , _ , n [1 ] = b1 .rpartition (":" )
52+ for i in range (2 ):
53+ try :
54+ n [i ] = int (n [i ])
55+ except ValueError :
56+ raise ValueError ("Invalid bookmark: {}" .format (b0 ))
57+ return b0 if n [0 ] > n [1 ] else b1
58+
59+
4560def retry_delay_generator (initial_delay , multiplier , jitter_factor ):
4661 delay = initial_delay
4762 while True :
@@ -141,14 +156,25 @@ def __enter__(self):
141156 def __exit__ (self , exc_type , exc_value , traceback ):
142157 self .close ()
143158
144- def session (self , access_mode = None , bookmark = None ):
159+ def session (self , access_mode = None , ** parameters ):
145160 """ Create a new session using a connection from the driver connection
146161 pool. Session creation is a lightweight operation and sessions are
147162 not thread safe, therefore a session should generally be short-lived
148163 within a single thread.
149164
150- :param access_mode:
151- :param bookmark:
165+ :param access_mode: access mode for this session (read or write)
166+ :param parameters: set of parameters for this session:
167+
168+ `bookmark`
169+ A bookmark after which this session should begin.
170+
171+ `bookmarks`
172+ A collection of bookmarks after which this session should begin.
173+
174+ `max_retry_time`
175+ The maximum time after which to stop attempting retries of failed
176+ transactions.
177+
152178 :returns: new :class:`.Session` object
153179 """
154180 if self .closed ():
@@ -190,28 +216,39 @@ class Session(object):
190216
191217 """
192218
193- #: The current connection.
219+ # The current connection.
194220 _connection = None
195221
196- #: The access mode for the current connection.
222+ # The access mode for the current connection.
197223 _connection_access_mode = None
198224
199- #: The current :class:`.Transaction` instance, if any.
225+ # The current :class:`.Transaction` instance, if any.
200226 _transaction = None
201227
202- #: The last result received.
228+ # The last result received.
203229 _last_result = None
204230
205- #: The bookmark received from completion of the last :class:`.Transaction`.
206- _bookmark = None
231+ # The collection of bookmarks after which the next
232+ # :class:`.Transaction` should be carried out.
233+ _bookmarks = ()
234+
235+ # Default maximum time to keep retrying failed transactions.
236+ _max_retry_time = DEFAULT_MAX_RETRY_TIME
207237
208238 _closed = False
209239
210- def __init__ (self , acquirer , max_retry_time = None , access_mode = None , bookmark = None ):
240+ def __init__ (self , acquirer , access_mode , ** parameters ):
211241 self ._acquirer = acquirer
212- self ._max_retry_time = DEFAULT_MAX_RETRY_TIME if max_retry_time is None else max_retry_time
213- self ._default_access_mode = access_mode or WRITE_ACCESS
214- self ._bookmark = bookmark
242+ self ._default_access_mode = access_mode
243+ for key , value in parameters .items ():
244+ if key == "bookmark" :
245+ self ._bookmarks = [value ] if value else []
246+ elif key == "bookmarks" :
247+ self ._bookmarks = value or []
248+ elif key == "max_retry_time" :
249+ self ._max_retry_time = value
250+ else :
251+ pass # for compatibility
215252
216253 def __del__ (self ):
217254 try :
@@ -354,7 +391,13 @@ def detach(self, result):
354391 def last_bookmark (self ):
355392 """ The bookmark returned by the last :class:`.Transaction`.
356393 """
357- return self ._bookmark
394+ last = None
395+ for bookmark in self ._bookmarks :
396+ if last is None :
397+ last = bookmark
398+ else :
399+ last = last_bookmark (last , bookmark )
400+ return last
358401
359402 def has_transaction (self ):
360403 return bool (self ._transaction )
@@ -383,7 +426,7 @@ def begin_transaction(self, bookmark=None):
383426 from warnings import warn
384427 warn ("Passing bookmarks at transaction level is deprecated" , category = DeprecationWarning , stacklevel = 2 )
385428 _warned_about_transaction_bookmarks = True
386- self ._bookmark = bookmark
429+ self ._bookmarks = [ bookmark ]
387430
388431 self ._create_transaction ()
389432 self ._connect ()
@@ -401,8 +444,9 @@ def commit_transaction(self):
401444 self ._transaction = None
402445 result = self .__commit__ ()
403446 result .consume ()
404- self ._bookmark = self .__bookmark__ (result )
405- return self ._bookmark
447+ bookmark = self .__bookmark__ (result )
448+ self ._bookmarks = [bookmark ]
449+ return bookmark
406450
407451 def rollback_transaction (self ):
408452 """ Rollback the current transaction.
@@ -412,7 +456,6 @@ def rollback_transaction(self):
412456 if not self .has_transaction ():
413457 raise TransactionError ("No transaction to rollback" )
414458 self ._transaction = None
415- self ._bookmark = None
416459 self .__rollback__ ().consume ()
417460
418461 def _run_transaction (self , access_mode , unit_of_work , * args , ** kwargs ):
0 commit comments