Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 75 additions & 17 deletions .librarian/generator-input/noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from __future__ import absolute_import
import os
import pathlib
import re
import shutil

import nox
Expand All @@ -28,7 +27,13 @@

DEFAULT_PYTHON_VERSION = "3.14"
SYSTEM_TEST_PYTHON_VERSIONS = ["3.10", "3.14"]
UNIT_TEST_PYTHON_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"]
UNIT_TEST_PYTHON_VERSIONS = [
"3.10",
"3.11",
"3.12",
"3.13",
"3.14",
]
CONFORMANCE_TEST_PYTHON_VERSIONS = ["3.12"]

CURRENT_DIRECTORY = pathlib.Path(__file__).parent.absolute()
Expand All @@ -39,6 +44,7 @@
nox.options.sessions = [
"blacken",
"conftest_retry",
"conftest_retry_bidi",
"docfx",
"docs",
"lint",
Expand All @@ -61,9 +67,7 @@ def lint(session):
Returns a failure if the linters find linting errors or sufficiently
serious code quality issues.
"""
# Pin flake8 to 6.0.0
# See https://github.com/googleapis/python-storage/issues/1102
session.install("flake8==6.0.0", BLACK_VERSION)
session.install("flake8", BLACK_VERSION)
session.run(
"black",
"--check",
Expand Down Expand Up @@ -116,6 +120,8 @@ def default(session, install_extras=True):

session.install("-e", ".", "-c", constraints_path)

session.run("python", "-m", "pip", "freeze")

# This dependency is included in setup.py for backwards compatibility only
# and the client library is expected to pass all tests without it. See
# setup.py and README for details.
Expand Down Expand Up @@ -180,7 +186,14 @@ def system(session):
# 2021-05-06: defer installing 'google-cloud-*' to after this package,
# in order to work around Python 2.7 googolapis-common-protos
# issue.
session.install("mock", "pytest", "pytest-rerunfailures", "-c", constraints_path)
session.install(
"mock",
"pytest",
"pytest-rerunfailures",
"pytest-asyncio",
"-c",
constraints_path,
)
session.install("-e", ".", "-c", constraints_path)
session.install(
"google-cloud-testutils",
Expand All @@ -207,30 +220,75 @@ def system(session):
@nox.session(python=CONFORMANCE_TEST_PYTHON_VERSIONS)
def conftest_retry(session):
"""Run the retry conformance test suite."""
conformance_test_folder_path = os.path.join("tests", "conformance")
conformance_test_folder_exists = os.path.exists(conformance_test_folder_path)
json_conformance_tests = "tests/conformance/test_conformance.py"
# Environment check: only run tests if found.
if not conformance_test_folder_exists:
if not os.path.exists(json_conformance_tests):
session.skip("Conformance tests were not found")

constraints_path = str(
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
)

# Install all test dependencies and pytest plugin to run tests in parallel.
# Then install this package in-place.
session.install("pytest", "pytest-xdist")
session.install("-e", ".")
session.install(
"pytest",
"pytest-xdist",
"-c",
constraints_path,
)
session.install("-e", ".", "-c", constraints_path)

# Run #CPU processes in parallel if no test session arguments are passed in.
if session.posargs:
test_cmd = [
"py.test",
"--quiet",
conformance_test_folder_path,
"pytest",
"-vv",
"-s",
json_conformance_tests,
*session.posargs,
]
else:
test_cmd = ["py.test", "-n", "auto", "--quiet", conformance_test_folder_path]
test_cmd = ["pytest", "-vv", "-s", "-n", "auto", json_conformance_tests]

# Run pytest against the conformance tests.
session.run(*test_cmd, env={"DOCKER_API_VERSION": "1.39"})


# Run py.test against the conformance tests.
session.run(*test_cmd)
@nox.session(python=CONFORMANCE_TEST_PYTHON_VERSIONS)
def conftest_retry_bidi(session):
"""Run the retry conformance test suite."""

constraints_path = str(
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
)

# Install all test dependencies and pytest plugin to run tests in parallel.
# Then install this package in-place.
session.install(
"pytest",
"pytest-xdist",
"pytest-asyncio",
"grpcio",
"grpcio-status",
"grpc-google-iam-v1",
"-c",
constraints_path,
)
session.install("-e", ".", "-c", constraints_path)

bidi_tests = [
"tests/conformance/test_bidi_reads.py",
"tests/conformance/test_bidi_writes.py",
]
for test_file in bidi_tests:
session.run(
"pytest",
"-vv",
"-s",
test_file,
env={"DOCKER_API_VERSION": "1.39"},
)
Comment on lines +280 to +291
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 conftest_retry_bidi session runs pytest in a loop for each test file. This is inefficient as it incurs pytest startup overhead for each file. You can improve performance by running pytest once with all test files passed as arguments.

    bidi_tests = [
        "tests/conformance/test_bidi_reads.py",
        "tests/conformance/test_bidi_writes.py",
    ]
    session.run(
        "pytest",
        "-vv",
        "-s",
        *bidi_tests,
        env={"DOCKER_API_VERSION": "1.39"},
    )



@nox.session(python=DEFAULT_PYTHON_VERSION)
Expand Down
38 changes: 37 additions & 1 deletion .librarian/generator-input/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,46 @@
"google-crc32c >= 1.1.3, < 2.0.0",
]
extras = {
# TODO: Make these extra dependencies as mandatory once gRPC out of
# experimental in this SDK. More info in b/465352227
"grpc": [
"google-api-core[grpc] >= 2.27.0, < 3.0.0",
"grpcio >= 1.33.2, < 2.0.0; python_version < '3.14'",
"grpcio >= 1.75.1, < 2.0.0; python_version >= '3.14'",
"grpcio-status >= 1.76.0, < 2.0.0",
"proto-plus >= 1.22.3, <2.0.0; python_version < '3.13'",
"proto-plus >= 1.25.0, <2.0.0; python_version >= '3.13'",
"protobuf>=3.20.2,<7.0.0,!=4.21.0,!=4.21.1,!=4.21.2,!=4.21.3,!=4.21.4,!=4.21.5",
"grpc-google-iam-v1 >= 0.14.0, <1.0.0",
],
"protobuf": ["protobuf >= 3.20.2, < 7.0.0"],
"tracing": [
"opentelemetry-api >= 1.1.0, < 2.0.0",
],
"testing": [
"google-cloud-testutils",
"numpy",
"psutil",
"py-cpuinfo",
"pytest-benchmark",
"PyYAML",
"mock",
"pytest",
"pytest-cov",
"pytest-asyncio",
"pytest-rerunfailures",
"pytest-xdist",
"google-cloud-testutils",
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 dependency google-cloud-testutils is listed twice in the testing extras (here and on line 63). Please remove this duplicate entry to keep the dependencies clean.

"google-cloud-iam",
"google-cloud-pubsub",
"google-cloud-kms",
"brotli",
"coverage",
"pyopenssl",
"opentelemetry-sdk",
"flake8",
"black",
],
}


Expand Down Expand Up @@ -99,7 +135,7 @@
packages=packages,
install_requires=dependencies,
extras_require=extras,
python_requires=">=3.7",
python_requires=">=3.10",
include_package_data=True,
zip_safe=False,
)
2 changes: 1 addition & 1 deletion .librarian/state.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-li
libraries:
- id: google-cloud-storage
version: 3.10.1
last_generated_commit: 5400ccce473c439885bd6bf2924fd242271bfcab
last_generated_commit: 9918ca7a6f52f62633a19885a83c91270a61725f
apis:
- path: google/storage/v2
service_config: storage_v2.yaml
Expand Down
2 changes: 1 addition & 1 deletion .repo-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"api_id": "storage.googleapis.com",
"requires_billing": true,
"default_version": "v2",
"codeowner_team": "@googleapis/cloud-sdk-python-team @googleapis/gcs-team @googleapis/gcs-fs",
"codeowner_team": "@googleapis/yoshi-python @googleapis/gcs-sdk-team @googleapis/gcs-fs",
"api_shortname": "storage",
"api_description": "is a durable and highly available object storage service. Google Cloud Storage is almost infinitely scalable and guarantees consistency: when a write succeeds, the latest copy of the object will be returned to any GET, globally."
}
5 changes: 3 additions & 2 deletions google/cloud/_storage_v2/services/storage/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2472,8 +2472,9 @@ def bidi_read_object(
across several messages. If an error occurs with any request,
the stream closes with a relevant error code. Since you can have
multiple outstanding requests, the error response includes a
``BidiReadObjectRangesError`` field detailing the specific error
for each pending ``read_id``.
``BidiReadObjectError`` proto in its ``details`` field,
reporting the specific error, if any, for each pending
``read_id``.

**IAM Permissions**:

Expand Down
51 changes: 17 additions & 34 deletions google/cloud/_storage_v2/services/storage/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,34 +184,6 @@ def _get_default_mtls_endpoint(api_endpoint):
_DEFAULT_ENDPOINT_TEMPLATE = "storage.{UNIVERSE_DOMAIN}"
_DEFAULT_UNIVERSE = "googleapis.com"

@staticmethod
def _use_client_cert_effective():
"""Returns whether client certificate should be used for mTLS if the
google-auth version supports should_use_client_cert automatic mTLS enablement.

Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.

Returns:
bool: whether client certificate should be used for mTLS
Raises:
ValueError: (If using a version of google-auth without should_use_client_cert and
GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
"""
# check if google-auth version supports should_use_client_cert for automatic mTLS enablement
if hasattr(mtls, "should_use_client_cert"):
return mtls.should_use_client_cert()
else:
# if unsupported, fallback to reading from env var
use_client_cert_str = os.getenv(
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
).lower()
if use_client_cert_str not in ("true", "false"):
raise ValueError(
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
" either `true` or `false`"
)
return use_client_cert_str == "true"

@classmethod
def from_service_account_info(cls, info: dict, *args, **kwargs):
"""Creates an instance of this client using the provided credentials
Expand Down Expand Up @@ -418,16 +390,20 @@ def get_mtls_endpoint_and_cert_source(
)
if client_options is None:
client_options = client_options_lib.ClientOptions()
use_client_cert = StorageClient._use_client_cert_effective()
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
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.

high

The value of the GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable is not converted to lowercase here. This is inconsistent with the _read_environment_variables method (line 438) and means that values like 'True' or 'TRUE' will raise a ValueError instead of being correctly interpreted. To handle the environment variable case-insensitively and improve robustness, please add .lower().

Suggested change
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()

use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
if use_client_cert not in ("true", "false"):
raise ValueError(
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
)
if use_mtls_endpoint not in ("auto", "never", "always"):
raise MutualTLSChannelError(
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
)

# Figure out the client cert source to use.
client_cert_source = None
if use_client_cert:
if use_client_cert == "true":
if client_options.client_cert_source:
client_cert_source = client_options.client_cert_source
elif mtls.has_default_client_cert_source():
Expand Down Expand Up @@ -459,14 +435,20 @@ def _read_environment_variables():
google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
is not any of ["auto", "never", "always"].
"""
use_client_cert = StorageClient._use_client_cert_effective()
use_client_cert = os.getenv(
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
).lower()
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
if use_client_cert not in ("true", "false"):
raise ValueError(
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
)
if use_mtls_endpoint not in ("auto", "never", "always"):
raise MutualTLSChannelError(
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
)
return use_client_cert, use_mtls_endpoint, universe_domain_env
return use_client_cert == "true", use_mtls_endpoint, universe_domain_env

@staticmethod
def _get_client_cert_source(provided_cert_source, use_cert_flag):
Expand Down Expand Up @@ -2899,8 +2881,9 @@ def bidi_read_object(
across several messages. If an error occurs with any request,
the stream closes with a relevant error code. Since you can have
multiple outstanding requests, the error response includes a
``BidiReadObjectRangesError`` field detailing the specific error
for each pending ``read_id``.
``BidiReadObjectError`` proto in its ``details`` field,
reporting the specific error, if any, for each pending
``read_id``.

**IAM Permissions**:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -946,8 +946,9 @@ def bidi_read_object(
across several messages. If an error occurs with any request,
the stream closes with a relevant error code. Since you can have
multiple outstanding requests, the error response includes a
``BidiReadObjectRangesError`` field detailing the specific error
for each pending ``read_id``.
``BidiReadObjectError`` proto in its ``details`` field,
reporting the specific error, if any, for each pending
``read_id``.

**IAM Permissions**:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -971,8 +971,9 @@ def bidi_read_object(
across several messages. If an error occurs with any request,
the stream closes with a relevant error code. Since you can have
multiple outstanding requests, the error response includes a
``BidiReadObjectRangesError`` field detailing the specific error
for each pending ``read_id``.
``BidiReadObjectError`` proto in its ``details`` field,
reporting the specific error, if any, for each pending
``read_id``.

**IAM Permissions**:

Expand Down
Loading
Loading