Skip to content

Comments

feat(spanner): add Client Context support to options#1499

Open
aseering wants to merge 10 commits intogoogleapis:mainfrom
aseering:feat/client-context-support
Open

feat(spanner): add Client Context support to options#1499
aseering wants to merge 10 commits intogoogleapis:mainfrom
aseering:feat/client-context-support

Conversation

@aseering
Copy link

Re-opening #1495 due to permissions issues.

Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly:

  • Make sure to open an issue as a bug/issue before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea
  • Ensure the tests and linter pass
  • Code coverage does not decrease (if any source code was changed)
  • Appropriate docs were updated (if necessary)

Fixes #<issue_number_goes_here> 🦕

aseering and others added 8 commits February 11, 2026 20:18
This change adds support for ClientContext in Options and ensures it is
propagated to ExecuteSql, Read, Commit, and BeginTransaction requests.
It aligns with go/spanner-client-scoped-session-state design.

ClientContext allows passing opaque, RPC-scoped side-channel information
(like application-level user context) to Spanner. This implementation
supports setting ClientContext at the Client, Database, and Request levels,
with request-level options taking precedence.

Key changes:
- Added ClientContext to types/spanner.py and exposed it.
- Updated Client.__init__ to accept a default client_context.
- Added helpers for merging ClientContext with correct precedence.
- Updated Snapshot, Transaction, Batch, and Database wrappers to propagate the context.
- Added comprehensive unit tests in tests/unit/test_client_context.py.
- Update Session.transaction to accept client_context.
- Update unit tests to support client_context propagation.
- Update mock objects in tests to match the expected attribute hierarchy.
- Clean up nested imports in test files.
Implement a double-checked locking pattern in Transaction and _SnapshotBase methods.
When multiple threads attempt to use a lazy transaction simultaneously, they race to acquire the lock.
Previously, losing threads would acquire the lock and blindly send another 'begin transaction' request,
ignoring that the winner had already initialized the transaction ID.

This change ensures that threads re-check self._transaction_id after acquiring the lock.
If the ID is present, they skip the initialization request and use the established ID.
- Fix critical security vulnerability in  where in-place modification of the base object could lead to context leakage across requests.
- Replace  with  throughout , , , and  for better robustness and subclass support.
- Simplify  construction logic in  for better readability.

Based on suggestions from Gemini Code Assist.
@aseering aseering requested review from a team as code owners February 24, 2026 18:01
@product-auto-label product-auto-label bot added the size: l Pull request size is large. label Feb 24, 2026
@product-auto-label product-auto-label bot added the api: spanner Issues related to the googleapis/python-spanner API. label Feb 24, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello @aseering, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces comprehensive support for ClientContext across the Google Cloud Spanner Python client library. This enhancement allows users to define and propagate client-specific contextual information, such as secure context parameters, throughout their Spanner interactions. The changes involve defining a new ClientContext type, implementing robust merging logic for these contexts, and integrating this functionality into the core client, database, session, batch, snapshot, and transaction classes. This provides greater flexibility and control over contextual data associated with Spanner API calls.

Highlights

  • New ClientContext Type: Introduced a new ClientContext message type in google.cloud.spanner_v1.types.spanner.py to allow attaching client-owned context, such as secure context parameters, to requests.
  • ClientContext Merging Logic: Added helper functions _merge_client_context and _merge_request_options in google/cloud/spanner_v1/_helpers.py to handle the merging of ClientContext and RequestOptions objects, ensuring higher precedence contexts overwrite base fields.
  • Client-Level ClientContext Support: The Client class constructor in google/cloud/spanner_v1/client.py now accepts an optional client_context parameter, allowing a default context to be set for all requests made by the client.
  • Propagation Across Operations: Integrated client_context support into various Spanner operations, including batch writes, transactions, snapshots, and SQL execution, by updating relevant classes (_BatchBase, MutationGroups, _SnapshotBase, Transaction) and methods (database.batch, database.mutation_groups, database.batch_snapshot, database.run_in_transaction, session.transaction, session.run_in_transaction).
  • Improved Type Checking: Replaced direct type comparisons (type(var) is dict) with isinstance(var, dict) for more robust type checking in _helpers.py.
  • Unit Test Coverage: Added a new dedicated unit test file tests/unit/test_client_context.py to thoroughly verify the functionality and propagation of ClientContext.
Changelog
  • google/cloud/spanner_v1/init.py
    • Imported ClientContext from types.spanner.
    • Added ClientContext to the module's __all__ export list.
  • google/cloud/spanner_v1/_helpers.py
    • Imported ClientContext and RequestOptions types.
    • Replaced type(combined) is dict with isinstance(combined, dict) in _merge_query_options.
    • Added _merge_client_context function to merge two ClientContext objects.
    • Added _merge_request_options function to merge ClientContext into RequestOptions.
  • google/cloud/spanner_v1/batch.py
    • Imported _merge_client_context and _merge_request_options.
    • Imported ClientContext type.
    • Added client_context parameter to _BatchBase constructor and stored it.
    • Added client_context parameter to MutationGroups constructor and stored it.
    • Modified commit method to merge client and batch client_context into request_options.
    • Modified batch_write method to merge client and mutation group client_context into request_options.
  • google/cloud/spanner_v1/client.py
    • Imported ClientContext type.
    • Added client_context parameter to the Client constructor.
    • Stored the provided client_context in self._client_context after type validation.
  • google/cloud/spanner_v1/database.py
    • Updated docstring for snapshot to mention client_context.
    • Added client_context parameter to batch method.
    • Updated docstring for batch to include client_context.
    • Passed client_context to BatchCheckout in batch method.
    • Added client_context parameter to mutation_groups method.
    • Updated docstring for mutation_groups to include client_context.
    • Passed client_context to MutationGroupsCheckout in mutation_groups method.
    • Added client_context parameter to batch_snapshot method.
    • Updated docstring for batch_snapshot to include client_context.
    • Passed client_context to BatchSnapshot in batch_snapshot method.
    • Updated docstring for run_in_transaction to include client_context.
    • Added client_context parameter to BatchCheckout constructor and stored it.
    • Passed client_context to Batch constructor in BatchCheckout.__enter__.
    • Added client_context parameter to MutationGroupsCheckout constructor and stored it.
    • Passed client_context to MutationGroups constructor in MutationGroupsCheckout.__enter__.
    • Added client_context parameter to BatchSnapshot constructor and stored it.
    • Passed client_context to Snapshot constructor in BatchSnapshot._get_snapshot.
  • google/cloud/spanner_v1/session.py
    • Added client_context parameter to transaction method.
    • Updated docstring for transaction to include client_context.
    • Passed client_context to Transaction constructor in transaction method.
    • Updated docstring for run_in_transaction to include client_context.
    • Popped client_context from kw in run_in_transaction.
    • Passed client_context to Transaction constructor in run_in_transaction loop.
  • google/cloud/spanner_v1/snapshot.py
    • Imported _merge_client_context and _merge_request_options.
    • Imported ClientContext type.
    • Added client_context parameter to _SnapshotBase constructor and stored it.
    • Modified read method to merge client and snapshot client_context into request_options.
    • Modified execute_sql method to merge client and snapshot client_context into request_options.
    • Modified _begin_transaction method to merge client and snapshot client_context into request_options.
    • Added client_context parameter to Snapshot constructor and passed it to super.
  • google/cloud/spanner_v1/transaction.py
    • Imported _merge_client_context and _merge_request_options.
    • Added client_context parameter to Transaction constructor and passed it to super.
    • Modified commit method to merge client and transaction client_context into request_options.
    • Modified execute_update method to merge client and transaction client_context into request_options.
    • Modified batch_update method to merge client and transaction client_context into request_options.
  • google/cloud/spanner_v1/types/init.py
    • Imported ClientContext from spanner.
    • Added ClientContext to the module's __all__ export list.
  • google/cloud/spanner_v1/types/spanner.py
    • Added ClientContext to the RequestOptions message.
    • Defined the new ClientContext message type with a secure_context map field.
  • tests/unit/spanner_dbapi/test_connection.py
    • Initialized self._client_context = None in _Client.__init__.
  • tests/unit/test_backup.py
    • Initialized self._client_context = None in _Client.__init__.
  • tests/unit/test_batch.py
    • Initialized self._instance._client._client_context = None in _Database.__init__ mock.
  • tests/unit/test_client_context.py
    • Added new test cases for _merge_client_context and _merge_request_options.
    • Added tests for Client initialization with client_context.
    • Added tests for client_context propagation in Snapshot.execute_sql, Transaction.commit, Transaction.execute_update, MutationGroups.batch_write, BatchSnapshot, and Database.snapshot.
    • Added a test to confirm client_context is not supported in Transaction.rollback.
  • tests/unit/test_database.py
    • Imported ExecuteSqlRequest.
    • Initialized self._client_context = None in _Client.__init__ mock.
    • Initialized instance._client._client_context = None in _Database.__init__ mock.
  • tests/unit/test_instance.py
    • Initialized self._client_context = None in _Client.__init__ mock.
  • tests/unit/test_pool.py
    • Cleaned up redundant imports.
    • Initialized self._instance._client._client_context = None in _Database mock for session creation.
  • tests/unit/test_session.py
    • Initialized database._instance._client._client_context = None in _make_database mock.
  • tests/unit/test_snapshot.py
    • Initialized self._client_context = None in _Client.__init__ mock.
  • tests/unit/test_spanner.py
    • Initialized self._client_context = None in _Client.__init__ mock.
  • tests/unit/test_transaction.py
    • Initialized self._client_context = None in _Client.__init__ mock.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively adds support for ClientContext across the Spanner client library, allowing for more granular control over request options. The changes are consistently applied through various layers of the API, from the client down to individual transactions and snapshots. The addition of a comprehensive test suite for the new functionality is also a great inclusion. I have one suggestion to improve maintainability by refactoring a duplicated block of code into a shared helper function.

Comment on lines 70 to 75
if client_context is not None:
if isinstance(client_context, dict):
client_context = ClientContext(client_context)
elif not isinstance(client_context, ClientContext):
raise TypeError("client_context must be a ClientContext or a dict")
self._client_context = client_context

Choose a reason for hiding this comment

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

medium

This block of code for validating and converting client_context is duplicated in several places (e.g., _BatchBase, MutationGroups, Client, _SnapshotBase). To improve maintainability and reduce code duplication, consider extracting this logic into a helper function in google/cloud/spanner_v1/_helpers.py.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: spanner Issues related to the googleapis/python-spanner API. size: l Pull request size is large.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants