1717
1818
1919from collections import deque
20+ from warnings import warn
2021
2122from ..._async_compat .util import AsyncUtil
2223from ...data import DataDehydrator
@@ -248,11 +249,11 @@ async def _buffer(self, n=None):
248249 record_buffer .append (record )
249250 if n is not None and len (record_buffer ) >= n :
250251 break
251- self ._exhausted = False
252252 if n is None :
253253 self ._record_buffer = record_buffer
254254 else :
255255 self ._record_buffer .extend (record_buffer )
256+ self ._exhausted = not self ._record_buffer
256257
257258 async def _buffer_all (self ):
258259 """Sets the Result object in an detached state by fetching all records
@@ -286,12 +287,20 @@ def keys(self):
286287 """
287288 return self ._keys
288289
290+ async def _exhaust (self ):
291+ # Exhaust the result, ditching all remaining records.
292+ if not self ._exhausted :
293+ self ._discarding = True
294+ self ._record_buffer .clear ()
295+ async for _ in self :
296+ pass
297+
289298 async def _tx_end (self ):
290299 # Handle closure of the associated transaction.
291300 #
292301 # This will consume the result and mark it at out of scope.
293302 # Subsequent calls to `next` will raise a ResultConsumedError.
294- await self .consume ()
303+ await self ._exhaust ()
295304 self ._out_of_scope = True
296305
297306 async def consume (self ):
@@ -329,43 +338,93 @@ async def get_two_tx(tx):
329338 values, info = session.read_transaction(get_two_tx)
330339
331340 :returns: The :class:`neo4j.ResultSummary` for this result
341+
342+ :raises ResultConsumedError: if the transaction from which this result
343+ was obtained has been closed.
344+
345+ .. versionchanged:: 5.0
346+ Can raise :exc:`ResultConsumedError`.
332347 """
333- if self ._exhausted is False :
334- self . _discarding = True
335- async for _ in self :
336- pass
348+ if self ._out_of_scope :
349+ raise ResultConsumedError ( self , _RESULT_OUT_OF_SCOPE_ERROR )
350+ if self . _consumed :
351+ return self . _obtain_summary ()
337352
353+ await self ._exhaust ()
338354 summary = self ._obtain_summary ()
339355 self ._consumed = True
340356 return summary
341357
342- async def single (self ):
343- """Obtain the next and only remaining record from this result if available else return None.
358+ async def single (self , strict = False ):
359+ """Obtain the next and only remaining record or None.
360+
344361 Calling this method always exhausts the result.
345362
346363 A warning is generated if more than one record is available but
347364 the first of these is still returned.
348365
349- :returns: the next :class:`neo4j.AsyncRecord`.
366+ :param strict:
367+ If :const:`True`, raise a :class:`neo4j.ResultNotSingleError`
368+ instead of returning None if there is more than one record or
369+ warning if there are more than 1 record.
370+ :const:`False` by default.
371+ :type strict: bool
372+
373+ :returns: the next :class:`neo4j.Record` or :const:`None` if none remain
374+ :warns: if more than one record is available
375+
376+ :raises ResultNotSingleError:
377+ If ``strict=True`` and not exactly one record is available.
378+ :raises ResultConsumedError: if the transaction from which this result
379+ was obtained has been closed or the Result has been explicitly
380+ consumed.
350381
351- :raises ResultNotSingleError: if not exactly one record is available.
352- :raises ResultConsumedError: if the transaction from which this result was
353- obtained has been closed.
382+ .. versionchanged:: 5.0
383+ Added ``strict`` parameter.
384+ .. versionchanged:: 5.0
385+ Can raise :exc:`ResultConsumedError`.
354386 """
355387 await self ._buffer (2 )
356- if not self ._record_buffer :
388+ buffer = self ._record_buffer
389+ self ._record_buffer = deque ()
390+ await self ._exhaust ()
391+ if not buffer :
392+ if not strict :
393+ return None
357394 raise ResultNotSingleError (
358395 self ,
359396 "No records found. "
360397 "Make sure your query returns exactly one record."
361398 )
362- elif len (self ._record_buffer ) > 1 :
363- raise ResultNotSingleError (
364- self ,
365- "More than one record found. "
366- "Make sure your query returns exactly one record."
367- )
368- return self ._record_buffer .popleft ()
399+ elif len (buffer ) > 1 :
400+ res = buffer .popleft ()
401+ if not strict :
402+ warn ("Expected a result with a single record, "
403+ "but found multiple." )
404+ return res
405+ else :
406+ raise ResultNotSingleError (
407+ self ,
408+ "More than one record found. "
409+ "Make sure your query returns exactly one record."
410+ )
411+ return buffer .popleft ()
412+
413+ async def fetch (self , n ):
414+ """Obtain up to n records from this result.
415+
416+ :param n: the maximum number of records to fetch.
417+ :type n: int
418+
419+ :returns: list of :class:`neo4j.AsyncRecord`
420+
421+ .. versionadded:: 5.0
422+ """
423+ await self ._buffer (n )
424+ return [
425+ self ._record_buffer .popleft ()
426+ for _ in range (min (n , len (self ._record_buffer )))
427+ ]
369428
370429 async def peek (self ):
371430 """Obtain the next record from this result without consuming it.
@@ -376,6 +435,9 @@ async def peek(self):
376435 :raises ResultConsumedError: if the transaction from which this result
377436 was obtained has been closed or the Result has been explicitly
378437 consumed.
438+
439+ .. versionchanged:: 5.0
440+ Can raise :exc:`ResultConsumedError`.
379441 """
380442 await self ._buffer (1 )
381443 if self ._record_buffer :
@@ -392,6 +454,9 @@ async def graph(self):
392454 :raises ResultConsumedError: if the transaction from which this result
393455 was obtained has been closed or the Result has been explicitly
394456 consumed.
457+
458+ .. versionchanged:: 5.0
459+ Can raise :exc:`ResultConsumedError`.
395460 """
396461 await self ._buffer_all ()
397462 return self ._hydrant .graph
@@ -410,6 +475,9 @@ async def value(self, key=0, default=None):
410475 :raises ResultConsumedError: if the transaction from which this result
411476 was obtained has been closed or the Result has been explicitly
412477 consumed.
478+
479+ .. versionchanged:: 5.0
480+ Can raise :exc:`ResultConsumedError`.
413481 """
414482 return [record .value (key , default ) async for record in self ]
415483
@@ -426,6 +494,9 @@ async def values(self, *keys):
426494 :raises ResultConsumedError: if the transaction from which this result
427495 was obtained has been closed or the Result has been explicitly
428496 consumed.
497+
498+ .. versionchanged:: 5.0
499+ Can raise :exc:`ResultConsumedError`.
429500 """
430501 return [record .values (* keys ) async for record in self ]
431502
@@ -439,8 +510,12 @@ async def data(self, *keys):
439510 :returns: list of dictionaries
440511 :rtype: list
441512
442- :raises ResultConsumedError: if the transaction from which this result was
443- obtained has been closed.
513+ :raises ResultConsumedError: if the transaction from which this result
514+ was obtained has been closed or the Result has been explicitly
515+ consumed.
516+
517+ .. versionchanged:: 5.0
518+ Can raise :exc:`ResultConsumedError`.
444519 """
445520 return [record .data (* keys ) async for record in self ]
446521
0 commit comments