Skip to content

Commit 7e99258

Browse files
authored
feat(gapic): support mTLS certificates when available (#717)
chore: librarian update image pull request: 20251216T195732Z
1 parent 724cc7d commit 7e99258

File tree

15 files changed

+899
-320
lines changed

15 files changed

+899
-320
lines changed

.librarian/state.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:ce48ed695c727f7e13efd1fd68f466a55a0d772c87b69158720cec39965bc8b2
1+
image: us-central1-docker.pkg.dev/cloud-sdk-librarian-prod/images-prod/python-librarian-generator@sha256:b8058df4c45e9a6e07f6b4d65b458d0d059241dd34c814f151c8bf6b89211209
22
libraries:
33
- id: google-cloud-error-reporting
44
version: 1.13.0

docs/conf.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
# -*- coding: utf-8 -*-
2-
# Copyright 2024 Google LLC
2+
# Copyright 2025 Google LLC
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
66
# You may obtain a copy of the License at
77
#
8-
# http://www.apache.org/licenses/LICENSE-2.0
8+
# http://www.apache.org/licenses/LICENSE-2.0
99
#
1010
# Unless required by applicable law or agreed to in writing, software
1111
# distributed under the License is distributed on an "AS IS" BASIS,
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16+
#
1617
# google-cloud-error-reporting documentation build configuration file
1718
#
1819
# This file is execfile()d with the current directory set to its
@@ -42,7 +43,7 @@
4243
# -- General configuration ------------------------------------------------
4344

4445
# If your documentation needs a minimal Sphinx version, state it here.
45-
needs_sphinx = "1.5.5"
46+
needs_sphinx = "4.5.0"
4647

4748
# Add any Sphinx extension module names here, as strings. They can be
4849
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
@@ -81,7 +82,7 @@
8182

8283
# General information about the project.
8384
project = "google-cloud-error-reporting"
84-
copyright = "2019, Google"
85+
copyright = "2025, Google, LLC"
8586
author = "Google APIs"
8687

8788
# The version info for the project you're documenting, acts as replacement for
@@ -156,7 +157,7 @@
156157
html_theme_options = {
157158
"description": "Google Cloud Client Libraries for google-cloud-error-reporting",
158159
"github_user": "googleapis",
159-
"github_repo": "python-error-reporting",
160+
"github_repo": "google-cloud-python",
160161
"github_banner": True,
161162
"font_family": "'Roboto', Georgia, sans",
162163
"head_font_family": "'Roboto', Georgia, serif",
@@ -266,13 +267,13 @@
266267

267268
latex_elements = {
268269
# The paper size ('letterpaper' or 'a4paper').
269-
#'papersize': 'letterpaper',
270+
# 'papersize': 'letterpaper',
270271
# The font size ('10pt', '11pt' or '12pt').
271-
#'pointsize': '10pt',
272+
# 'pointsize': '10pt',
272273
# Additional stuff for the LaTeX preamble.
273-
#'preamble': '',
274+
# 'preamble': '',
274275
# Latex figure (float) alignment
275-
#'figure_align': 'htbp',
276+
# 'figure_align': 'htbp',
276277
}
277278

278279
# Grouping the document tree into LaTeX files. List of tuples

google/cloud/errorreporting_v1beta1/__init__.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@
1515
#
1616
from google.cloud.errorreporting_v1beta1 import gapic_version as package_version
1717

18+
import google.api_core as api_core
19+
import sys
20+
1821
__version__ = package_version.__version__
1922

23+
if sys.version_info >= (3, 8): # pragma: NO COVER
24+
from importlib import metadata
25+
else: # pragma: NO COVER
26+
# TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
27+
# this code path once we drop support for Python 3.7
28+
import importlib_metadata as metadata
29+
2030

2131
from .services.error_group_service import ErrorGroupServiceClient
2232
from .services.error_group_service import ErrorGroupServiceAsyncClient
@@ -51,6 +61,100 @@
5161
from .types.report_errors_service import ReportErrorEventRequest
5262
from .types.report_errors_service import ReportErrorEventResponse
5363

64+
if hasattr(api_core, "check_python_version") and hasattr(
65+
api_core, "check_dependency_versions"
66+
): # pragma: NO COVER
67+
api_core.check_python_version("google.cloud.errorreporting_v1beta1") # type: ignore
68+
api_core.check_dependency_versions("google.cloud.errorreporting_v1beta1") # type: ignore
69+
else: # pragma: NO COVER
70+
# An older version of api_core is installed which does not define the
71+
# functions above. We do equivalent checks manually.
72+
try:
73+
import warnings
74+
import sys
75+
76+
_py_version_str = sys.version.split()[0]
77+
_package_label = "google.cloud.errorreporting_v1beta1"
78+
if sys.version_info < (3, 9):
79+
warnings.warn(
80+
"You are using a non-supported Python version "
81+
+ f"({_py_version_str}). Google will not post any further "
82+
+ f"updates to {_package_label} supporting this Python version. "
83+
+ "Please upgrade to the latest Python version, or at "
84+
+ f"least to Python 3.9, and then update {_package_label}.",
85+
FutureWarning,
86+
)
87+
if sys.version_info[:2] == (3, 9):
88+
warnings.warn(
89+
f"You are using a Python version ({_py_version_str}) "
90+
+ f"which Google will stop supporting in {_package_label} in "
91+
+ "January 2026. Please "
92+
+ "upgrade to the latest Python version, or at "
93+
+ "least to Python 3.10, before then, and "
94+
+ f"then update {_package_label}.",
95+
FutureWarning,
96+
)
97+
98+
def parse_version_to_tuple(version_string: str):
99+
"""Safely converts a semantic version string to a comparable tuple of integers.
100+
Example: "4.25.8" -> (4, 25, 8)
101+
Ignores non-numeric parts and handles common version formats.
102+
Args:
103+
version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
104+
Returns:
105+
Tuple of integers for the parsed version string.
106+
"""
107+
parts = []
108+
for part in version_string.split("."):
109+
try:
110+
parts.append(int(part))
111+
except ValueError:
112+
# If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
113+
# This is a simplification compared to 'packaging.parse_version', but sufficient
114+
# for comparing strictly numeric semantic versions.
115+
break
116+
return tuple(parts)
117+
118+
def _get_version(dependency_name):
119+
try:
120+
version_string: str = metadata.version(dependency_name)
121+
parsed_version = parse_version_to_tuple(version_string)
122+
return (parsed_version, version_string)
123+
except Exception:
124+
# Catch exceptions from metadata.version() (e.g., PackageNotFoundError)
125+
# or errors during parse_version_to_tuple
126+
return (None, "--")
127+
128+
_dependency_package = "google.protobuf"
129+
_next_supported_version = "4.25.8"
130+
_next_supported_version_tuple = (4, 25, 8)
131+
_recommendation = " (we recommend 6.x)"
132+
(_version_used, _version_used_string) = _get_version(_dependency_package)
133+
if _version_used and _version_used < _next_supported_version_tuple:
134+
warnings.warn(
135+
f"Package {_package_label} depends on "
136+
+ f"{_dependency_package}, currently installed at version "
137+
+ f"{_version_used_string}. Future updates to "
138+
+ f"{_package_label} will require {_dependency_package} at "
139+
+ f"version {_next_supported_version} or higher{_recommendation}."
140+
+ " Please ensure "
141+
+ "that either (a) your Python environment doesn't pin the "
142+
+ f"version of {_dependency_package}, so that updates to "
143+
+ f"{_package_label} can require the higher version, or "
144+
+ "(b) you manually update your Python environment to use at "
145+
+ f"least version {_next_supported_version} of "
146+
+ f"{_dependency_package}.",
147+
FutureWarning,
148+
)
149+
except Exception:
150+
warnings.warn(
151+
"Could not determine the version of Python "
152+
+ "currently being used. To continue receiving "
153+
+ "updates for {_package_label}, ensure you are "
154+
+ "using a supported version of Python; see "
155+
+ "https://devguide.python.org/versions/"
156+
)
157+
54158
__all__ = (
55159
"ErrorGroupServiceAsyncClient",
56160
"ErrorStatsServiceAsyncClient",

google/cloud/errorreporting_v1beta1/services/error_group_service/client.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,34 @@ def _get_default_mtls_endpoint(api_endpoint):
148148
_DEFAULT_ENDPOINT_TEMPLATE = "clouderrorreporting.{UNIVERSE_DOMAIN}"
149149
_DEFAULT_UNIVERSE = "googleapis.com"
150150

151+
@staticmethod
152+
def _use_client_cert_effective():
153+
"""Returns whether client certificate should be used for mTLS if the
154+
google-auth version supports should_use_client_cert automatic mTLS enablement.
155+
156+
Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.
157+
158+
Returns:
159+
bool: whether client certificate should be used for mTLS
160+
Raises:
161+
ValueError: (If using a version of google-auth without should_use_client_cert and
162+
GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
163+
"""
164+
# check if google-auth version supports should_use_client_cert for automatic mTLS enablement
165+
if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER
166+
return mtls.should_use_client_cert()
167+
else: # pragma: NO COVER
168+
# if unsupported, fallback to reading from env var
169+
use_client_cert_str = os.getenv(
170+
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
171+
).lower()
172+
if use_client_cert_str not in ("true", "false"):
173+
raise ValueError(
174+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
175+
" either `true` or `false`"
176+
)
177+
return use_client_cert_str == "true"
178+
151179
@classmethod
152180
def from_service_account_info(cls, info: dict, *args, **kwargs):
153181
"""Creates an instance of this client using the provided credentials
@@ -330,20 +358,16 @@ def get_mtls_endpoint_and_cert_source(
330358
)
331359
if client_options is None:
332360
client_options = client_options_lib.ClientOptions()
333-
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
361+
use_client_cert = ErrorGroupServiceClient._use_client_cert_effective()
334362
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
335-
if use_client_cert not in ("true", "false"):
336-
raise ValueError(
337-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
338-
)
339363
if use_mtls_endpoint not in ("auto", "never", "always"):
340364
raise MutualTLSChannelError(
341365
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
342366
)
343367

344368
# Figure out the client cert source to use.
345369
client_cert_source = None
346-
if use_client_cert == "true":
370+
if use_client_cert:
347371
if client_options.client_cert_source:
348372
client_cert_source = client_options.client_cert_source
349373
elif mtls.has_default_client_cert_source():
@@ -375,20 +399,14 @@ def _read_environment_variables():
375399
google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
376400
is not any of ["auto", "never", "always"].
377401
"""
378-
use_client_cert = os.getenv(
379-
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
380-
).lower()
402+
use_client_cert = ErrorGroupServiceClient._use_client_cert_effective()
381403
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
382404
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
383-
if use_client_cert not in ("true", "false"):
384-
raise ValueError(
385-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
386-
)
387405
if use_mtls_endpoint not in ("auto", "never", "always"):
388406
raise MutualTLSChannelError(
389407
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
390408
)
391-
return use_client_cert == "true", use_mtls_endpoint, universe_domain_env
409+
return use_client_cert, use_mtls_endpoint, universe_domain_env
392410

393411
@staticmethod
394412
def _get_client_cert_source(provided_cert_source, use_cert_flag):

google/cloud/errorreporting_v1beta1/services/error_stats_service/client.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,34 @@ def _get_default_mtls_endpoint(api_endpoint):
151151
_DEFAULT_ENDPOINT_TEMPLATE = "clouderrorreporting.{UNIVERSE_DOMAIN}"
152152
_DEFAULT_UNIVERSE = "googleapis.com"
153153

154+
@staticmethod
155+
def _use_client_cert_effective():
156+
"""Returns whether client certificate should be used for mTLS if the
157+
google-auth version supports should_use_client_cert automatic mTLS enablement.
158+
159+
Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.
160+
161+
Returns:
162+
bool: whether client certificate should be used for mTLS
163+
Raises:
164+
ValueError: (If using a version of google-auth without should_use_client_cert and
165+
GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
166+
"""
167+
# check if google-auth version supports should_use_client_cert for automatic mTLS enablement
168+
if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER
169+
return mtls.should_use_client_cert()
170+
else: # pragma: NO COVER
171+
# if unsupported, fallback to reading from env var
172+
use_client_cert_str = os.getenv(
173+
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
174+
).lower()
175+
if use_client_cert_str not in ("true", "false"):
176+
raise ValueError(
177+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
178+
" either `true` or `false`"
179+
)
180+
return use_client_cert_str == "true"
181+
154182
@classmethod
155183
def from_service_account_info(cls, info: dict, *args, **kwargs):
156184
"""Creates an instance of this client using the provided credentials
@@ -333,20 +361,16 @@ def get_mtls_endpoint_and_cert_source(
333361
)
334362
if client_options is None:
335363
client_options = client_options_lib.ClientOptions()
336-
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
364+
use_client_cert = ErrorStatsServiceClient._use_client_cert_effective()
337365
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
338-
if use_client_cert not in ("true", "false"):
339-
raise ValueError(
340-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
341-
)
342366
if use_mtls_endpoint not in ("auto", "never", "always"):
343367
raise MutualTLSChannelError(
344368
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
345369
)
346370

347371
# Figure out the client cert source to use.
348372
client_cert_source = None
349-
if use_client_cert == "true":
373+
if use_client_cert:
350374
if client_options.client_cert_source:
351375
client_cert_source = client_options.client_cert_source
352376
elif mtls.has_default_client_cert_source():
@@ -378,20 +402,14 @@ def _read_environment_variables():
378402
google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT
379403
is not any of ["auto", "never", "always"].
380404
"""
381-
use_client_cert = os.getenv(
382-
"GOOGLE_API_USE_CLIENT_CERTIFICATE", "false"
383-
).lower()
405+
use_client_cert = ErrorStatsServiceClient._use_client_cert_effective()
384406
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
385407
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
386-
if use_client_cert not in ("true", "false"):
387-
raise ValueError(
388-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
389-
)
390408
if use_mtls_endpoint not in ("auto", "never", "always"):
391409
raise MutualTLSChannelError(
392410
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
393411
)
394-
return use_client_cert == "true", use_mtls_endpoint, universe_domain_env
412+
return use_client_cert, use_mtls_endpoint, universe_domain_env
395413

396414
@staticmethod
397415
def _get_client_cert_source(provided_cert_source, use_cert_flag):

0 commit comments

Comments
 (0)