From ca2340513a5ef1cd82d7af0bd8f066f16367887c Mon Sep 17 00:00:00 2001 From: Jillian Kozyra Date: Fri, 9 May 2025 21:59:14 -0700 Subject: [PATCH 1/8] fix: update the async transactional types to not require extra awaits --- .../cloud/firestore_v1/async_transaction.py | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 038710929..15327dad5 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -15,7 +15,18 @@ """Helpers for applying Google Cloud Firestore changes in a transaction.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any, AsyncGenerator, Callable, Coroutine, Optional +from typing import ( + TYPE_CHECKING, + Any, + AsyncGenerator, + Awaitable, + Callable, + Coroutine, + Optional, + TypeVar, + ParamSpec, + Concatenate, +) from google.api_core import exceptions, gapic_v1 from google.api_core import retry_async as retries @@ -41,6 +52,10 @@ from google.cloud.firestore_v1.query_profile import ExplainOptions +T = TypeVar("T") +P = ParamSpec("P") + + class AsyncTransaction(async_batch.AsyncWriteBatch, BaseTransaction): """Accumulate read-and-write operations to be sent in a transaction. @@ -236,11 +251,13 @@ class _AsyncTransactional(_BaseTransactional): A coroutine that should be run (and retried) in a transaction. """ - def __init__(self, to_wrap) -> None: + def __init__( + self, to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] + ) -> None: super(_AsyncTransactional, self).__init__(to_wrap) async def _pre_commit( - self, transaction: AsyncTransaction, *args, **kwargs + self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs ) -> Coroutine: """Begin transaction and call the wrapped coroutine. @@ -254,7 +271,7 @@ async def _pre_commit( along to the wrapped coroutine. Returns: - Any: result of the wrapped coroutine. + T: result of the wrapped coroutine. Raises: Exception: Any failure caused by ``to_wrap``. @@ -269,12 +286,14 @@ async def _pre_commit( self.retry_id = self.current_id return await self.to_wrap(transaction, *args, **kwargs) - async def __call__(self, transaction, *args, **kwargs): + async def __call__( + self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs + ) -> T: """Execute the wrapped callable within a transaction. Args: transaction - (:class:`~google.cloud.firestore_v1.transaction.Transaction`): + (:class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`): A transaction to execute the callable within. args (Tuple[Any, ...]): The extra positional arguments to pass along to the wrapped callable. @@ -282,7 +301,7 @@ async def __call__(self, transaction, *args, **kwargs): along to the wrapped callable. Returns: - Any: The result of the wrapped callable. + T: The result of the wrapped callable. Raises: ValueError: If the transaction does not succeed in @@ -321,13 +340,13 @@ async def __call__(self, transaction, *args, **kwargs): def async_transactional( - to_wrap: Callable[[AsyncTransaction], Any] -) -> _AsyncTransactional: + to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] +) -> Callable[Concatenate[AsyncTransaction, P], Awaitable[T]]: """Decorate a callable so that it runs in a transaction. Args: to_wrap - (Callable[[:class:`~google.cloud.firestore_v1.transaction.Transaction`, ...], Any]): + (Callable[[:class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`, ...], Any]): A callable that should be run (and retried) in a transaction. Returns: From c0e1446d57e51924fe28b5f93647dc869404f1c2 Mon Sep 17 00:00:00 2001 From: Jillian Kozyra Date: Wed, 21 May 2025 20:51:45 -0700 Subject: [PATCH 2/8] add typing extensions --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 2a47080a1..290eec0e4 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ "proto-plus >= 1.22.2, <2.0.0; python_version>='3.11'", "proto-plus >= 1.25.0, <2.0.0; python_version>='3.13'", "protobuf>=3.20.2,<7.0.0dev,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", + "typing_extensions>=4.13, <5; python_version<'3.10'", ] extras = {} From a630701e28c84d58d558881f393d148274a574a6 Mon Sep 17 00:00:00 2001 From: Jillian Kozyra Date: Wed, 21 May 2025 21:10:32 -0700 Subject: [PATCH 3/8] python <3.10 compat --- google/cloud/firestore_v1/async_transaction.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 15327dad5..416ffaf1e 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -24,10 +24,14 @@ Coroutine, Optional, TypeVar, - ParamSpec, Concatenate, ) +try: + from typing import ParamSpec +except ImportError: + from typing_extensions import ParamSpec + from google.api_core import exceptions, gapic_v1 from google.api_core import retry_async as retries From 576a8a8e85d68eac15fb94402422912b4be6ede9 Mon Sep 17 00:00:00 2001 From: Jillian Kozyra Date: Wed, 28 May 2025 22:04:34 -0700 Subject: [PATCH 4/8] use a protocol --- .../cloud/firestore_v1/async_transaction.py | 23 ++++++++----------- setup.py | 1 - 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 416ffaf1e..b38cdfb71 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -24,14 +24,9 @@ Coroutine, Optional, TypeVar, - Concatenate, + Protocol, ) -try: - from typing import ParamSpec -except ImportError: - from typing_extensions import ParamSpec - from google.api_core import exceptions, gapic_v1 from google.api_core import retry_async as retries @@ -56,8 +51,7 @@ from google.cloud.firestore_v1.query_profile import ExplainOptions -T = TypeVar("T") -P = ParamSpec("P") +T = TypeVar("T", bound=Callable[..., Any]) class AsyncTransaction(async_batch.AsyncWriteBatch, BaseTransaction): @@ -256,12 +250,12 @@ class _AsyncTransactional(_BaseTransactional): """ def __init__( - self, to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] + self, to_wrap: Callable[..., Awaitable[T]] ) -> None: super(_AsyncTransactional, self).__init__(to_wrap) async def _pre_commit( - self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs + self, transaction: AsyncTransaction, *args: Any, **kwargs: Any ) -> Coroutine: """Begin transaction and call the wrapped coroutine. @@ -291,7 +285,7 @@ async def _pre_commit( return await self.to_wrap(transaction, *args, **kwargs) async def __call__( - self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs + self, transaction: AsyncTransaction, *args: Any, **kwargs: Any ) -> T: """Execute the wrapped callable within a transaction. @@ -343,9 +337,12 @@ async def __call__( raise +class WithAsyncTransaction(Protocol[T]): + def __call__(self, transaction: AsyncTransaction, *args: Any, **kwargs: Any) -> Awaitable[T]: ... + def async_transactional( - to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] -) -> Callable[Concatenate[AsyncTransaction, P], Awaitable[T]]: + to_wrap: Callable[..., Awaitable[T]] +) -> WithAsyncTransaction[T]: """Decorate a callable so that it runs in a transaction. Args: diff --git a/setup.py b/setup.py index 290eec0e4..2a47080a1 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,6 @@ "proto-plus >= 1.22.2, <2.0.0; python_version>='3.11'", "proto-plus >= 1.25.0, <2.0.0; python_version>='3.13'", "protobuf>=3.20.2,<7.0.0dev,!=3.20.0,!=3.20.1,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5", - "typing_extensions>=4.13, <5; python_version<'3.10'", ] extras = {} From b4265792ae56d4ddb3682262b96a500997e3daf1 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 10 Jun 2025 16:56:50 -0700 Subject: [PATCH 5/8] moved back to ParamSpec implementation --- .../cloud/firestore_v1/async_transaction.py | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 73defbade..236baf71c 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -21,10 +21,7 @@ AsyncGenerator, Awaitable, Callable, - Coroutine, Optional, - TypeVar, - Protocol, ) from google.api_core import exceptions, gapic_v1 @@ -47,13 +44,14 @@ # Types needed only for Type Hints if TYPE_CHECKING: # pragma: NO COVER import datetime + from typing_extensions import TypeVar, ParamSpec, Concatenate from google.cloud.firestore_v1.async_stream_generator import AsyncStreamGenerator from google.cloud.firestore_v1.base_document import DocumentSnapshot from google.cloud.firestore_v1.query_profile import ExplainOptions - -T = TypeVar("T", bound=Callable[..., Any]) + T = TypeVar("T") + P = ParamSpec("P") class AsyncTransaction(async_batch.AsyncWriteBatch, BaseTransaction): @@ -267,13 +265,13 @@ class _AsyncTransactional(_BaseTransactional): """ def __init__( - self, to_wrap: Callable[..., Awaitable[T]] + self, to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] ) -> None: super(_AsyncTransactional, self).__init__(to_wrap) async def _pre_commit( - self, transaction: AsyncTransaction, *args: Any, **kwargs: Any - ) -> Coroutine: + self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs + ) -> T: """Begin transaction and call the wrapped coroutine. Args: @@ -301,9 +299,7 @@ async def _pre_commit( self.retry_id = self.current_id return await self.to_wrap(transaction, *args, **kwargs) - async def __call__( - self, transaction: AsyncTransaction, *args: Any, **kwargs: Any - ) -> T: + async def __call__(self, transaction, *args: P.args, **kwargs: P.kwargs) -> T: """Execute the wrapped callable within a transaction. Args: @@ -330,7 +326,7 @@ async def __call__( try: for attempt in range(transaction._max_attempts): - result = await self._pre_commit(transaction, *args, **kwargs) + result: T = await self._pre_commit(transaction, *args, **kwargs) try: await transaction._commit() return result @@ -354,12 +350,9 @@ async def __call__( raise -class WithAsyncTransaction(Protocol[T]): - def __call__(self, transaction: AsyncTransaction, *args: Any, **kwargs: Any) -> Awaitable[T]: ... - def async_transactional( - to_wrap: Callable[..., Awaitable[T]] -) -> WithAsyncTransaction[T]: + to_wrap: Callable[Concatenate[AsyncTransaction, P], Awaitable[T]] +) -> Callable[Concatenate[AsyncTransaction, P], Awaitable[T]]: """Decorate a callable so that it runs in a transaction. Args: From a2b71c2288b6bb2bea1d2c50164f770eee406501 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 10 Jun 2025 16:57:30 -0700 Subject: [PATCH 6/8] updated mypy.ini --- mypy.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy.ini b/mypy.ini index 4505b4854..beaa679a8 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,3 +1,3 @@ [mypy] -python_version = 3.6 +python_version = 3.8 namespace_packages = True From 8f6b8c1cf3430229a58da425bf47e6fa9e410918 Mon Sep 17 00:00:00 2001 From: Daniel Sanche Date: Tue, 17 Jun 2025 14:07:01 -0700 Subject: [PATCH 7/8] fixed some types --- google/cloud/firestore_v1/async_transaction.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 236baf71c..4a6f17de6 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -299,7 +299,7 @@ async def _pre_commit( self.retry_id = self.current_id return await self.to_wrap(transaction, *args, **kwargs) - async def __call__(self, transaction, *args: P.args, **kwargs: P.kwargs) -> T: + async def __call__(self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs) -> T: """Execute the wrapped callable within a transaction. Args: @@ -357,11 +357,11 @@ def async_transactional( Args: to_wrap - (Callable[[:class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`, ...], Any]): + (Callable[[:class:`~google.cloud.firestore_v1.async_transaction.AsyncTransaction`, ...], Awaitable[Any]]): A callable that should be run (and retried) in a transaction. Returns: - Callable[[:class:`~google.cloud.firestore_v1.transaction.Transaction`, ...], Any]: + Callable[[:class:`~google.cloud.firestore_v1.transaction.Transaction`, ...], Awaitable[Any]]: the wrapped callable. """ return _AsyncTransactional(to_wrap) From d6e688904e1ebda9a72568f5c6d37a8803897b00 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 17 Jun 2025 21:09:28 +0000 Subject: [PATCH 8/8] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- google/cloud/firestore_v1/async_transaction.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/google/cloud/firestore_v1/async_transaction.py b/google/cloud/firestore_v1/async_transaction.py index 4a6f17de6..36509941e 100644 --- a/google/cloud/firestore_v1/async_transaction.py +++ b/google/cloud/firestore_v1/async_transaction.py @@ -299,7 +299,9 @@ async def _pre_commit( self.retry_id = self.current_id return await self.to_wrap(transaction, *args, **kwargs) - async def __call__(self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs) -> T: + async def __call__( + self, transaction: AsyncTransaction, *args: P.args, **kwargs: P.kwargs + ) -> T: """Execute the wrapped callable within a transaction. Args: