Skip to content
This repository was archived by the owner on Mar 2, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ffae9bc
feat: Added read_time as a parameter to various calls (synchronous/base
gkevinzheng Jan 30, 2025
0bc5302
Added system test for DocumentReference.collections
gkevinzheng Jan 30, 2025
e9eefd6
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jan 30, 2025
a1d8030
linting
gkevinzheng Jan 30, 2025
d551aa2
Conditionally adding read_time to kwargs for cleaner test separation
gkevinzheng Jan 30, 2025
3151b8b
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Jan 30, 2025
4e6f95e
Conditionally adding read_time to kwargs in base_aggregation.py
gkevinzheng Jan 31, 2025
2ed7eb5
More conditionally adding read_time to kwargs
gkevinzheng Feb 3, 2025
402fe49
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Feb 3, 2025
39ae878
Added tests for collections.get/stream
gkevinzheng Feb 3, 2025
d85f2f0
linting
gkevinzheng Feb 3, 2025
a8ced8f
Addressed review feedback
gkevinzheng Feb 14, 2025
f19995e
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Feb 14, 2025
7f99200
linting
gkevinzheng Feb 14, 2025
6f5b1c4
chore: update protoplus for python 3.13 (#1009)
daniel-sanche Jan 16, 2025
0652cbc
fix: bump default deadline on CreateDatabase and RestoreDatabase to 2…
gcf-owl-bot[bot] Jan 21, 2025
fbd6ab2
fix: client-side path validation for batch.update (#1021)
daniel-sanche Feb 24, 2025
33c134b
fix: Watch thread deadlock on exit (#1014)
daniel-sanche Feb 25, 2025
4d24a95
chore(python): fix typo in README (#1015)
gcf-owl-bot[bot] Feb 25, 2025
029fcfb
chore(python): conditionally load credentials in .kokoro/build.sh (#1…
gcf-owl-bot[bot] Feb 25, 2025
e6bc31d
chore: add logging section to readme (#1018)
gcf-owl-bot[bot] Feb 26, 2025
d8ae170
chore: pull up gapic updates (#1016)
gcf-owl-bot[bot] Feb 26, 2025
1fcb218
chore(main): release 2.20.1 (#1011)
release-please[bot] Feb 26, 2025
1a08c4b
chore: Update gapic-generator-python to v1.23.2 (#1024)
gcf-owl-bot[bot] Mar 2, 2025
f71aba8
chore: remove unused files (#1027)
parthea Mar 10, 2025
17668b7
fix: allow Protobuf 6.x (#1028)
parthea Mar 17, 2025
79a4a88
chore: Update gapic-generator-python to 1.23.6 (#1032)
gcf-owl-bot[bot] Mar 19, 2025
4061cf7
fix: remove setup.cfg configuration for creating universal wheels (#1…
parthea Mar 22, 2025
5957feb
chore(docs): add BulkWriter to docs (#1033)
daniel-sanche Apr 7, 2025
e5e416a
chore(python): fix incorrect import statement in README (#1034)
gcf-owl-bot[bot] Apr 10, 2025
79cfc5d
chore(python): remove .gitignore from templates (#1036)
gcf-owl-bot[bot] Apr 10, 2025
32e729d
chore(python): remove CONTRIBUTING.rst from templates (#1038)
gcf-owl-bot[bot] Apr 10, 2025
4c69636
chore(python): remove noxfile.py from templates (#1041)
gcf-owl-bot[bot] Apr 14, 2025
d51ac35
chore(main): release 2.20.2 (#1031)
release-please[bot] Apr 17, 2025
c0606e2
feat: Added read_time as a parameter to various calls (synchronous/base
gkevinzheng Jan 30, 2025
6b1bb42
Fixed unit tests
gkevinzheng Apr 30, 2025
e33292c
🦉 Updates from OwlBot post-processor
gcf-owl-bot[bot] Apr 30, 2025
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
26 changes: 25 additions & 1 deletion google/cloud/firestore_v1/aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"""
from __future__ import annotations

from datetime import datetime

@daniel-sanche daniel-sanche Feb 3, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: just importing datetime as a module is probably better? This import overrides the "datetime" module name with the class name, which would get a bit weird if we have to use other datetime imports in the future. And it makes some of the type annotations a bit ambiguous

But let me know what you think

from typing import TYPE_CHECKING, Any, Generator, List, Optional, Union

from google.api_core import exceptions, gapic_v1
Expand Down Expand Up @@ -56,6 +57,7 @@ def get(
timeout: float | None = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> QueryResultsList[AggregationResult]:
"""Runs the aggregation query.

Expand All @@ -78,6 +80,10 @@ def get(
(Optional[:class:`~google.cloud.firestore_v1.query_profile.ExplainOptions`]):
Options to enable query profiling for this query. When set,
explain_metrics will be available on the returned generator.
read_time (Optional[datetime]): If set, reads documents as they were at the given
time. This must be a microsecond precision timestamp within the past one hour,
or if Point-in-Time Recovery is enabled, can additionally be a whole minute
timestamp within the past 7 days.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple questions:

  • how are timezones handled? Do you expect "aware" or "naive" datetimes?
  • I just did a quick search, but it seems like datetime.datetime also has microsecond precision. Do we need to mention the precision here?


Returns:
QueryResultsList[AggregationResult]: The aggregation query results.
Expand All @@ -90,6 +96,7 @@ def get(
retry=retry,
timeout=timeout,
explain_options=explain_options,
read_time=read_time,
)
result_list = list(result)

Expand All @@ -100,13 +107,16 @@ def get(

return QueryResultsList(result_list, explain_options, explain_metrics)

def _get_stream_iterator(self, transaction, retry, timeout, explain_options=None):
def _get_stream_iterator(
self, transaction, retry, timeout, explain_options=None, read_time=None
):
"""Helper method for :meth:`stream`."""
request, kwargs = self._prep_stream(
transaction,
retry,
timeout,
explain_options,
read_time,
)

return self._client._firestore_api.run_aggregation_query(
Expand All @@ -132,6 +142,7 @@ def _make_stream(
retry: Union[retries.Retry, None, object] = gapic_v1.method.DEFAULT,
timeout: Optional[float] = None,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> Generator[List[AggregationResult], Any, Optional[ExplainMetrics]]:
"""Internal method for stream(). Runs the aggregation query.

Expand All @@ -155,6 +166,10 @@ def _make_stream(
(Optional[:class:`~google.cloud.firestore_v1.query_profile.ExplainOptions`]):
Options to enable query profiling for this query. When set,
explain_metrics will be available on the returned generator.
read_time (Optional[datetime]): If set, reads documents as they were at the given
time. This must be a microsecond precision timestamp within the past one hour,
or if Point-in-Time Recovery is enabled, can additionally be a whole minute
timestamp within the past 7 days.

Yields:
List[AggregationResult]:
Expand All @@ -172,6 +187,7 @@ def _make_stream(
retry,
timeout,
explain_options,
read_time,
)
while True:
try:
Expand All @@ -182,6 +198,8 @@ def _make_stream(
transaction,
retry,
timeout,
explain_options,
read_time,
)
continue
else:
Expand All @@ -206,6 +224,7 @@ def stream(
timeout: Optional[float] = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> StreamGenerator[List[AggregationResult]]:
"""Runs the aggregation query.

Expand All @@ -229,6 +248,10 @@ def stream(
(Optional[:class:`~google.cloud.firestore_v1.query_profile.ExplainOptions`]):
Options to enable query profiling for this query. When set,
explain_metrics will be available on the returned generator.
read_time (Optional[datetime]): If set, reads documents as they were at the given
time. This must be a microsecond precision timestamp within the past one hour,
or if Point-in-Time Recovery is enabled, can additionally be a whole minute
timestamp within the past 7 days.

Returns:
`StreamGenerator[List[AggregationResult]]`:
Expand All @@ -239,5 +262,6 @@ def stream(
retry=retry,
timeout=timeout,
explain_options=explain_options,
read_time=read_time,
)
return StreamGenerator(inner_generator, explain_options)
14 changes: 14 additions & 0 deletions google/cloud/firestore_v1/base_aggregation.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import abc
from abc import ABC
from datetime import datetime
from typing import TYPE_CHECKING, Any, Coroutine, List, Optional, Tuple, Union

from google.api_core import gapic_v1
Expand Down Expand Up @@ -205,6 +206,7 @@ def _prep_stream(
retry: Union[retries.Retry, retries.AsyncRetry, None, object] = None,
timeout: float | None = None,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> Tuple[dict, dict]:
parent_path, expected_prefix = self._collection_ref._parent_info()
request = {
Expand All @@ -214,6 +216,8 @@ def _prep_stream(
}
if explain_options:
request["explain_options"] = explain_options._to_dict()
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, kwargs
Expand All @@ -228,6 +232,7 @@ def get(
timeout: float | None = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> (
QueryResultsList[AggregationResult]
| Coroutine[Any, Any, List[List[AggregationResult]]]
Expand All @@ -253,6 +258,10 @@ def get(
(Optional[:class:`~google.cloud.firestore_v1.query_profile.ExplainOptions`]):
Options to enable query profiling for this query. When set,
explain_metrics will be available on the returned generator.
read_time (Optional[datetime]): If set, reads documents as they were at the given
time. This must be a microsecond precision timestamp within the past one hour,
or if Point-in-Time Recovery is enabled, can additionally be a whole minute
timestamp within the past 7 days.

Returns:
(QueryResultsList[List[AggregationResult]] | Coroutine[Any, Any, List[List[AggregationResult]]]):
Expand All @@ -270,6 +279,7 @@ def stream(
timeout: Optional[float] = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> (
StreamGenerator[List[AggregationResult]]
| AsyncStreamGenerator[List[AggregationResult]]
Expand All @@ -291,6 +301,10 @@ def stream(
(Optional[:class:`~google.cloud.firestore_v1.query_profile.ExplainOptions`]):
Options to enable query profiling for this query. When set,
explain_metrics will be available on the returned generator.
read_time (Optional[datetime]): If set, reads documents as they were at the given
time. This must be a microsecond precision timestamp within the past one hour,
or if Point-in-Time Recovery is enabled, can additionally be a whole minute
timestamp within the past 7 days.

Returns:
StreamGenerator[List[AggregationResult]] | AsyncStreamGenerator[List[AggregationResult]]:
Expand Down
15 changes: 14 additions & 1 deletion google/cloud/firestore_v1/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from __future__ import annotations

import os
from datetime import datetime
from typing import (
Any,
AsyncGenerator,
Expand Down Expand Up @@ -437,6 +438,7 @@ def _prep_get_all(
transaction: BaseTransaction | None = None,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: float | None = None,
read_time: datetime | None = None,
) -> Tuple[dict, dict, dict]:
"""Shared setup for async/sync :meth:`get_all`."""
document_paths, reference_map = _reference_info(references)
Expand All @@ -447,6 +449,8 @@ def _prep_get_all(
"mask": mask,
"transaction": _helpers.get_transaction_id(transaction),
}
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, reference_map, kwargs
Expand All @@ -458,6 +462,8 @@ def get_all(
transaction=None,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: float | None = None,
*,
read_time: datetime | None = None,
) -> Union[
AsyncGenerator[DocumentSnapshot, Any], Generator[DocumentSnapshot, Any, Any]
]:
Expand All @@ -467,9 +473,14 @@ def _prep_collections(
self,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: float | None = None,
read_time: datetime | None = None,
) -> Tuple[dict, dict]:
"""Shared setup for async/sync :meth:`collections`."""
request = {"parent": "{}/documents".format(self._database_string)}
request = {
"parent": "{}/documents".format(self._database_string),
}
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, kwargs
Expand All @@ -478,6 +489,8 @@ def collections(
self,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: float | None = None,
*,
read_time: datetime | None = None,
):
raise NotImplementedError

Expand Down
9 changes: 9 additions & 0 deletions google/cloud/firestore_v1/base_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from __future__ import annotations

import random

from datetime import datetime
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -202,6 +204,7 @@ def _prep_list_documents(
page_size: Optional[int] = None,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: Optional[float] = None,
read_time: Optional[datetime] = None,
) -> Tuple[dict, dict]:
"""Shared setup for async / sync :method:`list_documents`"""
parent, _ = self._parent_info()
Expand All @@ -215,6 +218,8 @@ def _prep_list_documents(
# to include no fields
"mask": {"field_paths": None},
}
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, kwargs
Expand All @@ -224,6 +229,8 @@ def list_documents(
page_size: Optional[int] = None,
retry: retries.Retry | retries.AsyncRetry | object | None = None,
timeout: Optional[float] = None,
*,
read_time: Optional[datetime] = None,
) -> Union[
Generator[DocumentReference, Any, Any], AsyncGenerator[DocumentReference, Any]
]:
Expand Down Expand Up @@ -497,6 +504,7 @@ def get(
timeout: Optional[float] = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> (
QueryResultsList[DocumentSnapshot]
| Coroutine[Any, Any, QueryResultsList[DocumentSnapshot]]
Expand All @@ -510,6 +518,7 @@ def stream(
timeout: Optional[float] = None,
*,
explain_options: Optional[ExplainOptions] = None,
read_time: Optional[datetime] = None,
) -> StreamGenerator[DocumentSnapshot] | AsyncIterator[DocumentSnapshot]:
raise NotImplementedError

Expand Down
16 changes: 15 additions & 1 deletion google/cloud/firestore_v1/base_document.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Union,
Awaitable,
)
from datetime import datetime

from google.api_core import retry as retries

Expand Down Expand Up @@ -290,6 +291,7 @@ def _prep_batch_get(
transaction=None,
retry: retries.Retry | retries.AsyncRetry | None | object = None,
timeout: float | None = None,
read_time: datetime | None = None,
) -> Tuple[dict, dict]:
"""Shared setup for async/sync :meth:`get`."""
if isinstance(field_paths, str):
Expand All @@ -306,6 +308,8 @@ def _prep_batch_get(
"mask": mask,
"transaction": _helpers.get_transaction_id(transaction),
}
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, kwargs
Expand All @@ -316,6 +320,8 @@ def get(
transaction=None,
retry: retries.Retry | retries.AsyncRetry | None | object = None,
timeout: float | None = None,
*,
read_time: Optional[datetime] = None,
) -> "DocumentSnapshot" | Awaitable["DocumentSnapshot"]:
raise NotImplementedError

Expand All @@ -324,9 +330,15 @@ def _prep_collections(
page_size: int | None = None,
retry: retries.Retry | retries.AsyncRetry | None | object = None,
timeout: float | None = None,
read_time: datetime | None = None,
) -> Tuple[dict, dict]:
"""Shared setup for async/sync :meth:`collections`."""
request = {"parent": self._document_path, "page_size": page_size}
request = {
"parent": self._document_path,
"page_size": page_size,
}
if read_time is not None:
request["read_time"] = read_time
kwargs = _helpers.make_retry_timeout_kwargs(retry, timeout)

return request, kwargs
Expand All @@ -336,6 +348,8 @@ def collections(
page_size: int | None = None,
retry: retries.Retry | retries.AsyncRetry | None | object = None,
timeout: float | None = None,
*,
read_time: Optional[datetime] = None,
):
raise NotImplementedError

Expand Down
Loading