Skip to content

perf(storage): opt-in zero-copy BidiReadObjectResponse parsing for async reads - #4

Open
yuxin00j wants to merge 229 commits into
mainfrom
storage-zero-copy-read
Open

yuxin00j wants to merge 229 commits into
mainfrom
storage-zero-copy-read

Conversation

@yuxin00j

@yuxin00j yuxin00j commented Oct 8, 2026

Copy link
Copy Markdown
Owner

Summary

Add an opt-in zero-copy deserializer for BidiReadObjectResponse so _AsyncReadObjectStream hands each chunk's checksummed_data.content to the read path as a memoryview of the gRPC frame instead of the bytes copy the generated parser makes on the event-loop thread. Off by default; enabled with GOOGLE_CLOUD_STORAGE_ZERO_COPY_BIDI_READ=1 when the installed google-crc32c accepts memoryview (#1).

Changes

  • asyncio/_zero_copy_bidi_read.py (new): deserialize(buf) walks the wire format of data responses (object_data_ranges, read_handle) over memoryview(buf) and returns duck-typed stand-ins with the attributes the read path uses (object_data_ranges, read_handle, metadata, HasField, _pb). A response carrying metadata, or bytes that do not parse cleanly, is handed to BidiReadObjectResponse.deserialize, which returns the real message or raises its usual DecodeError. Varint, int64 and tag handling match upb.
  • Gating: is_supported() requires the env var and a google_crc32c.value(memoryview(b"\0")) probe; google-crc32c <= 1.9.0 rejects memoryview, so without fix(crc32c): accept any contiguous bytes-like object in value() and extend() #1 the generated parser is used and nothing changes.
  • wrapped_rpc(transport): the transport's BidiReadObject stub with the deserializer swapped in, wrapped with gapic_v1.method_async.wrap_method like the generated _wrapped_methods entry (same error mapping and client-info header), cached per transport.
  • asyncio/async_read_object_stream.py: _AsyncReadObjectStream uses wrapped_rpc(transport) when available, else the generated wrapped method.

With the parser enabled, the content passed to user_buffer.write() is a memoryview; BytesIO accepts it, custom sinks must too, which is why it is opt-in.

Tests

tests/unit/asyncio/test_zero_copy_bidi_read.py: field-by-field comparison with the generated parser (including unknown fields of every wire type at each nesting level), 16 malformed inputs on which both parsers raise DecodeError, fallback cases, env-var and probe gating, wrapped_rpc caching, and an in-process grpc.aio server driving _AsyncReadObjectStream with the parser on and off. 100% line and branch coverage of the module.

Benchmark

c4-standard-192 (us-central1-c), Rapid bucket, 16 concurrent streams per process, 4 x 16 MiB per download_ranges call into preallocated buffers, CRC32C verification on (inline), google-crc32c from #1 (mrd_bench.py, median of 3 runs, aggregate GB/s):

Processes main zero-copy parser
1 1.55 2.62
8 9.76 11.29

Parse plus the read path's field reads for one 2 MiB response: 257 us with the generated parser, 5 us with this one.

release-please Bot and others added 30 commits October 2, 2026 06:01
The generated BidiReadObject stub parses every response with proto-plus
over upb, which copies each data chunk twice on the event-loop thread
before it reaches the caller's buffer. Add a wire-format parser for plain
data responses that exposes `checksummed_data.content` as a memoryview of
the received message, and let `_AsyncReadObjectStream` use a stub built
with it (wrapped like the generated method: error mapping, no default
retry or timeout, same client-info header). Responses carrying object
metadata, or anything that does not parse cleanly, are handed to the
generated parser, so callers keep seeing the real message or its usual
DecodeError.

CRC32C verification in the read path is unchanged: the memoryview flows
through google_crc32c.value(), the user buffer write and the rolling
checksum. Because google-crc32c releases up to 1.9.0 only accept bytes
there, the fast path is used only when the installed google-crc32c can
checksum a memoryview; GOOGLE_CLOUD_STORAGE_FAST_BIDI_READ=0 forces the
generated parser as well.
…sages and invalid tags

The zero-copy BidiReadObjectResponse parser is meant to be indistinguishable
from the generated one apart from the type of ``content``; a comparison
against upb found a few places where it was not:

- ReadRange.read_offset/read_length/read_id are int64, so a ten-byte
  two's-complement varint must come back negative, and bits above 64 in
  an over-long varint are dropped rather than kept.
- A singular embedded message that occurs twice in its parent is merged;
  the sub-message parsers now fill in the existing object, which also
  removes two throw-away allocations per ObjectRangeData.
- Field number 0, tags above 32 bits and tags encoded in more than five
  bytes are malformed and now reach the generated parser's DecodeError.

Tests cover each case against the generated parser.
…c write microbenchmarks (googleapis#18511)

## Description
Passes `storage_class="RAPID"` (when `RUN_RCU_SYSTEM_TESTS=True`) and
`finalize_on_close=True` to `AsyncAppendableObjectWriter` in
`upload_chunks_using_grpc_async` in microbenchmarks writes.
…googleapis#18543)

Previously, Resumable Uploads would only accept the request_body as part
of the `upload` method. Now, it can also be passed in the constructor,
to serve as a default value
…eapis#18542)

This PR allows AsyncAuthorizedSession to support standard credential
types by wrapping them in an adapter

---------

Co-authored-by: Anthonios Partheniou <partheniou@google.com>
…e uploads (googleapis#18544)

Currently, resumable uploads only support `aiohttp.ClientSession`. Gapic
clients use `AsyncAuthorizedSession` instead. This PR refactors the
implementation to support both options

---------

Co-authored-by: Anthonios Partheniou <partheniou@google.com>
🤖 I have created a release *beep* *boop*
---


##
[2.60.0](googleapis/google-cloud-python@google-auth-v2.59.1...google-auth-v2.60.0)
(2026-10-02)


### Features

* **auth:** Support sync credentials in AsyncAuthorizedSession
([googleapis#18542](googleapis#18542))
([3799568](googleapis@3799568))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
🤖 I have created a release *beep* *boop*
---


<details><summary>google-api-core: 2.41.0</summary>

##
[2.41.0](googleapis/google-cloud-python@google-api-core-v2.40.0...google-api-core-v2.41.0)
(2026-10-02)


### Features

* **api_core:** resumable upload supports request_body in constructor
([googleapis#18543](googleapis#18543))
([97bc619](googleapis@97bc619))
* **api-core:** support AsyncAuthorizedSession transport for resumable
uploads
([googleapis#18544](googleapis#18544))
([9b96af7](googleapis@9b96af7))
* **auth:** Support sync credentials in AsyncAuthorizedSession
([googleapis#18542](googleapis#18542))
([3799568](googleapis@3799568))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
…le uploads (googleapis#18495)

- Add client, transport, and unit test template support for resumable
upload RPCs.

- Resumable upload methods accept an optional `ResumableUploadConfig` on
the client and return `ResumableUploadSession` /
`AsyncResumableUploadSession` from `google.api_core.resumable_transfer`.
Since resumable uploads use HTTP, the gRPC and gRPC-async transports
delegate resumable upload calls to the corresponding REST transport.
googleapis#18501)

- Add sample generation support for resumable upload RPCs.

- Generated sync and async snippets demonstrate creating an upload
session with `ResumableUploadConfig`, uploading a stream directly,
iterating over `UploadProgress` updates per chunk, and resuming an
interrupted upload from a saved session URL.
…ptor (googleapis#18581)

Fix an issue where async streaming RPCs were incorrectly wrapped in
_AsyncUnaryResponseWrapper instead of _AsyncStreamingResponseWrapper,
preventing metrics from being properly recorded.

#### Problem
In grpc.aio, streaming call objects (like UnaryStreamCall) implement
`__aiter__` (returning an async generator) but do not define `__anext__`
directly on the call object itself. Because AsyncMetricsInterceptor
checked `hasattr(response, "__anext__")`, all async streaming calls
(such as ExecuteStreamingSql and StreamingRead) were misclassified as
unary calls and wrapped in _AsyncUnaryResponseWrapper.

Because _AsyncUnaryResponseWrapper only records metrics when awaited
(`__await__`), and callers iterate over streams chunk-by-chunk rather
than awaiting the stream object itself:
1. Frontend metrics (gfe_latencies, afe_latencies, and missing header
counts) were never recorded for any async queries or streaming reads.
2. Attempt completion was never recorded when the stream finished.
Instead, it was only recorded much later when the wrapper was
garbage-collected (`__del__`), resulting in wildly inflated attempt
latency measurements or attempts incorrectly reporting as CANCELLED.

#### Solution
- Explicitly dispatch async interceptor wrappers based on the gRPC call
type (is_streaming=True for intercept_unary_stream and
intercept_stream_stream) rather than fragile runtime dunder checks.
- Realign class inheritance so StreamUnaryCall is handled by
_AsyncUnaryResponseWrapper, adding write() and done_writing()
delegation.
- Support sending real HTTP/2 response headers in the in-memory mock
Spanner server via an add_header() helper, removing monkeypatching of
internal gRPC classes in integration tests.
- Add unit tests for direct `__anext__()` consumption (matching
StreamedResultSet), delegation methods, error handling, and sync/async
response wrappers.
)

This PR contains the following updates:

| Package | Change |
[Age](https://docs.renovatebot.com/merge-confidence/) |
[Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|
| [pytest](https://redirect.github.com/pytest-dev/pytest)
([changelog](https://docs.pytest.org/en/stable/changelog.html)) |
`==8.4.2` → `==9.0.3` |
![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/8.4.2/9.0.3?slim=true)
|
| [pytest](https://redirect.github.com/pytest-dev/pytest)
([changelog](https://docs.pytest.org/en/stable/changelog.html)) |
`==8.3.5` → `==9.0.3` |
![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/8.3.5/9.0.3?slim=true)
|
| [pytest](https://redirect.github.com/pytest-dev/pytest)
([changelog](https://docs.pytest.org/en/stable/changelog.html)) |
`==7.4.3` → `==9.0.3` |
![age](https://developer.mend.io/api/mc/badges/age/pypi/pytest/9.0.3?slim=true)
|
![confidence](https://developer.mend.io/api/mc/badges/confidence/pypi/pytest/7.4.3/9.0.3?slim=true)
|

---

### pytest has vulnerable tmpdir handling
[CVE-2025-71176](https://nvd.nist.gov/vuln/detail/CVE-2025-71176) /
[GHSA-6w46-j5rx-g56g](https://redirect.github.com/advisories/GHSA-6w46-j5rx-g56g)

<details>
<summary>More information</summary>

#### Details
pytest through 9.0.2 on UNIX relies on directories with the
`/tmp/pytest-of-{user}` name pattern, which allows local users to cause
a denial of service or possibly gain privileges.

#### Severity
- CVSS Score: 6.8 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:L`

#### References
-
[https://nvd.nist.gov/vuln/detail/CVE-2025-71176](https://nvd.nist.gov/vuln/detail/CVE-2025-71176)
-
[https://github.com/pytest-dev/pytest/issues/13669](https://redirect.github.com/pytest-dev/pytest/issues/13669)
-
[https://www.openwall.com/lists/oss-security/2026/01/21/5](https://www.openwall.com/lists/oss-security/2026/01/21/5)
-
[https://github.com/pytest-dev/pytest/pull/14343](https://redirect.github.com/pytest-dev/pytest/pull/14343)
-
[https://github.com/pytest-dev/pytest/commit/95d8423bd24992deea5b9df32555fa1741679e2c](https://redirect.github.com/pytest-dev/pytest/commit/95d8423bd24992deea5b9df32555fa1741679e2c)
-
[https://github.com/pytest-dev/pytest/releases/tag/9.0.3](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.3)
-
[https://github.com/advisories/GHSA-6w46-j5rx-g56g](https://redirect.github.com/advisories/GHSA-6w46-j5rx-g56g)

This data is provided by the [GitHub Advisory
Database](https://redirect.github.com/advisories/GHSA-6w46-j5rx-g56g)
([CC-BY
4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Release Notes

<details>
<summary>pytest-dev/pytest (pytest)</summary>

###
[`v9.0.3`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.3)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.2...9.0.3)

### pytest 9.0.3 (2026-04-07)

#### Bug fixes

-
[#&googleapis#8203;12444](https://redirect.github.com/pytest-dev/pytest/issues/12444):
Fixed `pytest.approx` which now correctly takes into account
`~collections.abc.Mapping` keys order to compare them.

-
[#&googleapis#8203;13634](https://redirect.github.com/pytest-dev/pytest/issues/13634):
Blocking a `conftest.py` file using the `-p no:` option is now
explicitly disallowed.

Previously this resulted in an internal assertion failure during plugin
loading.

Pytest now raises a clear `UsageError` explaining that conftest files
are not plugins and cannot be disabled via `-p`.

-
[#&googleapis#8203;13734](https://redirect.github.com/pytest-dev/pytest/issues/13734):
Fixed crash when a test raises an exceptiongroup with `__tracebackhide__
= True`.

-
[#&googleapis#8203;14195](https://redirect.github.com/pytest-dev/pytest/issues/14195):
Fixed an issue where non-string messages passed to <span
class="title-ref">unittest.TestCase.subTest()</span> were not printed.

-
[#&googleapis#8203;14343](https://redirect.github.com/pytest-dev/pytest/issues/14343):
Fixed use of insecure temporary directory (CVE-2025-71176).

#### Improved documentation

-
[#&googleapis#8203;13388](https://redirect.github.com/pytest-dev/pytest/issues/13388):
Clarified documentation for `-p` vs `PYTEST_PLUGINS` plugin loading and
fixed an incorrect `-p` example.
-
[#&googleapis#8203;13731](https://redirect.github.com/pytest-dev/pytest/issues/13731):
Clarified that capture fixtures (e.g. `capsys` and `capfd`) take
precedence over the `-s` / `--capture=no` command-line options in
`Accessing captured output from a test function
<accessing-captured-output>`.
-
[#&googleapis#8203;14088](https://redirect.github.com/pytest-dev/pytest/issues/14088):
Clarified that the default `pytest_collection` hook sets `session.items`
before it calls `pytest_collection_finish`, not after.
-
[#&googleapis#8203;14255](https://redirect.github.com/pytest-dev/pytest/issues/14255):
TOML integer log levels must be quoted: Updating reference
documentation.

#### Contributor-facing changes

-
[#&googleapis#8203;12689](https://redirect.github.com/pytest-dev/pytest/issues/12689):
The test reports are now published to Codecov from GitHub Actions.
The test statistics is visible [on the web
interface](https://app.codecov.io/gh/pytest-dev/pytest/tests).

  \-- by `aleguy02`

###
[`v9.0.2`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.2)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.1...9.0.2)

### pytest 9.0.2 (2025-12-06)

#### Bug fixes

-
[#&googleapis#8203;13896](https://redirect.github.com/pytest-dev/pytest/issues/13896):
The terminal progress feature added in pytest 9.0.0 has been disabled by
default, except on Windows, due to compatibility issues with some
terminal emulators.

You may enable it again by passing `-p terminalprogress`. We may enable
it by default again once compatibility improves in the future.

Additionally, when the environment variable `TERM` is `dumb`, the escape
codes are no longer emitted, even if the plugin is enabled.

-
[#&googleapis#8203;13904](https://redirect.github.com/pytest-dev/pytest/issues/13904):
Fixed the TOML type of the `tmp_path_retention_count` settings in the
API reference from number to string.

-
[#&googleapis#8203;13946](https://redirect.github.com/pytest-dev/pytest/issues/13946):
The private `config.inicfg` attribute was changed in a breaking manner
in pytest 9.0.0.
Due to its usage in the ecosystem, it is now restored to working order
using a compatibility shim.
  It will be deprecated in pytest 9.1 and removed in pytest 10.

-
[#&googleapis#8203;13965](https://redirect.github.com/pytest-dev/pytest/issues/13965):
Fixed quadratic-time behavior when handling `unittest` subtests in
Python 3.10.

#### Improved documentation

-
[#&googleapis#8203;4492](https://redirect.github.com/pytest-dev/pytest/issues/4492):
The API Reference now contains cross-reference-able documentation of
`pytest's command-line flags <command-line-flags>`.

###
[`v9.0.1`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.1)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest/compare/9.0.0...9.0.1)

### pytest 9.0.1 (2025-11-12)

#### Bug fixes

-
[#&googleapis#8203;13895](https://redirect.github.com/pytest-dev/pytest/issues/13895):
Restore support for skipping tests via `raise unittest.SkipTest`.
-
[#&googleapis#8203;13896](https://redirect.github.com/pytest-dev/pytest/issues/13896):
The terminal progress plugin added in pytest 9.0 is now automatically
disabled when iTerm2 is detected, it generated desktop notifications
instead of the desired functionality.
-
[#&googleapis#8203;13904](https://redirect.github.com/pytest-dev/pytest/issues/13904):
Fixed the TOML type of the verbosity settings in the API reference from
number to string.
-
[#&googleapis#8203;13910](https://redirect.github.com/pytest-dev/pytest/issues/13910):
Fixed <span class="title-ref">UserWarning: Do not expect
file\_or\_dir</span> on some earlier Python 3.12 and 3.13 point
versions.

#### Packaging updates and notes for downstreams

-
[#&googleapis#8203;13933](https://redirect.github.com/pytest-dev/pytest/issues/13933):
The tox configuration has been adjusted to make sure the desired
  version string can be passed into its `package_env` through
  the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment
  variable as a part of the release process -- by `webknjaz`.

#### Contributor-facing changes

-
[#&googleapis#8203;13891](https://redirect.github.com/pytest-dev/pytest/issues/13891),
[#&googleapis#8203;13942](https://redirect.github.com/pytest-dev/pytest/issues/13942):
The CI/CD part of the release automation is now capable of
  creating GitHub Releases without having a Git checkout on
  disk -- by `bluetech` and `webknjaz`.
-
[#&googleapis#8203;13933](https://redirect.github.com/pytest-dev/pytest/issues/13933):
The tox configuration has been adjusted to make sure the desired
  version string can be passed into its `package_env` through
  the `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST` environment
  variable as a part of the release process -- by `webknjaz`.

###
[`v9.0.0`](https://redirect.github.com/pytest-dev/pytest/releases/tag/9.0.0)

[Compare
Source](https://redirect.github.com/pytest-dev/pytest/compare/8.4.2...9.0.0)

### pytest 9.0.0 (2025-11-05)

#### New features

-
[#&googleapis#8203;1367](https://redirect.github.com/pytest-dev/pytest/issues/1367):
**Support for subtests** has been added.

`subtests <subtests>` are an alternative to parametrization, useful in
situations where the parametrization values are not all known at
collection time.

  Example:

  ```python
  def contains_docstring(p: Path) -> bool:
"""Return True if the given Python file contains a top-level
docstring."""
      ...

def test_py_files_contain_docstring(subtests: pytest.Subtests) -> None:
      for path in Path.cwd().glob("*.py"):
          with subtests.test(path=str(path)):
              assert contains_docstring(path)
  ```

Each assert failure or error is caught by the context manager and
reported individually, giving a clear picture of all files that are
missing a docstring.

  In addition, `unittest.TestCase.subTest` is now also supported.

This feature was originally implemented as a separate plugin in
[pytest-subtests](https://redirect.github.com/pytest-dev/pytest-subtests),
but since then has been merged into the core.

  > \[!NOTE]
> This feature is experimental and will likely evolve in future
releases. By that we mean that we might change how subtests are reported
on failure, but the functionality and how to use it are stable.

-
[#&googleapis#8203;13743](https://redirect.github.com/pytest-dev/pytest/issues/13743):
Added support for **native TOML configuration files**.

While pytest, since version 6, supports configuration in
`pyproject.toml` files under `[tool.pytest.ini_options]`,
it does so in an "INI compatibility mode", where all configuration
values are treated as strings or list of strings.
  Now, pytest supports the native TOML data model.

In `pyproject.toml`, the native TOML configuration is under the
`[tool.pytest]` table.

  ```toml
  # pyproject.toml
  [tool.pytest]
  minversion = "9.0"
  addopts = ["-ra", "-q"]
  testpaths = [
      "tests",
      "integration",
  ]
  ```

The `[tool.pytest.ini_options]` table remains supported, but both tables
cannot be used at the same time.

If you prefer to use a separate configuration file, or don't use
`pyproject.toml`, you can use `pytest.toml` or `.pytest.toml`:

  ```toml
  # pytest.toml or .pytest.toml
  [pytest]
  minversion = "9.0"
  addopts = ["-ra", "-q"]
  testpaths = [
      "tests",
      "integration",
  ]
  ```

The documentation now (sometimes) shows configuration snippets in both
TOML and INI formats, in a tabbed interface.

  See `config file formats` for full details.

-
[#&googleapis#8203;13823](https://redirect.github.com/pytest-dev/pytest/issues/13823):
Added a **"strict mode"** enabled by the `strict` configuration option.

  When set to `true`, the `strict` option currently enables

  - `strict_config`
  - `strict_markers`
  - `strict_parametrization_ids`
  - `strict_xfail`

The individual strictness options can be explicitly set to override the
global `strict` setting.

The previously-deprecated `--strict` command-line flag now enables
strict mode.

If pytest adds new strictness options in the future, they will also be
enabled in strict mode.
Therefore, you should only enable strict mode if you use a pinned/locked
version of pytest,
or if you want to proactively adopt new strictness options as they are
added.

  See `strict mode` for more details.

-
[#&googleapis#8203;13737](https://redirect.github.com/pytest-dev/pytest/issues/13737):
Added the `strict_parametrization_ids` configuration option.

When set, pytest emits an error if it detects non-unique parameter set
IDs,
rather than automatically making the IDs unique by adding <span
class="title-ref">0</span>, <span class="title-ref">1</span>, ... to
them.
  This can be particularly useful for catching unintended duplicates.

-
[#&googleapis#8203;13072](https://redirect.github.com/pytest-dev/pytest/issues/13072):
Added support for displaying test session **progress in the terminal
tab** using the [OSC
9;4;](https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC)
ANSI sequence.
When pytest runs in a supported terminal emulator like ConEmu, Gnome
Terminal, Ptyxis, Windows Terminal, Kitty or Ghostty,
  you'll see the progress in the terminal tab or window,
  allowing you to monitor pytest's progress at a glance.

This feature is automatically enabled when running in a TTY. It is
implemented as an internal plugin. If needed, it can be disabled as
follows:

- On a user level, using `-p no:terminalprogress` on the command line or
via an environment variable `PYTEST_ADDOPTS='-p no:terminalprogress'`.
- On a project configuration level, using `addopts = "-p
no:terminalprogress"`.

-
[#&googleapis#8203;478](https://redirect.github.com/pytest-dev/pytest/issues/478):
Support PEP420 (implicit namespace packages) as <span
class="title-ref">--pyargs</span> target when
`consider_namespace_packages` is <span class="title-ref">true</span> in
the config.

Previously, this option only impacted package imports, now it also
impacts tests discovery.

-
[#&googleapis#8203;13678](https://redirect.github.com/pytest-dev/pytest/issues/13678):
Added a new `faulthandler_exit_on_timeout` configuration option set to
"false" by default to let <span class="title-ref">faulthandler</span>
interrupt the <span class="title-ref">pytest</span> process after a
timeout in case of deadlock.

Previously, a <span class="title-ref">faulthandler</span> timeout would
only dump the traceback of all threads to stderr, but would not
interrupt the <span class="title-ref">pytest</span> process.

  \-- by `ogrisel`.

-
[#&googleapis#8203;13829](https://redirect.github.com/pytest-dev/pytest/issues/13829):
Added support for configuration option aliases via the `aliases`
parameter in `Parser.addini() <pytest.Parser.addini>`.

  Plugins can now register alternative names for configuration options,
allowing for more flexibility in configuration naming and supporting
backward compatibility when renaming options.
The canonical name always takes precedence if both the canonical name
and an alias are specified in the configuration file.

#### Improvements in existing functionality

-
[#&googleapis#8203;13330](https://redirect.github.com/pytest-dev/pytest/issues/13330):
Having pytest configuration spread over more than one file (for example
having both a `pytest.ini` file and `pyproject.toml` with a
`[tool.pytest.ini_options]` table) will now print a warning to make it
clearer to the user that only one of them is actually used.

  \-- by `sgaist`

-
[#&googleapis#8203;13574](https://redirect.github.com/pytest-dev/pytest/issues/13574):
The single argument `--version` no longer loads the entire plugin
infrastructure, making it faster and more reliable when displaying only
the pytest version.

Passing `--version` twice (e.g., `pytest --version --version`) retains
the original behavior, showing both the pytest version and plugin
information.

  > \[!NOTE]
> Since `--version` is now processed early, it only takes effect when
passed directly via the command line. It will not work if set through
other mechanisms, such as `PYTEST_ADDOPTS` or `addopts`.

-
[#&googleapis#8203;13823](https://redirect.github.com/pytest-dev/pytest/issues/13823):
Added `strict_xfail` as an alias to the `xfail_strict` option,
  `strict_config` as an alias to the `--strict-config` flag,
  and `strict_markers` as an alias to the `--strict-markers` flag.
This makes all strictness options consistently have configuration
options with the prefix `strict_`.

-
[#&googleapis#8203;13700](https://redirect.github.com/pytest-dev/pytest/issues/13700):
<span class="title-ref">--junitxml</span> no longer prints the <span
class="title-ref">generated xml file</span> summary at the end of the
pytest session when <span class="title-ref">--quiet</span> is given.

-
[#&googleapis#8203;13732](https://redirect.github.com/pytest-dev/pytest/issues/13732):
Previously, when filtering warnings, pytest would fail if the filter
referenced a class that could not be imported. Now, this only outputs a
message indicating the problem.

-
[#&googleapis#8203;13859](https://redirect.github.com/pytest-dev/pytest/issues/13859):
Clarify the error message for <span
class="title-ref">pytest.raises()</span> when a regex <span
class="title-ref">match</span> fails.

-
[#&googleapis#8203;13861](https://redirect.github.com/pytest-dev/pytest/issues/13861):
Better sentence structure in a test's expected error message.
Previously, the error message would be "expected exception must be
\<expected>, but got \<actual>". Now, it is "Expected \<expected>, but
got \<actual>".

#### Removals and backward incompatible breaking changes

-
[#&googleapis#8203;12083](https://redirect.github.com/pytest-dev/pytest/issues/12083):
Fixed a bug where an invocation such as <span class="title-ref">pytest
a/ a/b</span> would cause only tests from <span
class="title-ref">a/b</span> to run, and not other tests under <span
class="title-ref">a/</span>.

The fix entails a few breaking changes to how such overlapping arguments
and duplicates are handled:

1. <span class="title-ref">pytest a/b a/</span> or <span
class="title-ref">pytest a/ a/b</span> are equivalent to <span
class="title-ref">pytest a</span>; if an argument overlaps another
arguments, only the prefix remains.
2. <span class="title-ref">pytest x.py x.py</span> is equivalent to
<span class="title-ref">pytest x.py</span>; previously such an
invocation was taken as an explicit request to run the tests from the
file twice.

If you rely on these behaviors, consider using `--keep-duplicates
<duplicate-paths>`, which retains its existing behavior (including the
bug).

-
[#&googleapis#8203;13719](https://redirect.github.com/pytest-dev/pytest/issues/13719):
Support for Python 3.9 is dropped following its end of life.

-
[#&googleapis#8203;13766](https://redirect.github.com/pytest-dev/pytest/issues/13766):
Previously, pytest would assume it was running in a CI/CD environment if
either of the environment variables <span class="title-ref">$CI</span>
or <span class="title-ref">$BUILD\_NUMBER</span> was defined;
now, CI mode is only activated if at least one of those variables is
defined and set to a *non-empty* value.

-
[#&googleapis#8203;13779](https://redirect.github.com/pytest-dev/pytest/issues/13779):
**PytestRemovedIn9Warning deprecation warnings are now errors by
default.**

Following our plan to remove deprecated features with as little
disruption as
possible, all warnings of type `PytestRemovedIn9Warning` now generate
errors
  instead of warning messages by default.

**The affected features will be effectively removed in pytest 9.1**, so
please consult the
`deprecations` section in the docs for directions on how to update
existing code.

In the pytest `9.0.X` series, it is possible to change the errors back
into warnings as a
  stopgap measure by adding this to your `pytest.ini` file:

  ```ini
  [pytest]
  filterwarnings =
      ignore::pytest.PytestRemovedIn9Warning
  ```

  But this will stop working when pytest `9.1` is released.

**If you have concerns** about the removal of a specific feature, please
add a
  comment to `13779`.

#### Deprecations (removal in next major release)

-
[#&googleapis#8203;13807](https://redirect.github.com/pytest-dev/pytest/issues/13807):
`monkeypatch.syspath_prepend() <pytest.MonkeyPatch.syspath_prepend>` now
issues a deprecation warning when the prepended path contains legacy
namespace packages (those using `pkg_resources.declare_namespace()`).
  Users should migrate to native namespace packages (`420`).
  See `monkeypatch-fixup-namespace-packages` for details.

#### Bug fixes

-
[#&googleapis#8203;13445](https://redirect.github.com/pytest-dev/pytest/issues/13445):
Made the type annotations of `pytest.skip` and friends more
spec-complaint to have them work across more type checkers.

-
[#&googleapis#8203;13537](https://redirect.github.com/pytest-dev/pytest/issues/13537):
Fixed a bug in which `ExceptionGroup` with only `Skipped` exceptions in
teardown was not handled correctly and showed as error.

-
[#&googleapis#8203;13598](https://redirect.github.com/pytest-dev/pytest/issues/13598):
Fixed possible collection confusion on Windows when short paths and
symlinks are involved.

-
[#&googleapis#8203;13716](https://redirect.github.com/pytest-dev/pytest/issues/13716):
Fixed a bug where a nonsensical invocation like `pytest x.py[a]` (a file
cannot be parametrized) was silently treated as `pytest x.py`. This is
now a usage error.

-
[#&googleapis#8203;13722](https://redirect.github.com/pytest-dev/pytest/issues/13722):
Fixed a misleading assertion failure message when using `pytest.approx`
on mappings with differing lengths.

-
[#&googleapis#8203;13773](https://redirect.github.com/pytest-dev/pytest/issues/13773):
Fixed the static fixture closure calculation to properly consider
transitive dependencies requested by overridden fixtures.

-
[#&googleapis#8203;13816](https://redirect.github.com/pytest-dev/pytest/issues/13816):
Fixed `pytest.approx` which now returns a clearer error message when
comparing mappings with different keys.

-
[#&googleapis#8203;13849](https://redirect.github.com/pytest-dev/pytest/issues/13849):
Hidden `.pytest.ini` files are now picked up as the config file even if
empty.
  This was an inconsistency with non-hidden `pytest.ini`.

-
[#&googleapis#8203;13865](https://redirect.github.com/pytest-dev/pytest/issues/13865):
Fixed <span class="title-ref">--show-capture</span> with <span
class="title-ref">--tb=line</span>.

-
[#&googleapis#8203;13522](https://redirect.github.com/pytest-dev/pytest/issues/13522):
Fixed `pytester` in subprocess mode ignored all :attr\`pytester.plugins
\<pytest.Pytester.plugins>\` except the first.

Fixed `pytester` in subprocess mode silently ignored non-str
`pytester.plugins <pytest.Pytester.plugins>`.
  Now it errors instead.
If you are affected by this, specify the plugin by name, or switch the
affected tests to use `pytester.runpytest_inprocess
<pytest.Pytester.runpytest_inprocess>` explicitly instead.

#### Packaging updates and notes for downstreams

-
[#&googleapis#8203;13791](https://redirect.github.com/pytest-dev/pytest/issues/13791):
Minimum requirements on `iniconfig` and `packaging` were bumped to
`1.0.1` and `22.0.0`, respectively.

#### Contributor-facing changes

-
[#&googleapis#8203;12244](https://redirect.github.com/pytest-dev/pytest/issues/12244):
Fixed self-test failures when <span class="title-ref">TERM=dumb</span>.
-
[#&googleapis#8203;12474](https://redirect.github.com/pytest-dev/pytest/issues/12474):
Added scheduled GitHub Action Workflow to run Sphinx linkchecks in repo
documentation.
-
[#&googleapis#8203;13621](https://redirect.github.com/pytest-dev/pytest/issues/13621):
pytest's own testsuite now handles the `lsof` command hanging (e.g. due
to unreachable network filesystems), with the affected selftests being
skipped after 10 seconds.
-
[#&googleapis#8203;13638](https://redirect.github.com/pytest-dev/pytest/issues/13638):
Fixed deprecated `gh pr new` command in `scripts/prepare-release-pr.py`.
The script now uses `gh pr create` which is compatible with GitHub CLI
v2.0+.
-
[#&googleapis#8203;13695](https://redirect.github.com/pytest-dev/pytest/issues/13695):
Flush <span class="title-ref">stdout</span> and <span
class="title-ref">stderr</span> in <span
class="title-ref">Pytester.run</span> to avoid truncated outputs in
<span class="title-ref">test\_faulthandler.py::test\_timeout</span> on
CI -- by `ogrisel`.
-
[#&googleapis#8203;13771](https://redirect.github.com/pytest-dev/pytest/issues/13771):
Skip <span
class="title-ref">test\_do\_not\_collect\_symlink\_siblings</span> on
Windows environments without symlink support to avoid false negatives.
-
[#&googleapis#8203;13841](https://redirect.github.com/pytest-dev/pytest/issues/13841):
`tox>=4` is now required when contributing to pytest.
-
[#&googleapis#8203;13625](https://redirect.github.com/pytest-dev/pytest/issues/13625):
Added missing docstrings to `pytest_addoption()`, `pytest_configure()`,
and `cacheshow()` functions in `cacheprovider.py`.

#### Miscellaneous internal changes

-
[#&googleapis#8203;13830](https://redirect.github.com/pytest-dev/pytest/issues/13830):
Configuration overrides (`-o`/`--override-ini`) are now processed during
startup rather than during `config.getini() <pytest.Config.getini>`.

</details>

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/).
View the [repository job
log](https://developer.mend.io/github/googleapis/google-cloud-python).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMjUuMSIsInVwZGF0ZWRJblZlciI6IjQ0LjEzNC4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: Chalmer Lowe <chalmerlowe@google.com>
…ps (googleapis#18582)

Speed up the unit test suite by ~55% (reducing full suite runtime from
~66s to ~29s) by eliminating wall-clock sleep delays, avoiding live gRPC
transport initialization, and reducing cryptographic CPU burn in
protocol tests.

Key changes:
- Intercept retry backoff sleeps: Mock localized `time.sleep` and
`asyncio.sleep` in transaction, snapshot, and batch commit retry tests.
Each test now verifies retry counts and delay values without idling in
real time.
- Deterministic concurrency synchronization: Replace arbitrary timing
sleeps in session manager and metrics tests with explicit
`asyncio.Event` and `threading.Barrier` coordination, eliminating CI
flakiness under CPU contention.
- Eliminate teardown polling: Remove `_assert_true_with_timeout` and
sleep polling from `test_database_session_manager.py` by relying on
event-driven rotation callbacks and blocking thread joins.
- Isolate GAPIC admin transports: Use autospec mock transports in
`test_client.py` and `test_instance.py` instead of default credentials,
eliminating live gRPC channel creation and 60-second network timeout
risks.
- Lower Omni Argon2id test parameters: Use minimal valid test parameters
(1 iteration, 8 KB RAM) in protocol flow and state machine tests,
reducing key derivation time by ~1,100x while leaving RFC golden vector
tests untouched.
- Clean up legacy mocks: Convert legacy global
`mock.patch("time.sleep")` calls in `test_session.py` to localized
module paths adhering to repository mock hygiene rules.
)

This PR enables bound access tokens, bound ID tokens, and default mTLS certificate discovery for Agent Identity workloads on GKE/CR, and adds per-request control for ID token binding:

* Fall back to the well-known GKE workload credential bundle (`/var/run/secrets/workload-spiffe-credentials/x509.credential-bundle.private-key.pem`) in `_agent_identity_utils.get_agent_identity_certificate_path()`, `_mtls_helper._get_workload_cert_and_key()`, `_mtls_helper.check_use_client_cert()`, and `mtls.has_default_client_cert_source()` when no explicit or default certificate config file is present.

* Add an optional `bind_id_token` parameter (`True`, `False`, or `None`) to `compute_engine.IDTokenCredentials`, `fetch_id_token_credentials()`, and `fetch_id_token()` (sync and async) so callers can explicitly opt in or out of binding metadata server ID tokens per request.

* Scope default bound ID tokens to GKE by keeping ID tokens unbound when `GOOGLE_API_CERTIFICATE_CONFIG` is set (unless `bind_id_token=True`), while continuing to bind access tokens by default on both Cloud Run and GKE.

* Add and update unit tests for GKE credential bundle fallback, `bind_id_token` options, and the `GOOGLE_API_CERTIFICATE_CONFIG` default gate.

----

Note:
1. GKE mounts a single combined PEM file (`x509.credential-bundle.private-key.pem`) at `/var/run/secrets/workload-spiffe-credentials/` without setting `GOOGLE_API_CERTIFICATE_CONFIG`. With this fallback, GKE Agent Identity workloads automatically use bound access tokens and bound ID tokens by default when the file is present.

2. On Cloud Run (where `GOOGLE_API_CERTIFICATE_CONFIG` is set), default ID token requests (`bind_id_token=None`) temporarily remain unbound so existing callers do not receive bound ID tokens. Callers on Cloud Run can pass `bind_id_token=True` to request bound ID tokens now, and a follow-up PR will remove this temporary gate to enable bound ID tokens by default on Cloud Run as well.

design: go/sdk-mds-bound-token

b/545779266
🤖 I have created a release *beep* *boop*
---


##
[2.61.0](googleapis/google-cloud-python@google-auth-v2.60.0...google-auth-v2.61.0)
(2026-10-07)


### Features

* **auth:** support bound access and ID tokens for GKE
([googleapis#18594](googleapis#18594))
([9f5fd7b](googleapis@9f5fd7b))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
…8587)

`DocumentSnapshot` can be used with sync or async document reference
types, which can result in confusing or incorrect type annotations
upstream

This PR makes `DocumentSnapshot` into a Generic, so it can specify what
type of reference it holds.

Fixes googleapis#15367
Earlier this year, we replaced `rsa` as a required depdendency with
`cryptography`, because `rsa` has been deprecated. At the time, we kept
`rsa` as an optional, opt-in dependency.

Lately, `rsa` has been showing up in more version scanners as a problem
(example:
googleapis#15600 (comment),
internal bug reports). This is showing up for `cryptography`, even
though it's an optional dependency

This PR drops `rsa` completely from `setup.py`, following the same
pattern used for pyopenssl.
…googleapis#18357)

Eliminate the redundant inner 'CloudSpanner.Session.run_in_transaction'
span and duplicate MetricsCapture context, leaving
'CloudSpanner.Database.run_in_transaction' as the single transaction
lifecycle span (matching Java and Go client behavior).

- Attach transaction retry, abort, and rollback events directly to the
active span.
- Guard span event creation, retry delay computation, and exception
stringification behind span.is_recording().
- Refactor the run_in_transaction retry loop by extracting
_create_transaction_for_attempt and _handle_aborted helpers.
…ule (googleapis#18407)

Introduce the foundational OpenTelemetry client metrics gating and
configuration module for Google Cloud Storage.

- Add `_opentelemetry_metrics.py` with a development gate
(`_ENABLE_METRICS_DEV_GATE`) and environment variable parsing
(`GCP_STORAGE_PYTHON_ENABLE_OTEL_METRICS`,
`GCP_STORAGE_PYTHON_ENABLE_OTEL_DEBUG_METRICS`).
- Implement `is_metrics_enabled()` and `is_advanced_metrics_enabled()`
evaluation helpers with precedence hierarchy (dev gate -> client setting
-> environment variable -> default).
- Provide `get_common_attributes()` and `get_meter()` helpers for
OpenTelemetry metrics instrumentation.
- Add unit tests in `test__opentelemetry_metrics.py` covering gating
resolution, environment variable overrides, `ImportError` fallback, and
attribute helpers.
…stants

Explain the opt-in switch's accepted spellings, the four protobuf wire types, the 64-bit varint mask and the 32-bit tag limit.
ohmayr and others added 30 commits October 27, 2026 16:08
…e loads (googleapis#18824)

## Context & Problem
In `pandas 3.1.0+`, `DataFrame.to_csv()` applies `date_format` via
`strftime()` to Python `datetime.datetime` objects inside `object`-dtype
columns
([pandas-dev/pandas#27306](pandas-dev/pandas#27306)).
On Linux/glibc with Python `< 3.14`, `strftime("%Y-%m-%d %H:%M:%S.%f")`
does not zero-pad years `< 1000` (producing `"1-01-01 00:00:00.000000"`,
which BigQuery's CSV load parser rejects in
`test_load_table_from_dataframe_w_explicit_schema_source_format_csv`)
and strips timezone offsets from timezone-aware `datetime` objects in
`TIMESTAMP` columns.

## Summary of Changes
- Add `_pandas_helpers.dataframe_to_csv()` and
`_format_datetime_for_csv()` to pre-format `datetime.datetime` objects
(excluding `pandas.NaT`) in `DATETIME` and `TIMESTAMP` `object`-dtype
columns via `datetime.isoformat(value, sep=" ")` before delegating to
`dataframe.to_csv()`.
- Update `Client.load_table_from_dataframe()` to call
`_pandas_helpers.dataframe_to_csv(dataframe, new_job_config.schema,
tmppath)`.
- Add unit tests in `tests/unit/test_client.py` covering `DATETIME` and
`TIMESTAMP` `object`-dtype columns (including 4-digit year padding for
year `1`, timezone offsets, `None`, `pandas.NaT`, and pre-formatted
strings) as well as schema-less CSV dataframe loads.

## Verification
- Verified
`tests/system/test_pandas.py::test_load_table_from_dataframe_w_explicit_schema_source_format_csv`
and
`test_load_table_from_dataframe_w_explicit_schema_source_format_csv_floats`
pass against BigQuery with `pandas 3.1.0`.
- Verified the full unit test suite (`2895 passed`) with 100% statement
and branch coverage on modified lines under both `pandas 3.1.0` and
`pandas 3.0.6`.
- Verified `mypy` passes cleanly across all 56 source files.
…oogleapis#18799)

Before, `query_and_wait(query_results_format="ARROW")` always created a
job unless the caller set `client.default_job_creation_mode`, so small
Arrow queries paid about 200 ms for a job they didn't need. Arrow
callers want low latency, so ARROW queries now default to
`JOB_CREATION_OPTIONAL`, which is what `createQueryArrowStream()` in the
Node.js client already does.

This changes behavior for existing ARROW callers (Beta since 3.44.0):
results that fit in the first response now come back with
`RowIterator.job_id` set to `None`. `location` and `query_id` are still
set. To always get a job, set
`default_job_creation_mode="JOB_CREATION_REQUIRED"` on the client or use
`client.query()`.

- `query_and_wait()` sends `jobCreationMode=JOB_CREATION_OPTIONAL` when
`query_results_format` is `ARROW` and the client doesn't set
`default_job_creation_mode`. A client-level mode still wins.
- Multi-page and long-running ARROW queries still get a job, since
BigQuery creates one when rows remain. ARROW queries that need
`jobs.insert` (for example, with a destination table) also still create
a job.
- Queries without `ARROW` are unchanged.
- The `query_results_format` and `default_job_creation_mode` docstrings
describe the new default and how to opt out.
- Tests: unit tests for the ARROW default, a client-level mode taking
precedence, non-ARROW queries sending no mode, and the `jobs.insert`
fallback. Ran the unit suite and `tests/system/test_arrow.py` against
BigQuery. A live single-page ARROW query returned `job_id=None`, while a
multi-page ARROW query, a non-ARROW query and an ARROW query with
`JOB_CREATION_REQUIRED` on the client each returned a job.
## Summary

Temporarily pin `pytest<9.2` in the `noxfile.py` configuration of the 13
handwritten packages affected by the `pytest 9.2.0` release.

## Context

Upstream `pytest 9.2.0` (released on October 27, 2026) changed warning
filter initialization so that `filterwarnings = error` is applied
*before* `pytest_configure` plugin hooks run, and removed the private
`FixtureManager._arg2fixturedefs` attribute used by
`pytest-asyncio==0.21.2`.

Pinning `pytest<9.2` in the affected handwritten packages unblocks CI
and current release pull requests (such as googleapis#18876). A follow-up effort
will address `pytest 9.2` compatibility across these packages cleanly.

## Why These 13 Handwritten Packages Are Pinned

All 13 packages configure `filterwarnings = error` in `pytest.ini` or
`pyproject.toml` and fall into two categories:

1. **Direct `pytest-asyncio` usage (10 packages)**: `db-dtypes`,
`google-api-core`, `google-auth-httplib2`, `google-cloud-dns`,
`google-cloud-documentai-toolbox`, `google-cloud-error-reporting`,
`google-cloud-firestore` (also pins `pytest-asyncio==0.21.2`),
`google-cloud-logging`, `google-cloud-pubsub`, and
`google-cloud-runtimeconfig`. Each installs `pytest-asyncio` directly in
its own `noxfile.py` alongside `filterwarnings = error`, causing `pytest
9.2.0` to abort during startup when `pytest-asyncio` emits
`PytestDeprecationWarning` for `asyncio_default_fixture_loop_scope`.

2. **Shared CI virtualenv inheritance (3 packages)**:
`google-cloud-core`, `google-cloud-testutils`, and `proto-plus`.
Although these three do not install `pytest-asyncio` in their own
`noxfile.py`, our GitHub Actions test workflows reuse a shared Nox
virtualenv directory (`NOX_ENVDIR=/tmp/shared_nox_envs`) across all
packages in the same CI shard. When earlier packages in a shard install
`pytest-asyncio` into that shared environment it will trigger the
`filterwarnings = error` startup failure when these three packages run.
Update googleapis API sources and regenerate all Python libraries
(excluding `google-cloud-ces` per googleapis#18888 and `google-cloud-chronicle`
per googleapis#18890).

## Debug Info
### System Details
- Python version: Python 3.13.15
- Go version: go version go1.27.3 linux/amd64
- OS: Linux 7.1.6-1rodete1-amd64 x86_64
- Librarian version: v0.50.0

### Commands Executed
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 update
sources.googleapis`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 install`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 generate
--all`
- `git add .`
- `git commit -m "feat: update API sources and regenerate"`
### Included googleapis CLs

**google/chat/v1**
- http://cl/1005723277

**google/cloud/ces/v1beta**
- http://cl/1005071734

**google/cloud/chronicle/v1**
- http://cl/1009115372

**google/cloud/documentai/v1beta3**
- http://cl/1009078005

**google/cloud/geminidataanalytics/v1**
- http://cl/1004080644

**google/cloud/geminidataanalytics/v1alpha**
- http://cl/1004094508

**google/cloud/geminidataanalytics/v1beta**
- http://cl/1005749856

**google/cloud/memorystore/v1**
- http://cl/1007519450
- http://cl/1005379726

**google/cloud/memorystore/v1beta**
- http://cl/1007522660

**google/cloud/modelarmor/v1**
- http://cl/1006160230

**google/cloud/oracledatabase/v1**
- http://cl/1007799612

**google/cloud/redis/cluster/v1**
- http://cl/1007520135

**google/cloud/redis/cluster/v1beta1**
- http://cl/1007612497

**google/cloud/sql/v1**
- http://cl/1007133627

**google/cloud/sql/v1beta4**
- http://cl/1007133967

**google/cloud/video/transcoder/v1**
- http://cl/1008249014

**google/shopping/merchant/quota/v1**
- http://cl/1005563202
…ogleapis#18894)

Onboard google/cloud/designcenter/v1 API (google-cloud-designcenter).

Internal tracking bug http://b/577038296

PiperOrigin-RevId: 1005520228

## Debug Info
### System Details
- Python version: Python 3.13.15
- OS: Linux 7.1.6-1rodete1-amd64 x86_64
- Librarian version: v0.50.0

### Commands Executed
- `python3 -m venv venv && source venv/bin/activate`
- `pip install --upgrade pip`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 install`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 add
google/cloud/designcenter/v1`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 generate
-v google-cloud-designcenter`
- `go run github.com/googleapis/librarian/cmd/librarian@v0.50.0 tidy`
- `git add .`
- `git commit -m "feat(google/cloud/designcenter/v1): add
google-cloud-designcenter"`
🤖 I have created a release *beep* *boop*
---


<details><summary>gapic-generator: 1.44.1</summary>

##
[1.44.1](googleapis/google-cloud-python@gapic-generator-v1.44.0...gapic-generator-v1.44.1)
(2026-10-29)


### Bug Fixes

* **google-api-core:** align HTTP T3 and T4 OpenTelemetry span status
and attributes
([googleapis#18868](googleapis#18868))
([deb2194](googleapis@deb2194))
* **google-api-core:** align OpenTelemetry span names, kinds, and T3
endpoint attributes with spec
([googleapis#18866](googleapis#18866))
([2a10196](googleapis@2a10196))
</details>

<details><summary>google-api-core: 2.43.2</summary>

##
[2.43.2](googleapis/google-cloud-python@google-api-core-v2.43.1...google-api-core-v2.43.2)
(2026-10-29)


### Bug Fixes

* **google-api-core:** align HTTP T3 and T4 OpenTelemetry span status
and attributes
([googleapis#18868](googleapis#18868))
([deb2194](googleapis@deb2194))
* **google-api-core:** align OpenTelemetry span names, kinds, and T3
endpoint attributes with spec
([googleapis#18866](googleapis#18866))
([2a10196](googleapis@2a10196))
</details>

<details><summary>google-apps-chat: 0.10.8</summary>

##
[0.10.8](googleapis/google-cloud-python@google-apps-chat-v0.10.7...google-apps-chat-v0.10.8)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-documentai: 3.17.0</summary>

##
[3.17.0](googleapis/google-cloud-python@google-cloud-documentai-v3.16.0...google-cloud-documentai-v3.17.0)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-geminidataanalytics: 0.13.6</summary>

##
[0.13.6](googleapis/google-cloud-python@google-cloud-geminidataanalytics-v0.13.5...google-cloud-geminidataanalytics-v0.13.6)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-memorystore: 0.5.7</summary>

##
[0.5.7](googleapis/google-cloud-python@google-cloud-memorystore-v0.5.6...google-cloud-memorystore-v0.5.7)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-modelarmor: 0.7.4</summary>

##
[0.7.4](googleapis/google-cloud-python@google-cloud-modelarmor-v0.7.3...google-cloud-modelarmor-v0.7.4)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-oracledatabase: 0.6.4</summary>

##
[0.6.4](googleapis/google-cloud-python@google-cloud-oracledatabase-v0.6.3...google-cloud-oracledatabase-v0.6.4)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-redis-cluster: 0.5.7</summary>

##
[0.5.7](googleapis/google-cloud-python@google-cloud-redis-cluster-v0.5.6...google-cloud-redis-cluster-v0.5.7)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-sql: 0.1.3</summary>

##
[0.1.3](googleapis/google-cloud-python@google-cloud-sql-v0.1.2...google-cloud-sql-v0.1.3)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-cloud-video-transcoder: 1.23.0</summary>

##
[1.23.0](googleapis/google-cloud-python@google-cloud-video-transcoder-v1.22.0...google-cloud-video-transcoder-v1.23.0)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

<details><summary>google-shopping-merchant-quota: 1.7.0</summary>

##
[1.7.0](googleapis/google-cloud-python@google-shopping-merchant-quota-v1.6.0...google-shopping-merchant-quota-v1.7.0)
(2026-10-29)


### Features

* update API sources and regenerate
([googleapis#18889](googleapis#18889))
([8705243](googleapis@8705243))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
🤖 I have created a release *beep* *boop*
---


##
[3.47.0](googleapis/google-cloud-python@google-cloud-bigquery-v3.46.2...google-cloud-bigquery-v3.47.0)
(2026-10-29)


### Features

* **bigquery:** add column data policies to SchemaField
([googleapis#18710](googleapis#18710))
([157efe4](googleapis@157efe4))
* **bigquery:** add timestamp_precision for Arrow query results
([googleapis#18649](googleapis#18649))
([a29d384](googleapis@a29d384))
* **bigquery:** read Arrow results from explicit destinations
([googleapis#18654](googleapis#18654))
([8ac01b9](googleapis@8ac01b9))
* **bigquery:** share one BigQuery Storage client per Client
([googleapis#18652](googleapis#18652))
([89cbc50](googleapis@89cbc50))
* **bigquery:** skip job creation for ARROW query_and_wait results
([googleapis#18799](googleapis#18799))
([ca24f26](googleapis@ca24f26))
* **bigquery:** validate Arrow queries before sending them
([googleapis#18645](googleapis#18645))
([1ac51ce](googleapis@1ac51ce))
* support Python 3.15
([8fceef4](googleapis@8fceef4))


### Bug Fixes

* **bigquery:** apply max_results to the whole Arrow result
([googleapis#18651](googleapis#18651))
([48c022c](googleapis@48c022c))
* **bigquery:** keep the Arrow first page and wait without rows
([googleapis#18650](googleapis#18650))
([4eb0e71](googleapis@4eb0e71))
* **bigquery:** keep the Arrow schema for empty results
([googleapis#18655](googleapis#18655))
([3ccf5ac](googleapis@3ccf5ac))
* **bigquery:** pass universe_domain to the BigQuery Storage client
([googleapis#18783](googleapis#18783))
([6ab4063](googleapis@6ab4063))
* **bigquery:** raise when an Arrow batch arrives before its schema
([googleapis#18791](googleapis#18791))
([7944a4c](googleapis@7944a4c))
* **bigquery:** require bqstorage 2.43.0
([googleapis#18780](googleapis#18780))
([3cd40ce](googleapis@3cd40ce))
* **bigquery:** send the same Arrow options on jobs.query and ReadRows
([googleapis#18646](googleapis#18646))
([d7a2d27](googleapis@d7a2d27))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
feat: update Compute Engine v1 (preview) API to 2026-10-01-preview
revision 20261007

feat: add AliasNetworkEndpoints service and AliasNetworkEndpoint
resource messages

feat: add HaControllerParams and HaControllerInstanceNetworkInterface
messages to HaController

feat: add ProtectionTier enum and protection_tier field to Scheduling
and ResourceStatusScheduling

feat: add ip_collection to AccessConfig and network_attachment and
service_class_id to Address and NetworkEndpointGroup

feat: add MEMORY_OPTIMIZED_X5 values to Commitment.Type enum and
FAILURE_CUSTOMER_REPORTED to UpcomingMaintenance.MaintenanceReasons enum

feat: remove FLOW_MANAGEMENT_UNSPECIFIED from
WireProperties.FlowManagement enum

docs: see release notes
https://docs.cloud.google.com/compute/docs/release-notes
…s#18700)

🤖 I have created a release *beep* *boop*
---


##
[1.56.0-preview.2](googleapis/google-cloud-python@google-cloud-compute-v1.56.0-preview.1...google-cloud-compute-v1.56.0-preview.2)
(2026-10-29)


### Features

* add AliasNetworkEndpoints service and AliasNetworkEndpoint
([1f86109](googleapis@1f86109))
* add HaControllerParams and HaControllerInstanceNetworkInterface
([1f86109](googleapis@1f86109))
* add ip_collection to AccessConfig and network_attachment and
([1f86109](googleapis@1f86109))
* add MEMORY_OPTIMIZED_X5 values to Commitment.Type enum and
([1f86109](googleapis@1f86109))
* add ProtectionTier enum and protection_tier field to Scheduling
([1f86109](googleapis@1f86109))
* remove FLOW_MANAGEMENT_UNSPECIFIED from
([1f86109](googleapis@1f86109))
* update API sources and regenerate
([googleapis#18699](googleapis#18699))
([1aa57db](googleapis@1aa57db))
* update Compute Engine v1 (preview) API to 2026-10-01-preview
([1f86109](googleapis@1f86109))


### Documentation

* see release notes
([1f86109](googleapis@1f86109))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
Bigtable had pytest-asyncio pinned to an old version, which was
incompatible with newer pytest versions. This PR unpins it, and changes
how the event loop is set up to match new expectations
….mutate (googleapis#18874)

## Summary

Validate that rows passed to `MutationsBatcher.mutate(row)` (and by
extension `MutationsBatcher.mutate_rows(rows)`) are instances of
`DirectRow` and are not bound to a different table, matching the
existing validation in `Table.mutate_rows()`.

### Problem
When `MutationsBatcher` was refactored to wrap
`self.table._table_impl.mutations_batcher(...)` directly (googleapis#18200)
instead of delegating to `self.table.mutate_rows(...)`, two
row-validation checks from `Table.mutate_rows()` were dropped:
1. **`DirectRow` type check (`TypeError`)**: Passing a `ConditionalRow`
to `batcher.mutate(cond_row)` called `cond_row._get_mutations()` with
the default `state=None`, which returns `self._false_pb_mutations` and
unconditionally sends false-branch mutations via `MutateRows` without
evaluating the row filter predicate. Passing an `AppendRow` raised a raw
`AttributeError: 'AppendRow' object has no attribute '_get_mutations'`
instead of `TypeError`.
2. **Table match check (`TableMismatchError`)**: Passing a `DirectRow`
bound to another table silently sent its mutations to `batcher.table`
instead of raising `TableMismatchError`.

### Solution
- Validate `isinstance(row, DirectRow)` in `MutationsBatcher.mutate()`,
raising `TypeError("Bulk processing can not be applied for conditional
or append mutations.")` if not.
- Validate `row.table is None or row.table.name == self.table.name` in
`MutationsBatcher.mutate()`, raising `TableMismatchError` if the row
belongs to a different table.
- Document `:raises:` (`TypeError` and `TableMismatchError`) on
`MutationsBatcher.mutate()` and `MutationsBatcher.mutate_rows()`.

## Test Plan
- Added unit tests in
`packages/google-cloud-bigtable/tests/unit/v2_client/test_batcher.py`:
  - `test_mutations_batcher_mutate_w_bound_table_row`
- `test_mutations_batcher_mutate_wrong_row_type` (`ConditionalRow` and
`AppendRow` across `mutate` and `mutate_rows`, verifying no mutations
are queued)
- `test_mutations_batcher_mutate_table_mismatch` (cross-table
`DirectRow` across `mutate` and `mutate_rows`)
Some samples are still implemented in the legacy client only. This PR
implements them in the data or admin clients, and adds a suffix to the
region tags for the legacy client
…w -2**63 in int mutations (googleapis#18881)

## Summary

- **`DeleteRangeFromColumn` qualifier validation and encoding**:
`DeleteRangeFromColumn` did not encode `str` qualifiers to `bytes` in
`__post_init__`, causing `proto-plus` to treat string qualifiers as
base64 during `_to_pb()` serialization and either silently decode into
the wrong column bytes or fail with `binascii.Error`. `__post_init__`
now encodes `str` qualifiers to UTF-8 `bytes` and raises `TypeError` for
non-`bytes`/`str` inputs, matching `SetCell` and `AddToCell`.
- **Allow `-2**63` (`INT64_MIN`) in integer mutations and increment
rules**: `SetCell`, `AddToCell`, and `IncrementRule` checked `abs(val) >
(1 << 63) - 1`, which rejected the valid two's-complement minimum 64-bit
signed integer `-2**63` (`-9223372036854775808`) with `ValueError`.
Replaced the `abs()` check with `_MIN_INCREMENT_VALUE <= val <=
_MAX_INCREMENT_VALUE`.

## Test plan

- [x] Added unit tests in `tests/unit/data/test_mutations.py` and
`tests/unit/data/test_read_modify_write_rules.py` covering `-2**63` and
`2**63 - 1` bounds and `DeleteRangeFromColumn` `str` / invalid qualifier
handling.
…es (googleapis#18408)

Optimize request ID generation and RPC retry hot paths across Database,
Transaction, Snapshot, and Batch:
- Use `int.from_bytes(os.urandom(8), "big")` for process-level random ID
generation and pre-format the static process prefix once at import time.
- Cache per-database request ID prefixes directly in `instance.__dict__`
via a non-data descriptor (`_CachedPrefixDescriptor`), lazily computing
on first access and invalidating when `_channel_id` changes.
- Inline `Database.with_error_augmentation` and construct gRPC metadata
lists with unpacking (`[*prior_metadata, ...]`) instead of intermediate
list copies and `.append()`.
- Replace thread-safe `AtomicCounter` instances with local integer
counters (`attempt = 0`) inside sequential retry closures, and pass
active OpenTelemetry spans directly to avoid repeated `contextvars`
lookups.
- Eliminate transient `functools.partial` allocations and redundant
`*args, **kwargs` unpacking inside retry closures.
- Make `wrap_with_request_id` idempotent on repeated wrapping and
replace stale request IDs when retrying failed calls.
…oogleapis#18920)

When signing AWS STS GetCallerIdentity requests against regional FIPS
endpoints (`sts-fips.{region}.amazonaws.com`), the `Host` header must
remain `sts-fips.{region}.amazonaws.com`, whereas the AWS SigV4
credential scope and signing key derivation require the canonical
service name (`sts`).

Previously, `_generate_authentication_header_map` derived `service_name
= host.split(".")[0]`, which produced `sts-fips` and caused AWS STS to
reject the signed request with `SignatureDoesNotMatch: Credential should
be scoped to correct service: 'sts'`.

This change strips any trailing `-fips` suffix from the extracted
hostname label when computing `service_name`.

## Manual Testing
End-to-end reproduction script and observed pre-fix / post-fix output
against GCP STS (`https://sts.googleapis.com/v1/token`) and AWS STS
(`https://sts-fips.us-east-2.amazonaws.com`):
- https://paste.googleplex.com/6755399436486780
🤖 I have created a release *beep* *boop*
---


##
[2.62.1](googleapis/google-cloud-python@google-auth-v2.62.0...google-auth-v2.62.1)
(2026-11-02)


### Bug Fixes

* **auth:** strip -fips suffix from AWS service name in SigV4 signing
([googleapis#18920](googleapis#18920))
([2e1899e](googleapis@2e1899e))
* **auth:** use an unencrypted key for mTLS certificate rotation
([googleapis#18709](googleapis#18709))
([8166325](googleapis@8166325))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com>
…leapis#18641)

## Summary

`AsyncAppendableObjectWriter` overloads `generation`: `None` / `0`
creates a new object via
`WriteObjectSpec(if_generation_match=generation)`, while `N > 0` reopens
an existing object via `AppendObjectSpec(generation=N)`. There was no
way to pass `if_generation_match=N` (`N > 0`) on `WriteObjectSpec`
(overwrite only if the current generation matches `N`) or other write
preconditions (`if_generation_not_match`, `if_metageneration_match`,
`if_metageneration_not_match`).

This adds a `Preconditions` dataclass (`if_generation_match`,
`if_generation_not_match`, `if_metageneration_match`,
`if_metageneration_not_match`) accepted via `preconditions:
Optional[Preconditions] = None` on
`AsyncAppendableObjectWriter.__init__`,
`AsyncAppendableObjectWriter.from_blob`, and
`_AsyncWriteObjectStream.__init__`:
- Generation preconditions (`if_generation_match`,
`if_generation_not_match`) require `generation=None`. Combining them
with `generation` raises `ValueError`, including `from_blob` when
`blob.generation` is set. To overwrite a fetched Blob, pass
`generation=None` to `from_blob`, for example with
`Preconditions(if_generation_match=blob.generation)`. `generation=0`
without `preconditions` still means `if_generation_match=0`.
- Creating or overwriting (`generation` is `None` or `0`) sets all 4
fields on `WriteObjectSpec`. Appending to an existing object
(`generation > 0`) sets the metageneration preconditions on
`AppendObjectSpec`. Metageneration preconditions cannot be combined with
`write_handle` (raises `ValueError`), because GCS only checks them when
no write handle is sent.
- After the writer creates a generation, reconnects send
`AppendObjectSpec` without preconditions, because they describe the
object that was replaced.

Motivation: fsspec/gcsfs#1101 (compare-and-swap writes in gcsfs).

## Test plan

```
cd packages/google-cloud-storage
PYTHONPATH=$PWD python -m pytest tests/unit/asyncio/test_async_write_object_stream.py tests/unit/asyncio/test_async_appendable_object_writer.py   # 86 passed
ruff check --select I --target-version=py310 --line-length=88 <changed files>
ruff format --check --target-version=py310 --line-length=88 <changed files>
flake8 <changed files>
```

> This change was authored with an AI assistant; the description above
was reviewed by the author.
…thread (googleapis#18628)

## Summary

Hash read chunks of 1 MiB or more on a small worker pool instead of the asyncio event-loop thread, and write each chunk to its buffer only after its checksum matches. On `main`, `google_crc32c.value()` is 10-12% of the event-loop thread's samples at 2 GB/s per process (py-spy, 1 process x 16 streams), time during which no other stream on that loop is served. `google-crc32c` 1.9.0 and later release the GIL while hashing `bytes` of 1 MiB or more, so the workers run in parallel with the loop thread; this PR raises the minimum `google-crc32c` to 1.9.0.

## Changes

- **`asyncio/retry/reads_resumption_strategy.py`:**
  - Chunks of at least `_CRC32C_OFFLOAD_MIN_BYTES` (defaults to `getattr(google_crc32c, "GIL_RELEASE_THRESHOLD", 1024 * 1024)`; configurable via `GCS_PYTHON_CLIENT_CRC32C_OFFLOAD_MIN_BYTES`, where `0` verifies every chunk inline) that are not part of a full-object read are hashed on a 2-thread `ThreadPoolExecutor`; smaller chunks and full-object reads (which feed the rolling checksum) are hashed inline as before.
  - Offloaded chunks (and any chunks arriving behind one) are held in `state["pending_checksums"]` and written in arrival order once their checksum matches (`verify_pending_checksums`). `next_expected_offset` advances on receipt and `bytes_written` only on write, so a retry resumes right after the last verified byte.
  - On a chunk checksum mismatch (which raises `DataCorruption`, as inline verification does) or an exception on the worker (which is re-raised), `discard_pending_checksums` drops every held chunk, cancels queued hashes and rewinds each read to its written end. `recover_state_on_failure` verifies held chunks before a stream is reopened.
  - The pool is replaced in forked children (`os.register_at_fork`); once interpreter shutdown refuses new work, chunks are hashed inline.
- **`setup.py`, `testing/constraints-3.10.txt`, `storage-integration.yaml`:** require `google-crc32c >= 1.9.0`, the first release that hashes large `bytes` without holding the GIL.
- **`asyncio/async_multi_range_downloader.py`:** `download_ranges` writes verified chunks after each received message (holding at most `_CRC32C_MAX_PENDING = 32`), drains fully when an attempt ends or fails with a stream error, discards held chunks on cancellation or `DataCorruption`, and documents all of this.

## Usage

Enabled automatically for non-full-object read chunks of at least 1 MiB (`getattr(google_crc32c, "GIL_RELEASE_THRESHOLD", 1024 * 1024)`). Set `GCS_PYTHON_CLIENT_CRC32C_OFFLOAD_MIN_BYTES` to override the threshold in bytes, or `0` to verify every chunk inline on the event-loop thread.

## Memory

`download_ranges` bounds `pending_checksums` to `_CRC32C_MAX_PENDING = 32` chunks per call before draining on the event loop. For 2 MiB chunks, each held `_PendingChunk` retains both the `data` slice and the enclosing `BidiReadObjectResponse`, bounding pending memory per `download_ranges` call to at most ~128 MiB with the generated `proto-plus`/`upb` parser (~4 MiB per chunk across the `upb` arena copy and `.content` `bytes` copy) or ~64 MiB with the zero-copy parser in googleapis#18627 (~2 MiB per chunk sharing the gRPC buffer).

## Tests

`tests/unit/asyncio/retry/test_reads_resumption_strategy.py` and `tests/unit/asyncio/test_async_multi_range_downloader.py`: offload threshold, zero threshold and full-object exclusion; in-order writes behind a pending chunk; mismatch on an offloaded chunk (nothing from it on is written, offsets rewound, the retry requests the bytes again); a worker exception (queue emptied, queued futures cancelled); drain before a retry and at the end of an attempt; discard on cancellation; fork and shutdown behavior of the pool.

```
nox -s unit lint cover
```

## Benchmark

Setup: GCE `c4-standard-192`, Rapid bucket in the same zone, 8 processes x 16 concurrent `AsyncMultiRangeDownloader` streams (128 streams total), `4 x 16 MiB` ranges per `download_ranges()` call into preallocated buffers, 5 s warmup + 15 s timed window per round, 8 interleaved rounds per arm.

### 1. Off-Loop CRC32C Only vs. `main` (Neutral Alone)

| Arm | Mean (GB/s) | Median (GB/s) | Min - Max (GB/s) | vs. `main` (Mean / Median) |
| :--- | :---: | :---: | :---: | :---: |
| `main` (generated parser + inline CRC32C) | 9.62 | 9.83 | 8.25 - 10.70 | - |
| `offloop` only (`GCS_PYTHON_CLIENT_ZERO_COPY_BIDI_READ=0`) | 9.64 | 9.96 | 7.32 - 10.85 | +0.2% / +1.3% |

With the generated `proto-plus`/`upb` parser active, each 2 MiB chunk is still copied twice on the event-loop thread under the GIL (~254 us per message), so the loop remains saturated and offloading CRC32C alone is neutral.

### 2. Zero-Copy Only vs. Zero-Copy + Off-Loop CRC32C (Additive on Top of googleapis#18627)

| Arm | Mean (GB/s) | Median (GB/s) | Min - Max (GB/s) | vs. `main` (Mean / Median) | vs. Zero-Copy Only (Mean / Median) |
| :--- | :---: | :---: | :---: | :---: | :---: |
| `main` (generated parser + inline CRC32C) | 9.47 | 9.48 | 8.07 - 10.51 | - | - |
| `zero-copy` only (googleapis#18626 + googleapis#18627, inline CRC32C) | 11.24 | 11.07 | 9.62 - 13.73 | +18.6% / +16.7% | - |
| `zero-copy + offloop` (googleapis#18626 + googleapis#18627 + this PR) | 13.30 | 12.96 | 10.35 - 16.02 | +40.4% / +36.7% | +18.3% / +17.1% |

Once googleapis#18627 removes the two `upb`/`proto-plus` copies from the event-loop thread (254 us -> 4.7 us per 2 MiB message, +18.6% mean / +16.7% median over `main`), synchronous `google_crc32c.value()` (~100-200 us per 2 MiB chunk) becomes the largest remaining per-message work on the loop thread; offloading >= 1 MiB chunks onto the worker pool adds another +18.3% mean / +17.1% median (+40.4% mean / +36.7% median total over `main`).
…xtend() (googleapis#18626)

## Summary

`google_crc32c.value()`, `extend()` and `Checksum.update()` accept any contiguous bytes-like object (`memoryview`, `bytearray`, `array`, `bytes`) instead of `bytes` only, and the C extension releases the GIL for inputs of 1 MiB or more whose memory belongs to a `bytes` object (a `bytes` or a `memoryview` of one). That threshold is exported as `google_crc32c.GIL_RELEASE_THRESHOLD` (1 MiB).

Motivation: the storage client wants to verify chunks it receives as `memoryview` slices of the gRPC message without copying them into `bytes` first; today `value(memoryview(...))` raises `TypeError: argument 1 must be read-only bytes-like object`.

## Changes

- `src/google_crc32c/_crc32c.c`: parse the data argument with `y*` into a `Py_buffer` (released after hashing) instead of `y#`, which rejects every exporter with a release function. The 1 MiB GIL release applies when the exported memory is owned by a `bytes` object; a `bytearray` or other writable owner keeps the GIL, since another thread could write to it mid-hash.
- `src/google_crc32c/python.py`: the pure-Python implementation hashes the raw bytes of the same inputs (a multi-byte `array` or cast view is hashed byte for byte, as the C extension does, instead of item by item). `Checksum(initial_value)` no longer compares the initial value with `b""`, which raised for numpy arrays.
- `GIL_RELEASE_THRESHOLD` (1 MiB) is exported from `_crc32c.c`, `cext.py`, `python.py` and the package, so callers such as the storage client (googleapis#18628) can size the chunks they hash off the event loop.
- Docstrings in `cext.py`, `_checksum.py` and `python.py` say bytes-like.
- `tests/test___init__.py`: `value`/`extend` on `bytes`, `bytearray`, `memoryview` (sliced, writable, read-only, cast, 2-D) and `array` inputs, `BufferError` on non-contiguous views, and GIL probes: held for `bytearray` and read-only views of it, released for `bytes`, `memoryview(bytes)` and a slice of it.

## Tests

```
nox -s unit lint cover
```

184 passed, 4 skipped (numpy not installed) with the compiled extension; 89 passed, 99 skipped, 100% coverage in pure-Python mode (`nox -s unit cover`).

## Notes

- Behavior change in the pure-Python implementation for multi-byte `array`/cast inputs (previously coerced item by item). It now matches the C extension.
- The GIL rule (`bytes`-owned memory only) is a choice: `hashlib` and `zlib.crc32` release the GIL for any buffer. Holding it for writable owners avoids hashing memory another thread may be writing to; happy to drop that restriction if you prefer parity with `hashlib`.
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.