Skip to content

feat(bigtable): support materialized views in the data client#17676

Open
axyjo wants to merge 2 commits into
googleapis:mainfrom
axyjo:axyjo-add-materialized-view-data-api-target
Open

feat(bigtable): support materialized views in the data client#17676
axyjo wants to merge 2 commits into
googleapis:mainfrom
axyjo:axyjo-add-materialized-view-data-api-target

Conversation

@axyjo

@axyjo axyjo commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds materialized view support to the Bigtable data client, alongside the existing Table and AuthorizedView surfaces:

  • New MaterializedView / MaterializedViewAsync classes (subclasses of _DataApiTarget) and a client.get_materialized_view(instance_id, materialized_view_id) factory. Reads (read_rows*, sample_row_keys, row_exists) route requests via the materialized_view_name field.
  • Materialized views are read-only in the Bigtable API — the mutation RPCs have no materialized_view_name field — so mutate_row, bulk_mutate_rows, check_and_mutate_row, read_modify_write_row, and mutations_batcher are overridden to raise NotImplementedError immediately instead of failing with an opaque proto error (or, for the batcher, failing later in a background flush).
  • Since materialized views are instance-scoped and have no backing table, table identity (table_id/table_name) moves from the shared _DataApiTarget base class into the Table and AuthorizedView subclasses. Public constructor signatures are unchanged.
  • Sync surfaces regenerated via nox -s generate_sync; docs pages added for both new classes.

Test plan

  • Unit tests: new TestMaterializedView suites (async + generated sync) cover construction, routing metadata (name=<instance path> header), factory passthrough, context-manager use, and the mutation-method rejection; get_materialized_view added to the existing API-surface parametrizations
  • Full tests/unit suite passes locally (5394 passed)
  • Manually exercised against a local gRPC server: ReadRows/SampleRowKeys requests carry materialized_view_name (not table_name), rows are returned through the public API, and Table/AuthorizedView behavior is unchanged
  • CI (docs build, lint, sync-up-to-date check across supported Python versions)

Adds a MaterializedView class (async and sync) to the Bigtable data
client, alongside Table and AuthorizedView, plus a
client.get_materialized_view() factory. Materialized views are
instance-scoped and read-only: read_rows and sample_row_keys route
requests via materialized_view_name, while mutation methods raise
NotImplementedError since the mutation RPCs cannot target a
materialized view.

To support targets without a backing table, table identity
(table_id/table_name) moves from the shared _DataApiTarget base class
into the Table and AuthorizedView subclasses.
@axyjo axyjo requested a review from a team as a code owner July 8, 2026 21:23

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

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.

Code Review

This pull request introduces support for read-only Materialized Views in both synchronous and asynchronous Bigtable data clients, including the necessary client methods, documentation, and unit tests. Feedback highlights a missing @property decorator on the abstract _request_path method in _DataApiTargetAsync for consistency, and identifies a bug in the async unit tests where non-iterable return values from read_row and row_exists are incorrectly subjected to async iteration, which silently swallows errors.

Comment on lines 1128 to 1129
@abc.abstractmethod
def _request_path(self) -> dict[str, str]:

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

The abstract method _request_path in the base class _DataApiTargetAsync is missing the @property decorator. Its subclasses (TableAsync, AuthorizedViewAsync, and MaterializedViewAsync) all implement _request_path as a property, and the synchronous counterpart _DataApiTarget._request_path is also defined as a property. Adding @property here ensures consistency, correct static analysis/type checking, and prevents potential runtime errors if the base class interface is called as a method.

    @property
    @abc.abstractmethod
    def _request_path(self) -> dict[str, str]:

Comment on lines +1789 to +1795
try:
test_fn = view.__getattribute__(fn_name)
maybe_stream = await test_fn(*fn_args)
[i async for i in maybe_stream]
except Exception:
# we expect an exception from attempting to call the mock
pass

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

In test_call_metadata, the test attempts to perform an asynchronous iteration ([i async for i in maybe_stream]) on the result of all read methods. However, read_row and row_exists are coroutines that return non-iterable objects (Row/None and bool respectively). This causes a TypeError which is silently swallowed by the broad except Exception: block. This can mask actual bugs in those methods. We should only perform the async iteration for methods that actually return an async iterable.

Suggested change
try:
test_fn = view.__getattribute__(fn_name)
maybe_stream = await test_fn(*fn_args)
[i async for i in maybe_stream]
except Exception:
# we expect an exception from attempting to call the mock
pass
try:
test_fn = view.__getattribute__(fn_name)
result = await test_fn(*fn_args)
if fn_name not in {"read_row", "row_exists"}:
[i async for i in result]
except Exception:
# we expect an exception from attempting to call the mock
pass
References
  1. In the google-cloud-bigtable Python async client, table.read_rows_stream() is a coroutine that returns an async iterable. It must be awaited before iterating, using the pattern async for row in await table.read_rows_stream(query):.

…view metadata test

read_row, row_exists, and the other non-streaming methods return concrete
values; async-iterating them raised a TypeError that the surrounding
except block silently swallowed, which could mask real errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant