Skip to content
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

feat: integrate limit to last #145

Merged
merged 3 commits into from Aug 7, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 20 additions & 14 deletions google/cloud/firestore_v1/async_collection.py
Expand Up @@ -13,9 +13,6 @@
# limitations under the License.

"""Classes for representing collections for the Google Cloud Firestore API."""
import warnings


from google.cloud.firestore_v1.base_collection import (
BaseCollectionReference,
_auto_id,
Expand Down Expand Up @@ -130,17 +127,26 @@ async def list_documents(
async for i in iterator:
yield _item_to_document_ref(self, i)

async def get(
self, transaction=None
) -> AsyncGenerator[async_document.DocumentSnapshot, Any]:
"""Deprecated alias for :meth:`stream`."""
warnings.warn(
"'Collection.get' is deprecated: please use 'Collection.stream' instead.",
DeprecationWarning,
stacklevel=2,
)
async for d in self.stream(transaction=transaction):
yield d # pytype: disable=name-error
async def get(self, transaction=None) -> list:
"""Read the documents in this collection.

This sends a ``RunQuery`` RPC and returns a list of documents
crwilcox marked this conversation as resolved.
Show resolved Hide resolved
returned in the stream of ``RunQueryResponse`` messages.

Args:
transaction
(Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]):
An existing transaction that this query will run in.

If a ``transaction`` is used and it already has write operations
added, this method cannot be used (i.e. read-after-write is not
allowed).

Returns:
list: The documents in this collection that match the query.
"""
query = self._query()
return await query.get(transaction=transaction)

async def stream(
self, transaction=None
Expand Down
60 changes: 47 additions & 13 deletions google/cloud/firestore_v1/async_query.py
Expand Up @@ -18,12 +18,11 @@
a :class:`~google.cloud.firestore_v1.collection.Collection` and that can be
a more common way to create a query than direct usage of the constructor.
"""
import warnings

from google.cloud.firestore_v1.base_query import (
BaseQuery,
_query_response_to_snapshot,
_collection_group_query_response_to_snapshot,
_enum_from_direction,
)

from google.cloud.firestore_v1 import _helpers
Expand Down Expand Up @@ -94,6 +93,7 @@ def __init__(
field_filters=(),
orders=(),
limit=None,
limit_to_last=False,
offset=None,
start_at=None,
end_at=None,
Expand All @@ -105,23 +105,51 @@ def __init__(
field_filters=field_filters,
orders=orders,
limit=limit,
limit_to_last=limit_to_last,
offset=offset,
start_at=start_at,
end_at=end_at,
all_descendants=all_descendants,
)

async def get(
self, transaction=None
) -> AsyncGenerator[async_document.DocumentSnapshot, None]:
"""Deprecated alias for :meth:`stream`."""
warnings.warn(
"'AsyncQuery.get' is deprecated: please use 'AsyncQuery.stream' instead.",
DeprecationWarning,
stacklevel=2,
)
async for d in self.stream(transaction=transaction):
yield d
async def get(self, transaction=None) -> list:
"""Read the documents in the collection that match this query.

This sends a ``RunQuery`` RPC and returns a list of documents
returned in the stream of ``RunQueryResponse`` messages.

Args:
crwilcox marked this conversation as resolved.
Show resolved Hide resolved
transaction
(Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]):
An existing transaction that this query will run in.

If a ``transaction`` is used and it already has write operations
added, this method cannot be used (i.e. read-after-write is not
allowed).

Returns:
list: The documents in the collection that match this query.
"""
is_limited_to_last = self._limit_to_last

if self._limit_to_last:
# In order to fetch up to `self._limit` results from the end of the
# query flip the defined ordering on the query to start from the
# end, retrieving up to `self._limit` results from the backend.
for order in self._orders:
order.direction = _enum_from_direction(
self.DESCENDING
if order.direction == self.ASCENDING
else self.ASCENDING
)
self._limit_to_last = False

result = self.stream(transaction=transaction)
result = [d async for d in result]
if is_limited_to_last:
result = list(reversed(result))

return result

async def stream(
self, transaction=None
Expand Down Expand Up @@ -152,6 +180,12 @@ async def stream(
:class:`~google.cloud.firestore_v1.async_document.DocumentSnapshot`:
The next document that fulfills the query.
"""
if self._limit_to_last:
raise ValueError(
"Query results for queries that include limit_to_last() "
"constraints cannot be streamed. Use Query.get() instead."
)

parent_path, expected_prefix = self._parent._parent_info()
response_iterator = await self._client._firestore_api.run_query(
request={
Expand Down
22 changes: 22 additions & 0 deletions google/cloud/firestore_v1/base_collection.py
Expand Up @@ -205,6 +205,10 @@ def order_by(self, field_path, **kwargs) -> NoReturn:
def limit(self, count) -> NoReturn:
"""Create a limited query with this collection as parent.

.. note::
`limit` and `limit_to_last` are mutually exclusive.
Setting `limit` will drop previously set `limit_to_last`.

See
:meth:`~google.cloud.firestore_v1.query.Query.limit` for
more information on this method.
Expand All @@ -220,6 +224,24 @@ def limit(self, count) -> NoReturn:
query = self._query()
return query.limit(count)

def limit_to_last(self, count):
"""Create a limited to last query with this collection as parent.
.. note::
`limit` and `limit_to_last` are mutually exclusive.
Setting `limit_to_last` will drop previously set `limit`.
See
:meth:`~google.cloud.firestore_v1.query.Query.limit_to_last`
for more information on this method.
Args:
count (int): Maximum number of documents to return that
match the query.
Returns:
:class:`~google.cloud.firestore_v1.query.Query`:
A limited to last query.
"""
query = self._query()
return query.limit_to_last(count)

def offset(self, num_to_skip) -> NoReturn:
"""Skip to an offset in a query with this collection as parent.

Expand Down
45 changes: 42 additions & 3 deletions google/cloud/firestore_v1/base_query.py
Expand Up @@ -98,6 +98,8 @@ class BaseQuery(object):
The "order by" entries to use in the query.
limit (Optional[int]):
The maximum number of documents the query is allowed to return.
limit_to_last (Optional[bool]):
Denotes whether a provided limit is applied to the end of the result set.
offset (Optional[int]):
The number of results to skip.
start_at (Optional[Tuple[dict, bool]]):
Expand Down Expand Up @@ -146,6 +148,7 @@ def __init__(
field_filters=(),
orders=(),
limit=None,
limit_to_last=False,
offset=None,
start_at=None,
end_at=None,
Expand All @@ -156,6 +159,7 @@ def __init__(
self._field_filters = field_filters
self._orders = orders
self._limit = limit
self._limit_to_last = limit_to_last
self._offset = offset
self._start_at = start_at
self._end_at = end_at
Expand All @@ -170,6 +174,7 @@ def __eq__(self, other):
and self._field_filters == other._field_filters
and self._orders == other._orders
and self._limit == other._limit
and self._limit_to_last == other._limit_to_last
and self._offset == other._offset
and self._start_at == other._start_at
and self._end_at == other._end_at
Expand Down Expand Up @@ -224,6 +229,7 @@ def select(self, field_paths) -> "BaseQuery":
field_filters=self._field_filters,
orders=self._orders,
limit=self._limit,
limit_to_last=self._limit_to_last,
offset=self._offset,
start_at=self._start_at,
end_at=self._end_at,
Expand Down Expand Up @@ -294,6 +300,7 @@ def where(self, field_path, op_string, value) -> "BaseQuery":
orders=self._orders,
limit=self._limit,
offset=self._offset,
limit_to_last=self._limit_to_last,
start_at=self._start_at,
end_at=self._end_at,
all_descendants=self._all_descendants,
Expand Down Expand Up @@ -345,21 +352,51 @@ def order_by(self, field_path, direction=ASCENDING) -> "BaseQuery":
field_filters=self._field_filters,
orders=new_orders,
limit=self._limit,
limit_to_last=self._limit_to_last,
offset=self._offset,
start_at=self._start_at,
end_at=self._end_at,
all_descendants=self._all_descendants,
)

def limit(self, count) -> "BaseQuery":
"""Limit a query to return a fixed number of results.

If the current query already has a limit set, this will overwrite it.
"""Limit a query to return at most `count` matching results.

If the current query already has a `limit` set, this will override it.
.. note::
`limit` and `limit_to_last` are mutually exclusive.
Setting `limit` will drop previously set `limit_to_last`.
Args:
count (int): Maximum number of documents to return that match
the query.
Returns:
:class:`~google.cloud.firestore_v1.query.Query`:
A limited query. Acts as a copy of the current query, modified
with the newly added "limit" filter.
"""
return self.__class__(
self._parent,
projection=self._projection,
field_filters=self._field_filters,
orders=self._orders,
limit=count,
limit_to_last=False,
offset=self._offset,
start_at=self._start_at,
end_at=self._end_at,
all_descendants=self._all_descendants,
)

def limit_to_last(self, count):
"""Limit a query to return the last `count` matching results.
If the current query already has a `limit_to_last`
set, this will override it.
.. note::
`limit` and `limit_to_last` are mutually exclusive.
Setting `limit_to_last` will drop previously set `limit`.
Args:
count (int): Maximum number of documents to return that match
the query.
Returns:
:class:`~google.cloud.firestore_v1.query.Query`:
A limited query. Acts as a copy of the current query, modified
Expand All @@ -371,6 +408,7 @@ def limit(self, count) -> "BaseQuery":
field_filters=self._field_filters,
orders=self._orders,
limit=count,
limit_to_last=True,
offset=self._offset,
start_at=self._start_at,
end_at=self._end_at,
Expand Down Expand Up @@ -398,6 +436,7 @@ def offset(self, num_to_skip) -> "BaseQuery":
field_filters=self._field_filters,
orders=self._orders,
limit=self._limit,
limit_to_last=self._limit_to_last,
offset=num_to_skip,
start_at=self._start_at,
end_at=self._end_at,
Expand Down
30 changes: 20 additions & 10 deletions google/cloud/firestore_v1/collection.py
Expand Up @@ -13,8 +13,6 @@
# limitations under the License.

"""Classes for representing collections for the Google Cloud Firestore API."""
import warnings

from google.cloud.firestore_v1.base_collection import (
BaseCollectionReference,
_auto_id,
Expand Down Expand Up @@ -121,14 +119,26 @@ def list_documents(self, page_size=None) -> Generator[Any, Any, None]:
)
return (_item_to_document_ref(self, i) for i in iterator)

def get(self, transaction=None) -> Generator[document.DocumentSnapshot, Any, None]:
"""Deprecated alias for :meth:`stream`."""
warnings.warn(
"'Collection.get' is deprecated: please use 'Collection.stream' instead.",
DeprecationWarning,
stacklevel=2,
)
return self.stream(transaction=transaction)
def get(self, transaction=None) -> list:
"""Read the documents in this collection.
crwilcox marked this conversation as resolved.
Show resolved Hide resolved

This sends a ``RunQuery`` RPC and returns a list of documents
returned in the stream of ``RunQueryResponse`` messages.

Args:
transaction
(Optional[:class:`~google.cloud.firestore_v1.transaction.Transaction`]):
An existing transaction that this query will run in.

If a ``transaction`` is used and it already has write operations
added, this method cannot be used (i.e. read-after-write is not
allowed).

Returns:
list: The documents in this collection that match the query.
"""
query = query_mod.Query(self)
return query.get(transaction=transaction)

def stream(
self, transaction=None
Expand Down