Skip to content

Commit 47926e4

Browse files
authored
Deprecate Session.last_bookmark for Session.last_bookmarks (#649)
Deprecate `Session.last_bookmark`. Add `Session.last_bookmarks` which returns a list of strings instead of an optional string. Furthermore, it will return *all* initial bookmarks (if not updated by a server message since session creation) instead of only the last bookmark, which not necessarily would be the latest bookmark. Add `neo4j.Bookmarks` to replace (now deprecated) `neo4j.Bookmark` and use if for managing bookmarks instead of raw bookmark strings.
1 parent 4a01b98 commit 47926e4

File tree

19 files changed

+503
-75
lines changed

19 files changed

+503
-75
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@
5454
was silently ignored.
5555
- `Result.single` now raises `ResultNotSingleError` if not exactly one result is
5656
available.
57+
- Bookmarks
58+
- `Session.last_bookmark` was deprecated. Its behaviour is partially incorrect
59+
and cannot be fixed without breaking its signature.
60+
Use `Session.last_bookmarks` instead.
61+
- `neo4j.Bookmark` was deprecated.
62+
Use `neo4j.Bookmarks` instead.
63+
5764

5865
## Version 4.4
5966

docs/source/api.rst

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,8 @@ Session
447447

448448
.. automethod:: run
449449

450+
.. automethod:: last_bookmarks
451+
450452
.. automethod:: last_bookmark
451453

452454
.. automethod:: begin_transaction
@@ -481,9 +483,16 @@ To construct a :class:`neo4j.Session` use the :meth:`neo4j.Driver.session` metho
481483

482484
``bookmarks``
483485
-------------
484-
An iterable containing :class:`neo4j.Bookmark`
486+
Optional :class:`neo4j.Bookmarks`. Use this to causally chain sessions.
487+
See :meth:`Session.last_bookmarks` or :meth:`AsyncSession.last_bookmarks` for
488+
more information.
489+
490+
.. deprecated:: 5.0
491+
Alternatively, an iterable of strings can be passed. This usage is
492+
deprecated and will be removed in a future release. Please use a
493+
:class:`neo4j.Bookmarks` object instead.
485494

486-
:Default: ``()``
495+
:Default: ``None``
487496

488497

489498
.. _database-ref:
@@ -1366,9 +1375,12 @@ This example shows how to suppress the :class:`neo4j.ExperimentalWarning` using
13661375
warnings.filterwarnings("ignore", category=ExperimentalWarning)
13671376
13681377
1369-
********
1370-
Bookmark
1371-
********
1378+
*********
1379+
Bookmarks
1380+
*********
1381+
1382+
.. autoclass:: neo4j.Bookmarks
1383+
:members:
13721384
13731385
.. autoclass:: neo4j.Bookmark
13741386
:members:

docs/source/async_api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ AsyncSession
299299

300300
.. automethod:: run
301301

302+
.. automethod:: last_bookmarks
303+
302304
.. automethod:: last_bookmark
303305

304306
.. automethod:: begin_transaction

neo4j/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"bearer_auth",
3333
"BoltDriver",
3434
"Bookmark",
35+
"Bookmarks",
3536
"Config",
3637
"custom_auth",
3738
"DEFAULT_DATABASE",
@@ -97,6 +98,7 @@
9798
basic_auth,
9899
bearer_auth,
99100
Bookmark,
101+
Bookmarks,
100102
custom_auth,
101103
DEFAULT_DATABASE,
102104
kerberos_auth,

neo4j/_async/work/session.py

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
from ..._async_compat import async_sleep
2525
from ...api import (
26+
Bookmarks,
2627
READ_ACCESS,
2728
WRITE_ACCESS,
2829
)
@@ -37,6 +38,10 @@
3738
TransactionError,
3839
TransientError,
3940
)
41+
from ...meta import (
42+
deprecated,
43+
deprecation_warn,
44+
)
4045
from ...work import Query
4146
from .result import AsyncResult
4247
from .transaction import AsyncTransaction
@@ -85,7 +90,7 @@ class AsyncSession(AsyncWorkspace):
8590
def __init__(self, pool, session_config):
8691
super().__init__(pool, session_config)
8792
assert isinstance(session_config, SessionConfig)
88-
self._bookmarks = tuple(session_config.bookmarks)
93+
self._bookmarks = self._prepare_bookmarks(session_config.bookmarks)
8994

9095
def __del__(self):
9196
if asyncio.iscoroutinefunction(self.close):
@@ -103,14 +108,29 @@ async def __aexit__(self, exception_type, exception_value, traceback):
103108
self._state_failed = True
104109
await self.close()
105110

111+
def _prepare_bookmarks(self, bookmarks):
112+
if isinstance(bookmarks, Bookmarks):
113+
return tuple(bookmarks.raw_values)
114+
if hasattr(bookmarks, "__iter__"):
115+
deprecation_warn(
116+
"Passing an iterable as `bookmarks` to `Session` is "
117+
"deprecated. Please use a `Bookmarks` instance.",
118+
stack_level=5
119+
)
120+
return tuple(bookmarks)
121+
if not bookmarks:
122+
return ()
123+
raise TypeError("Bookmarks must be an instance of Bookmarks or an "
124+
"iterable of raw bookmarks (deprecated).")
125+
106126
async def _connect(self, access_mode):
107127
if access_mode is None:
108128
access_mode = self._config.default_access_mode
109129
await super()._connect(access_mode)
110130

111131
def _collect_bookmark(self, bookmark):
112132
if bookmark:
113-
self._bookmarks = [bookmark]
133+
self._bookmarks = bookmark,
114134

115135
async def _result_closed(self):
116136
if self._auto_result:
@@ -222,11 +242,26 @@ async def run(self, query, parameters=None, **kwargs):
222242

223243
return self._auto_result
224244

245+
@deprecated(
246+
"`last_bookmark` has been deprecated in favor of `last_bookmarks`. "
247+
"This method can lead to unexpected behaviour."
248+
)
225249
async def last_bookmark(self):
226250
"""Return the bookmark received following the last completed transaction.
227-
Note: For auto-transaction (Session.run) this will trigger an consume for the current result.
228251
229-
:returns: :class:`neo4j.Bookmark` object
252+
Note: For auto-transactions (:meth:`Session.run`), this will trigger
253+
:meth:`Result.consume` for the current result.
254+
255+
.. warning::
256+
This method can lead to unexpected behaviour if the session has not
257+
yet successfully completed a transaction.
258+
259+
.. deprecated:: 5.0
260+
:meth:`last_bookmark` will be removed in version 6.0.
261+
Use :meth:`last_bookmarks` instead.
262+
263+
:returns: last bookmark
264+
:rtype: str or None
230265
"""
231266
# The set of bookmarks to be passed into the next transaction.
232267

@@ -237,10 +272,50 @@ async def last_bookmark(self):
237272
self._collect_bookmark(self._transaction._bookmark)
238273
self._transaction = None
239274

240-
if len(self._bookmarks):
241-
return self._bookmarks[len(self._bookmarks)-1]
275+
if self._bookmarks:
276+
return self._bookmarks[-1]
242277
return None
243278

279+
async def last_bookmarks(self):
280+
"""Return most recent bookmarks of the session.
281+
282+
Bookmarks can be used to causally chain sessions. For example,
283+
if a session (``session1``) wrote something, that another session
284+
(``session2``) needs to read, use
285+
``session2 = driver.session(bookmarks=session1.last_bookmarks())`` to
286+
achieve this.
287+
288+
Combine the bookmarks of multiple sessions like so::
289+
290+
bookmarks1 = await session1.last_bookmarks()
291+
bookmarks2 = await session2.last_bookmarks()
292+
session3 = driver.session(bookmarks=bookmarks1 + bookmarks2)
293+
294+
A session automatically manages bookmarks, so this method is rarely
295+
needed. If you need causal consistency, try to run the relevant queries
296+
in the same session.
297+
298+
"Most recent bookmarks" are either the bookmarks passed to the session
299+
or creation, or the last bookmark the session received after committing
300+
a transaction to the server.
301+
302+
Note: For auto-transactions (:meth:`Session.run`), this will trigger
303+
:meth:`Result.consume` for the current result.
304+
305+
:returns: the session's last known bookmarks
306+
:rtype: Bookmarks
307+
"""
308+
# The set of bookmarks to be passed into the next transaction.
309+
310+
if self._auto_result:
311+
await self._auto_result.consume()
312+
313+
if self._transaction and self._transaction._closed:
314+
self._collect_bookmark(self._transaction._bookmark)
315+
self._transaction = None
316+
317+
return Bookmarks.from_raw_values(self._bookmarks)
318+
244319
async def _transaction_closed_handler(self):
245320
if self._transaction:
246321
self._collect_bookmark(self._transaction._bookmark)

neo4j/_async_compat/network/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
# limitations under the License.
1717

1818

19-
from .bolt_socket import (
19+
from ._bolt_socket import (
2020
AsyncBoltSocket,
2121
BoltSocket,
2222
)
23-
from .util import (
23+
from ._util import (
2424
AsyncNetworkUtil,
2525
NetworkUtil,
2626
)

neo4j/_async_compat/network/bolt_socket.py renamed to neo4j/_async_compat/network/_bolt_socket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
DriverError,
4747
ServiceUnavailable,
4848
)
49-
from .util import (
49+
from ._util import (
5050
AsyncNetworkUtil,
5151
NetworkUtil,
5252
)
File renamed without changes.

neo4j/_async_compat/util.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
from ..meta import experimental
2222

2323

24+
__all__ = [
25+
"AsyncUtil",
26+
"Util",
27+
]
28+
29+
2430
class AsyncUtil:
2531
@staticmethod
2632
async def iter(it):

0 commit comments

Comments
 (0)