This is an automated email from the ASF dual-hosted git repository.

shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new fcf27fcf86c Fix Datastore rollback system test transaction (#73194)
fcf27fcf86c is described below

commit fcf27fcf86c193f9af9fd834ee0fa6d807e888f4
Author: Ulada Zakharava <[email protected]>
AuthorDate: Wed Sep 16 07:14:56 2026 +0200

    Fix Datastore rollback system test transaction (#73194)
---
 .../google/docs/operators/cloud/datastore.rst      | 42 ++++++++++++-------
 .../cloud/datastore/example_datastore_commit.py    | 16 -------
 .../cloud/datastore/example_datastore_query.py     | 14 +------
 .../cloud/datastore/example_datastore_rollback.py  | 49 ++++++++++++++++++----
 4 files changed, 68 insertions(+), 53 deletions(-)

diff --git a/providers/google/docs/operators/cloud/datastore.rst 
b/providers/google/docs/operators/cloud/datastore.rst
index a3856ea587a..d900d63d37b 100644
--- a/providers/google/docs/operators/cloud/datastore.rst
+++ b/providers/google/docs/operators/cloud/datastore.rst
@@ -88,19 +88,24 @@ Begin transaction
 To begin a new transaction use
 
:class:`~airflow.providers.google.cloud.operators.datastore.CloudDatastoreBeginTransactionOperator`
 
-.. exampleinclude:: 
/../../google/tests/system/google/cloud/datastore/example_datastore_commit.py
-    :language: python
-    :dedent: 4
-    :start-after: [START how_to_begin_transaction]
-    :end-before: [END how_to_begin_transaction]
+.. code-block:: python
 
-An example of a transaction options required by the operator:
+    TRANSACTION_OPTIONS = {"readWrite": {}}
 
-.. exampleinclude:: 
/../../google/tests/system/google/cloud/datastore/example_datastore_commit.py
-    :language: python
-    :dedent: 0
-    :start-after: [START how_to_transaction_def]
-    :end-before: [END how_to_transaction_def]
+    begin_transaction = CloudDatastoreBeginTransactionOperator(
+        task_id="begin_transaction",
+        transaction_options=TRANSACTION_OPTIONS,
+        project_id=PROJECT_ID,
+    )
+
+.. warning::
+
+    Datastore transactions expire after 270 seconds or after 60 seconds of 
inactivity.
+    Airflow does not guarantee that a downstream task will start before those 
limits.
+    Do not pass a transaction handle between tasks. Run all operations 
belonging to a
+    transaction within one task using
+    :class:`~airflow.providers.google.cloud.hooks.datastore.DatastoreHook` or 
a Datastore client.
+    See `Datastore transaction limits 
<https://cloud.google.com/datastore/docs/concepts/transactions>`__.
 
 .. _howto/operator:CloudDatastoreCommitOperator:
 
@@ -154,11 +159,16 @@ Roll back transaction
 To roll back a transaction
 use 
:class:`~airflow.providers.google.cloud.operators.datastore.CloudDatastoreRollbackOperator`
 
-.. exampleinclude:: 
/../../google/tests/system/google/cloud/datastore/example_datastore_rollback.py
-    :language: python
-    :dedent: 4
-    :start-after: [START how_to_rollback_transaction]
-    :end-before: [END how_to_rollback_transaction]
+This low-level operator requires a transaction handle that is still active 
when task execution begins.
+Given such a handle, configure the operator as follows:
+
+.. code-block:: python
+
+    rollback_transaction = CloudDatastoreRollbackOperator(
+        task_id="rollback_transaction",
+        transaction=TRANSACTION_ID,
+        project_id=PROJECT_ID,
+    )
 
 .. _howto/operator:CloudDatastoreGetOperationOperator:
 
diff --git 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_commit.py
 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_commit.py
index ae39f1fe10f..b138caf2d07 100644
--- 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_commit.py
+++ 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_commit.py
@@ -23,13 +23,11 @@ from __future__ import annotations
 
 import os
 from datetime import datetime
-from typing import Any
 
 from airflow.models.baseoperator import chain
 from airflow.models.dag import DAG
 from airflow.providers.google.cloud.operators.datastore import (
     CloudDatastoreAllocateIdsOperator,
-    CloudDatastoreBeginTransactionOperator,
     CloudDatastoreCommitOperator,
     CloudDatastoreDeleteOperationOperator,
     CloudDatastoreExportEntitiesOperator,
@@ -60,11 +58,6 @@ KEYS = [
 ]
 # [END how_to_keys_def]
 
-# [START how_to_transaction_def]
-TRANSACTION_OPTIONS: dict[str, Any] = {"readWrite": {}}
-# [END how_to_transaction_def]
-
-
 with DAG(
     DAG_ID,
     schedule="@once",
@@ -82,14 +75,6 @@ with DAG(
     )
     # [END how_to_allocate_ids]
 
-    # [START how_to_begin_transaction]
-    begin_transaction_commit = CloudDatastoreBeginTransactionOperator(
-        task_id="begin_transaction_commit",
-        transaction_options=TRANSACTION_OPTIONS,
-        project_id=PROJECT_ID,
-    )
-    # [END how_to_begin_transaction]
-
     # [START how_to_commit_def]
     COMMIT_BODY = {
         "mode": "TRANSACTIONAL",
@@ -156,7 +141,6 @@ with DAG(
         create_bucket,
         # TEST BODY
         allocate_ids,
-        begin_transaction_commit,
         commit_task,
         export_task,
         import_task,
diff --git 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_query.py
 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_query.py
index 7a7f9b7c25a..d69bc90209b 100644
--- 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_query.py
+++ 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_query.py
@@ -23,12 +23,10 @@ from __future__ import annotations
 
 import os
 from datetime import datetime
-from typing import Any
 
 from airflow.models.dag import DAG
 from airflow.providers.google.cloud.operators.datastore import (
     CloudDatastoreAllocateIdsOperator,
-    CloudDatastoreBeginTransactionOperator,
     CloudDatastoreRunQueryOperator,
 )
 
@@ -46,9 +44,6 @@ KEYS = [
     }
 ]
 
-TRANSACTION_OPTIONS: dict[str, Any] = {"readWrite": {}}
-
-
 with DAG(
     DAG_ID,
     schedule="@once",
@@ -60,16 +55,9 @@ with DAG(
         task_id="allocate_ids", partial_keys=KEYS, project_id=PROJECT_ID
     )
 
-    begin_transaction_query = CloudDatastoreBeginTransactionOperator(
-        task_id="begin_transaction_query",
-        transaction_options=TRANSACTION_OPTIONS,
-        project_id=PROJECT_ID,
-    )
-
     # [START how_to_query_def]
     QUERY = {
         "partitionId": {"projectId": PROJECT_ID, "namespaceId": "query"},
-        "readOptions": {"transaction": begin_transaction_query.output},
         "query": {},
     }
     # [END how_to_query_def]
@@ -78,7 +66,7 @@ with DAG(
     run_query = CloudDatastoreRunQueryOperator(task_id="run_query", 
body=QUERY, project_id=PROJECT_ID)
     # [END how_to_run_query]
 
-    allocate_ids >> begin_transaction_query >> run_query
+    allocate_ids >> run_query
 
     from tests_common.test_utils.watcher import watcher
 
diff --git 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_rollback.py
 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_rollback.py
index 00dfecba9f4..85b81ab2f4c 100644
--- 
a/providers/google/tests/system/google/cloud/datastore/example_datastore_rollback.py
+++ 
b/providers/google/tests/system/google/cloud/datastore/example_datastore_rollback.py
@@ -16,16 +16,17 @@
 # specific language governing permissions and limitations
 # under the License.
 """
-Airflow System Test DAG that verifies Datastore rollback operators.
+Airflow System Test DAG that verifies Datastore transaction operators.
 """
 
 from __future__ import annotations
 
 import os
 from datetime import datetime
-from typing import Any
+from typing import TYPE_CHECKING, Any, cast
 
 from airflow.models.dag import DAG
+from airflow.providers.google.cloud.hooks.datastore import DatastoreHook
 from airflow.providers.google.cloud.operators.datastore import (
     CloudDatastoreBeginTransactionOperator,
     CloudDatastoreRollbackOperator,
@@ -33,6 +34,9 @@ from airflow.providers.google.cloud.operators.datastore 
import (
 
 from system.google import DEFAULT_GCP_SYSTEM_TEST_PROJECT_ID
 
+if TYPE_CHECKING:
+    from airflow.sdk import Context
+
 ENV_ID = os.environ.get("SYSTEM_TESTS_ENV_ID", "default")
 PROJECT_ID = os.environ.get("SYSTEM_TESTS_GCP_PROJECT") or 
DEFAULT_GCP_SYSTEM_TEST_PROJECT_ID
 
@@ -41,6 +45,33 @@ DAG_ID = "datastore_rollback"
 TRANSACTION_OPTIONS: dict[str, Any] = {"readWrite": {}}
 
 
+def begin_transaction_for_rollback(context: Context) -> None:
+    task = cast("CloudDatastoreRollbackOperator", context["task"])
+
+    hook = DatastoreHook(
+        gcp_conn_id=task.gcp_conn_id,
+        impersonation_chain=task.impersonation_chain,
+    )
+
+    task.transaction = hook.begin_transaction(
+        transaction_options=TRANSACTION_OPTIONS,
+        project_id=task.project_id,
+    )
+
+    task.log.info("Created Datastore transaction immediately before rollback")
+
+
+def rollback_after_begin(context: Context, transaction: str) -> None:
+    task = cast("CloudDatastoreBeginTransactionOperator", context["task"])
+
+    hook = DatastoreHook(
+        gcp_conn_id=task.gcp_conn_id,
+        impersonation_chain=task.impersonation_chain,
+    )
+
+    hook.rollback(transaction=transaction, project_id=task.project_id)
+
+
 with DAG(
     DAG_ID,
     schedule="@once",
@@ -48,20 +79,22 @@ with DAG(
     catchup=False,
     tags=["datastore", "example"],
 ) as dag:
-    begin_transaction_to_rollback = CloudDatastoreBeginTransactionOperator(
-        task_id="begin_transaction_to_rollback",
+    begin_transaction = CloudDatastoreBeginTransactionOperator(
+        task_id="begin_transaction",
         transaction_options=TRANSACTION_OPTIONS,
         project_id=PROJECT_ID,
+        post_execute=rollback_after_begin,
     )
 
-    # [START how_to_rollback_transaction]
     rollback_transaction = CloudDatastoreRollbackOperator(
         task_id="rollback_transaction",
-        transaction=begin_transaction_to_rollback.output,
+        transaction="defined_in_begin_transaction_for_rollback",
+        pre_execute=begin_transaction_for_rollback,
+        project_id=PROJECT_ID,
     )
-    # [END how_to_rollback_transaction]
 
-    begin_transaction_to_rollback >> rollback_transaction
+    # Each task owns its transaction so scheduler delays cannot expire a 
handle between tasks.
+    begin_transaction >> rollback_transaction
 
     from tests_common.test_utils.watcher import watcher
 

Reply via email to