Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -429,10 +429,7 @@ def _merge_request_options(request_options, client_context):
if request_options is None and client_context is None:
return None

if request_options is None:
request_options = RequestOptions()
elif isinstance(request_options, dict):
request_options = RequestOptions(request_options)
request_options = RequestOptions(request_options)

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.

medium

Passing None explicitly to the RequestOptions constructor (i.e., RequestOptions(None)) when request_options is None and client_context is not None can be fragile. Depending on the underlying protobuf implementation (e.g., upb, cpp, or pure Python) and the version of proto-plus, passing None as a positional argument might not be consistently supported or could lead to unexpected behavior. It is safer and more explicit to instantiate an empty RequestOptions using RequestOptions() when request_options is None.

Suggested change
request_options = RequestOptions(request_options)
request_options = RequestOptions(request_options) if request_options is not None else RequestOptions()


if client_context:
request_options.client_context = _merge_client_context(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,46 @@ async def test_read_w_request_options_dict(self):
call_args = api.streaming_read.call_args
self.assertEqual(call_args.kwargs["request"].request_options.priority, 1)

async def test_read_and_execute_sql_preserve_request_options(self):
from google.cloud.spanner_v1.keyset import KeySet
from google.cloud.spanner_v1.types import RequestOptions

for method, args, rpc in (
("read", (TABLE_NAME, COLUMNS, KeySet(all_=True)), "streaming_read"),
("execute_sql", (SQL_QUERY,), "execute_streaming_sql"),
):
for options in (
{"request_tag": "r-tag", "transaction_tag": "caller-tx-tag"},
RequestOptions(request_tag="r-tag", transaction_tag="caller-tx-tag"),
):
with self.subTest(method=method, options=options):
original = RequestOptions(options)
for read_only, transaction_tag, expected_tag in (
(True, None, ""),
(False, None, "caller-tx-tag"),
(False, "snapshot-tx-tag", "snapshot-tx-tag"),
):
database = _Database()
snapshot = self._make_snapshot(_Session(database))
snapshot._transaction_id = TXN_ID
snapshot._read_only = read_only
snapshot.transaction_tag = transaction_tag
api_method = getattr(database.spanner_api, rpc)
api_method.return_value = _MockIterator(PartialResultSet())

results = await getattr(snapshot, method)(
*args, request_options=options
)
async for _ in results:
pass

sent_options = api_method.call_args.kwargs[
"request"
].request_options
self.assertEqual(sent_options.request_tag, "r-tag")
self.assertEqual(sent_options.transaction_tag, expected_tag)
self.assertEqual(RequestOptions(options), original)

async def test_read_w_directed_read_options(self):
from google.cloud.spanner_v1.types import DirectedReadOptions

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ def test__merge_client_context_both_set(self):
self.assertEqual(result.secure_context["c"], "C")

def test__merge_request_options_with_client_context(self):
request_options = RequestOptions(priority=RequestOptions.Priority.PRIORITY_LOW)
request_options = RequestOptions(
priority=RequestOptions.Priority.PRIORITY_LOW,
client_context={"secure_context": {"original": "value"}},
)
original = RequestOptions(request_options)
client_context = RequestOptions.ClientContext(
secure_context={"a": struct_pb2.Value(string_value="A")}
)
Expand All @@ -73,6 +77,26 @@ def test__merge_request_options_with_client_context(self):

self.assertEqual(result.priority, RequestOptions.Priority.PRIORITY_LOW)
self.assertEqual(result.client_context.secure_context["a"], "A")
self.assertEqual(result.client_context.secure_context["original"], "value")
self.assertEqual(request_options, original)

result.client_context.secure_context["original"] = "changed"
self.assertEqual(request_options, original)

def test__merge_request_options_without_client_context_copies_options(self):
for options in (
{"request_tag": "tag", "transaction_tag": "transaction"},
RequestOptions(request_tag="tag", transaction_tag="transaction"),
):
with self.subTest(options=options):
original = RequestOptions(options)
result = _merge_request_options(options, None)
self.assertEqual(result, original)
result.transaction_tag = "changed"
self.assertEqual(RequestOptions(options), original)

def test__merge_request_options_both_none(self):
self.assertIsNone(_merge_request_options(None, None))

def test_client_init_with_client_context(self):
from google.cloud.spanner_v1.client import Client
Expand Down
50 changes: 38 additions & 12 deletions packages/google-cloud-spanner/tests/unit/test_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -1866,12 +1866,25 @@ def test_read_w_transaction_tag_success(self, mock_region):
return_value="global",
)
def test_read_w_request_and_transaction_tag_success(self, mock_region):
request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"}
self._execute_read(
multi_use=False,
request_options=request_options,
expected_request_options=RequestOptions(request_tag="tag-1"),
)
for request_options in (
{"request_tag": "tag-1", "transaction_tag": "tag-1-1"},
RequestOptions(request_tag="tag-1", transaction_tag="tag-1-1"),
):
with self.subTest(request_options=request_options):
original = RequestOptions(request_options)
self._execute_read(
multi_use=False,
request_options=request_options,
expected_request_options=RequestOptions(request_tag="tag-1"),
)
self.assertEqual(RequestOptions(request_options), original)
self._execute_read(
multi_use=False,
read_only=False,
request_options=request_options,
expected_request_options=original,
)
self.assertEqual(RequestOptions(request_options), original)

@mock.patch(
"google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region",
Expand Down Expand Up @@ -2372,12 +2385,25 @@ def test_execute_sql_w_transaction_tag_success(self, mock_region):
return_value="global",
)
def test_execute_sql_w_request_and_transaction_tag_success(self, mock_region):
request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"}
self._execute_sql_helper(
multi_use=False,
request_options=request_options,
expected_request_options=RequestOptions(request_tag="tag-1"),
)
for request_options in (
{"request_tag": "tag-1", "transaction_tag": "tag-1-1"},
RequestOptions(request_tag="tag-1", transaction_tag="tag-1-1"),
):
with self.subTest(request_options=request_options):
original = RequestOptions(request_options)
self._execute_sql_helper(
multi_use=False,
request_options=request_options,
expected_request_options=RequestOptions(request_tag="tag-1"),
)
self.assertEqual(RequestOptions(request_options), original)
self._execute_sql_helper(
multi_use=False,
read_only=False,
request_options=request_options,
expected_request_options=original,
)
self.assertEqual(RequestOptions(request_options), original)

@mock.patch(
"google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region",
Expand Down
Loading