Skip to content

Commit c78ad04

Browse files
authored
feat: document the use of statement and transaction tags (#676)
Add a sample that shows how to use statement and transaction tags with SQLAlchemy.
1 parent 270852e commit c78ad04

File tree

3 files changed

+96
-5
lines changed

3 files changed

+96
-5
lines changed

samples/noxfile.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ def transaction(session):
6262
_sample(session)
6363

6464

65+
@nox.session()
66+
def tags(session):
67+
_sample(session)
68+
69+
6570
@nox.session()
6671
def isolation_level(session):
6772
_sample(session)

samples/tags_sample.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright 2025 Google LLC All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import uuid
16+
17+
from sqlalchemy import create_engine
18+
from sqlalchemy.orm import Session
19+
20+
from sample_helper import run_sample
21+
from model import Singer
22+
23+
24+
# Shows how to transaction tags and statement tags with Spanner and SQLAlchemy.
25+
def tags_sample():
26+
engine = create_engine(
27+
"spanner:///projects/sample-project/"
28+
"instances/sample-instance/"
29+
"databases/sample-database",
30+
echo=True,
31+
)
32+
# Set a transaction_tag in the execution options for the session to set
33+
# a transaction tag.
34+
with Session(
35+
engine.execution_options(transaction_tag="my_transaction_tag")
36+
) as session:
37+
# The transaction that is automatically started by SQLAlchemy will use the
38+
# transaction tag that is specified in the execution options.
39+
40+
# Execute a query with a request tag.
41+
singer_id = str(uuid.uuid4())
42+
singer = session.get(
43+
Singer, singer_id, execution_options={"request_tag": "my_tag_1"}
44+
)
45+
46+
# Add the singer if it was not found.
47+
if singer is None:
48+
# The session.Add(..) function does not support execution_options, but we can
49+
# set the execution_options on the connection of this session. This will be
50+
# propagated to the next statement that is executed on the connection.
51+
session.connection().execution_options(request_tag="insert_singer")
52+
singer = Singer(id=singer_id, first_name="John", last_name="Doe")
53+
session.add(singer)
54+
session.commit()
55+
56+
57+
if __name__ == "__main__":
58+
run_sample(tags_sample)

test/mockserver_tests/test_tags.py

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ def test_transaction_tag(self):
6565
from test.mockserver_tests.tags_model import Singer
6666

6767
add_singer_query_result("SELECT singers.id, singers.name\n" + "FROM singers")
68+
add_single_singer_query_result(
69+
"SELECT singers.id AS singers_id, singers.name AS singers_name\n"
70+
"FROM singers\n"
71+
"WHERE singers.id = @a0"
72+
)
6873
add_update_count("INSERT INTO singers (id, name) VALUES (@a0, @a1)", 1)
6974
engine = create_engine(
7075
"spanner:///projects/p/instances/i/databases/d",
@@ -75,26 +80,32 @@ def test_transaction_tag(self):
7580
engine.execution_options(transaction_tag="my-transaction-tag")
7681
) as session:
7782
# Execute a query and an insert statement in a read/write transaction.
83+
session.get(Singer, 1, execution_options={"request_tag": "my-tag-1"})
7884
session.scalars(
79-
select(Singer).execution_options(request_tag="my-tag-1")
85+
select(Singer).execution_options(request_tag="my-tag-2")
8086
).all()
87+
session.connection().execution_options(request_tag="insert-singer")
8188
session.add(Singer(id=1, name="Some Singer"))
8289
session.commit()
8390

8491
# Verify the requests that we got.
8592
requests = self.spanner_service.requests
86-
eq_(5, len(requests))
93+
eq_(6, len(requests))
8794
is_instance_of(requests[0], BatchCreateSessionsRequest)
8895
is_instance_of(requests[1], BeginTransactionRequest)
8996
is_instance_of(requests[2], ExecuteSqlRequest)
9097
is_instance_of(requests[3], ExecuteSqlRequest)
91-
is_instance_of(requests[4], CommitRequest)
98+
is_instance_of(requests[4], ExecuteSqlRequest)
99+
is_instance_of(requests[5], CommitRequest)
92100
for request in requests[2:]:
93101
eq_("my-transaction-tag", request.request_options.transaction_tag)
102+
eq_("my-tag-1", requests[2].request_options.request_tag)
103+
eq_("my-tag-2", requests[3].request_options.request_tag)
104+
eq_("insert-singer", requests[4].request_options.request_tag)
94105

95106

96-
def add_singer_query_result(sql: str):
97-
result = result_set.ResultSet(
107+
def empty_singer_result_set():
108+
return result_set.ResultSet(
98109
dict(
99110
metadata=result_set.ResultSetMetadata(
100111
dict(
@@ -124,6 +135,10 @@ def add_singer_query_result(sql: str):
124135
),
125136
)
126137
)
138+
139+
140+
def add_singer_query_result(sql: str):
141+
result = empty_singer_result_set()
127142
result.rows.extend(
128143
[
129144
(
@@ -137,3 +152,16 @@ def add_singer_query_result(sql: str):
137152
]
138153
)
139154
add_result(sql, result)
155+
156+
157+
def add_single_singer_query_result(sql: str):
158+
result = empty_singer_result_set()
159+
result.rows.extend(
160+
[
161+
(
162+
"1",
163+
"Jane Doe",
164+
),
165+
]
166+
)
167+
add_result(sql, result)

0 commit comments

Comments
 (0)