1717
1818
1919from collections import deque
20- from warnings import warn
2120
2221from ..._async_compat .util import AsyncUtil
2322from ...data import DataDehydrator
24- from ...exceptions import ResultNotSingleError
23+ from ...exceptions import (
24+ ResultError ,
25+ ResultNotSingleError ,
26+ )
2527from ...work import ResultSummary
2628from ..io import ConnectionErrorHandler
2729
2830
31+ _RESULT_OUT_OF_SCOPE_ERROR = (
32+ "The result is out of scope. The associated transaction "
33+ "has been closed. Results can only be used while the "
34+ "transaction is open."
35+ )
36+ _RESULT_CONSUMED_ERROR = (
37+ "The result has been consumed. Fetch all needed records before calling "
38+ "Result.consume()."
39+ )
40+
41+
2942class AsyncResult :
3043 """A handler for the result of Cypher query execution. Instances
3144 of this class are typically constructed and returned by
@@ -54,6 +67,10 @@ def __init__(self, connection, hydrant, fetch_size, on_closed,
5467 self ._has_more = False
5568 # the result has been fully iterated or consumed
5669 self ._closed = False
70+ # the result has been consumed
71+ self ._consumed = False
72+ # the result has been closed as a result of closing the transaction
73+ self ._out_of_scope = False
5774
5875 @property
5976 def _qid (self ):
@@ -196,6 +213,10 @@ async def __aiter__(self):
196213 await self ._connection .send_all ()
197214
198215 self ._closed = True
216+ if self ._out_of_scope :
217+ raise ResultError (self , _RESULT_OUT_OF_SCOPE_ERROR )
218+ if self ._consumed :
219+ raise ResultError (self , _RESULT_CONSUMED_ERROR )
199220
200221 async def __anext__ (self ):
201222 return await self .__aiter__ ().__anext__ ()
@@ -216,6 +237,10 @@ async def _buffer(self, n=None):
216237 Might ent up with fewer records in the buffer if there are not enough
217238 records available.
218239 """
240+ if self ._out_of_scope :
241+ raise ResultError (self , _RESULT_OUT_OF_SCOPE_ERROR )
242+ if self ._consumed :
243+ raise ResultError (self , _RESULT_CONSUMED_ERROR )
219244 if n is not None and len (self ._record_buffer ) >= n :
220245 return
221246 record_buffer = deque ()
@@ -261,6 +286,14 @@ def keys(self):
261286 """
262287 return self ._keys
263288
289+ async def _tx_end (self ):
290+ # Handle closure of the associated transaction.
291+ #
292+ # This will consume the result and mark it at out of scope.
293+ # Subsequent calls to `next` will raise a ResultError.
294+ await self .consume ()
295+ self ._out_of_scope = True
296+
264297 async def consume (self ):
265298 """Consume the remainder of this result and return a :class:`neo4j.ResultSummary`.
266299
@@ -302,7 +335,9 @@ async def get_two_tx(tx):
302335 async for _ in self :
303336 pass
304337
305- return self ._obtain_summary ()
338+ summary = self ._obtain_summary ()
339+ self ._consumed = True
340+ return summary
306341
307342 async def single (self ):
308343 """Obtain the next and only remaining record from this result if available else return None.
@@ -312,7 +347,10 @@ async def single(self):
312347 the first of these is still returned.
313348
314349 :returns: the next :class:`neo4j.AsyncRecord`.
315- :raises: ResultNotSingleError if not exactly one record is available.
350+
351+ :raises ResultNotSingleError: if not exactly one record is available.
352+ :raises ResultError: if the transaction from which this result was
353+ obtained has been closed.
316354 """
317355 await self ._buffer (2 )
318356 if not self ._record_buffer :
@@ -332,6 +370,10 @@ async def peek(self):
332370 This leaves the record in the buffer for further processing.
333371
334372 :returns: the next :class:`.Record` or :const:`None` if none remain
373+
374+ :raises ResultError: if the transaction from which this result was
375+ obtained has been closed or the Result has been explicitly
376+ consumed.
335377 """
336378 await self ._buffer (1 )
337379 if self ._record_buffer :
@@ -344,6 +386,10 @@ async def graph(self):
344386
345387 :returns: a result graph
346388 :rtype: :class:`neo4j.graph.Graph`
389+
390+ :raises ResultError: if the transaction from which this result was
391+ obtained has been closed or the Result has been explicitly
392+ consumed.
347393 """
348394 await self ._buffer_all ()
349395 return self ._hydrant .graph
@@ -355,8 +401,13 @@ async def value(self, key=0, default=None):
355401
356402 :param key: field to return for each remaining record. Obtain a single value from the record by index or key.
357403 :param default: default value, used if the index of key is unavailable
404+
358405 :returns: list of individual values
359406 :rtype: list
407+
408+ :raises ResultError: if the transaction from which this result was
409+ obtained has been closed or the Result has been explicitly
410+ consumed.
360411 """
361412 return [record .value (key , default ) async for record in self ]
362413
@@ -366,8 +417,13 @@ async def values(self, *keys):
366417 See :class:`neo4j.AsyncRecord.values`
367418
368419 :param keys: fields to return for each remaining record. Optionally filtering to include only certain values by index or key.
420+
369421 :returns: list of values lists
370422 :rtype: list
423+
424+ :raises ResultError: if the transaction from which this result was
425+ obtained has been closed or the Result has been explicitly
426+ consumed.
371427 """
372428 return [record .values (* keys ) async for record in self ]
373429
@@ -377,7 +433,26 @@ async def data(self, *keys):
377433 See :class:`neo4j.AsyncRecord.data`
378434
379435 :param keys: fields to return for each remaining record. Optionally filtering to include only certain values by index or key.
436+
380437 :returns: list of dictionaries
381438 :rtype: list
439+
440+ :raises ResultError: if the transaction from which this result was
441+ obtained has been closed.
382442 """
383443 return [record .data (* keys ) async for record in self ]
444+
445+ def closed (self ):
446+ """Return True if the result is still valid (not closed).
447+
448+ When a result gets consumed :meth:`consume` or the transaction that
449+ owns the result gets closed (committed, rolled back, closed), the
450+ result cannot be used to acquire further records.
451+
452+ In such case, all methods that need to access the Result's records,
453+ will raise a :exc:`ResultError` when called.
454+
455+ :returns: whether the result is closed.
456+ :rtype: bool
457+ """
458+ return self ._out_of_scope or self ._consumed
0 commit comments