17
17
18
18
19
19
from collections import deque
20
- from warnings import warn
21
20
22
21
from ..._async_compat .util import AsyncUtil
23
22
from ...data import DataDehydrator
24
- from ...exceptions import ResultNotSingleError
23
+ from ...exceptions import (
24
+ ResultError ,
25
+ ResultNotSingleError ,
26
+ )
25
27
from ...work import ResultSummary
26
28
from ..io import ConnectionErrorHandler
27
29
28
30
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
+
29
42
class AsyncResult :
30
43
"""A handler for the result of Cypher query execution. Instances
31
44
of this class are typically constructed and returned by
@@ -54,6 +67,10 @@ def __init__(self, connection, hydrant, fetch_size, on_closed,
54
67
self ._has_more = False
55
68
# the result has been fully iterated or consumed
56
69
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
57
74
58
75
@property
59
76
def _qid (self ):
@@ -196,6 +213,10 @@ async def __aiter__(self):
196
213
await self ._connection .send_all ()
197
214
198
215
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 )
199
220
200
221
async def __anext__ (self ):
201
222
return await self .__aiter__ ().__anext__ ()
@@ -216,6 +237,10 @@ async def _buffer(self, n=None):
216
237
Might ent up with fewer records in the buffer if there are not enough
217
238
records available.
218
239
"""
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 )
219
244
if n is not None and len (self ._record_buffer ) >= n :
220
245
return
221
246
record_buffer = deque ()
@@ -261,6 +286,14 @@ def keys(self):
261
286
"""
262
287
return self ._keys
263
288
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
+
264
297
async def consume (self ):
265
298
"""Consume the remainder of this result and return a :class:`neo4j.ResultSummary`.
266
299
@@ -302,7 +335,9 @@ async def get_two_tx(tx):
302
335
async for _ in self :
303
336
pass
304
337
305
- return self ._obtain_summary ()
338
+ summary = self ._obtain_summary ()
339
+ self ._consumed = True
340
+ return summary
306
341
307
342
async def single (self ):
308
343
"""Obtain the next and only remaining record from this result if available else return None.
@@ -312,7 +347,10 @@ async def single(self):
312
347
the first of these is still returned.
313
348
314
349
: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.
316
354
"""
317
355
await self ._buffer (2 )
318
356
if not self ._record_buffer :
@@ -332,6 +370,10 @@ async def peek(self):
332
370
This leaves the record in the buffer for further processing.
333
371
334
372
: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.
335
377
"""
336
378
await self ._buffer (1 )
337
379
if self ._record_buffer :
@@ -344,6 +386,10 @@ async def graph(self):
344
386
345
387
:returns: a result graph
346
388
: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.
347
393
"""
348
394
await self ._buffer_all ()
349
395
return self ._hydrant .graph
@@ -355,8 +401,13 @@ async def value(self, key=0, default=None):
355
401
356
402
:param key: field to return for each remaining record. Obtain a single value from the record by index or key.
357
403
:param default: default value, used if the index of key is unavailable
404
+
358
405
:returns: list of individual values
359
406
: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.
360
411
"""
361
412
return [record .value (key , default ) async for record in self ]
362
413
@@ -366,8 +417,13 @@ async def values(self, *keys):
366
417
See :class:`neo4j.AsyncRecord.values`
367
418
368
419
:param keys: fields to return for each remaining record. Optionally filtering to include only certain values by index or key.
420
+
369
421
:returns: list of values lists
370
422
: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.
371
427
"""
372
428
return [record .values (* keys ) async for record in self ]
373
429
@@ -377,7 +433,26 @@ async def data(self, *keys):
377
433
See :class:`neo4j.AsyncRecord.data`
378
434
379
435
:param keys: fields to return for each remaining record. Optionally filtering to include only certain values by index or key.
436
+
380
437
:returns: list of dictionaries
381
438
:rtype: list
439
+
440
+ :raises ResultError: if the transaction from which this result was
441
+ obtained has been closed.
382
442
"""
383
443
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