Skip to content

Extend Result's API #673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@
- Creation of a driver with `bolt[+s[sc]]://` scheme has been deprecated and
will raise an error in the Future. The routing context was and will be
silently ignored until then.
- `Result.single` now raises `ResultNotSingleError` if not exactly one result is
available.
- Bookmarks
- `Session.last_bookmark` was deprecated. Its behaviour is partially incorrect
and cannot be fixed without breaking its signature.
Expand Down
5 changes: 5 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,8 @@ A :class:`neo4j.Result` is attached to an active connection, through a :class:`n

.. automethod:: single

.. automethod:: fetch

.. automethod:: peek

.. automethod:: graph
Expand Down Expand Up @@ -1368,6 +1370,9 @@ Connectivity Errors
.. autoclass:: neo4j.exceptions.ResultConsumedError
:show-inheritance:

.. autoclass:: neo4j.exceptions.ResultNotSingleError
:show-inheritance:



Internal Driver Errors
Expand Down
2 changes: 2 additions & 0 deletions docs/source/async_api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,8 @@ A :class:`neo4j.AsyncResult` is attached to an active connection, through a :cla

.. automethod:: single

.. automethod:: fetch

.. automethod:: peek

.. automethod:: graph
Expand Down
119 changes: 97 additions & 22 deletions neo4j/_async/work/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@


from collections import deque
from warnings import warn

from ..._async_compat.util import AsyncUtil
from ...data import DataDehydrator
Expand Down Expand Up @@ -248,11 +249,11 @@ async def _buffer(self, n=None):
record_buffer.append(record)
if n is not None and len(record_buffer) >= n:
break
self._exhausted = False
if n is None:
self._record_buffer = record_buffer
else:
self._record_buffer.extend(record_buffer)
self._exhausted = not self._record_buffer

async def _buffer_all(self):
"""Sets the Result object in an detached state by fetching all records
Expand Down Expand Up @@ -286,12 +287,20 @@ def keys(self):
"""
return self._keys

async def _exhaust(self):
# Exhaust the result, ditching all remaining records.
if not self._exhausted:
self._discarding = True
self._record_buffer.clear()
async for _ in self:
pass

async def _tx_end(self):
# Handle closure of the associated transaction.
#
# This will consume the result and mark it at out of scope.
# Subsequent calls to `next` will raise a ResultConsumedError.
await self.consume()
await self._exhaust()
self._out_of_scope = True

async def consume(self):
Expand Down Expand Up @@ -329,43 +338,93 @@ async def get_two_tx(tx):
values, info = session.read_transaction(get_two_tx)

:returns: The :class:`neo4j.ResultSummary` for this result

:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
if self._exhausted is False:
self._discarding = True
async for _ in self:
pass
if self._out_of_scope:
raise ResultConsumedError(self, _RESULT_OUT_OF_SCOPE_ERROR)
if self._consumed:
return self._obtain_summary()

await self._exhaust()
summary = self._obtain_summary()
self._consumed = True
return summary

async def single(self):
"""Obtain the next and only remaining record from this result if available else return None.
async def single(self, strict=False):
"""Obtain the next and only remaining record or None.

Calling this method always exhausts the result.

A warning is generated if more than one record is available but
the first of these is still returned.

:returns: the next :class:`neo4j.AsyncRecord`.
:param strict:
If :const:`True`, raise a :class:`neo4j.ResultNotSingleError`
instead of returning None if there is more than one record or
warning if there are more than 1 record.
:const:`False` by default.
:type strict: bool

:returns: the next :class:`neo4j.Record` or :const:`None` if none remain
:warns: if more than one record is available

:raises ResultNotSingleError:
If ``strict=True`` and not exactly one record is available.
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

:raises ResultNotSingleError: if not exactly one record is available.
:raises ResultConsumedError: if the transaction from which this result was
obtained has been closed.
.. versionchanged:: 5.0
Added ``strict`` parameter.
.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
await self._buffer(2)
if not self._record_buffer:
buffer = self._record_buffer
self._record_buffer = deque()
await self._exhaust()
if not buffer:
if not strict:
return None
raise ResultNotSingleError(
self,
"No records found. "
"Make sure your query returns exactly one record."
)
elif len(self._record_buffer) > 1:
raise ResultNotSingleError(
self,
"More than one record found. "
"Make sure your query returns exactly one record."
)
return self._record_buffer.popleft()
elif len(buffer) > 1:
res = buffer.popleft()
if not strict:
warn("Expected a result with a single record, "
"but found multiple.")
return res
else:
raise ResultNotSingleError(
self,
"More than one record found. "
"Make sure your query returns exactly one record."
)
return buffer.popleft()

async def fetch(self, n):
"""Obtain up to n records from this result.

:param n: the maximum number of records to fetch.
:type n: int

:returns: list of :class:`neo4j.AsyncRecord`

.. versionadded:: 5.0
"""
await self._buffer(n)
return [
self._record_buffer.popleft()
for _ in range(min(n, len(self._record_buffer)))
]

async def peek(self):
"""Obtain the next record from this result without consuming it.
Expand All @@ -376,6 +435,9 @@ async def peek(self):
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
await self._buffer(1)
if self._record_buffer:
Expand All @@ -392,6 +454,9 @@ async def graph(self):
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
await self._buffer_all()
return self._hydrant.graph
Expand All @@ -410,6 +475,9 @@ async def value(self, key=0, default=None):
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
return [record.value(key, default) async for record in self]

Expand All @@ -426,6 +494,9 @@ async def values(self, *keys):
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
return [record.values(*keys) async for record in self]

Expand All @@ -439,8 +510,12 @@ async def data(self, *keys):
:returns: list of dictionaries
:rtype: list

:raises ResultConsumedError: if the transaction from which this result was
obtained has been closed.
:raises ResultConsumedError: if the transaction from which this result
was obtained has been closed or the Result has been explicitly
consumed.

.. versionchanged:: 5.0
Can raise :exc:`ResultConsumedError`.
"""
return [record.data(*keys) async for record in self]

Expand Down
Loading