GeezerVison is showing github.com — feat(gapic): support mTLS certificates when available (#1566) · googleapis/python-pubsub@24761a2
This repository was archived by the owner on Oct 13, 2026. It is now read-only.
File treeExpand file treeCollapse file tree Open diff view settings tests/unit/gapic/pubsub_v1
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard File renamed without changes. Collapse file Copy file name to clipboardExpand all lines: .librarian/generator-input/setup.py -1Lines changed: 0 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 98 | 98 | install_requires=dependencies, | 99 | 99 | extras_require=extras, | 100 | 100 | python_requires=">=3.7", | 101 | | - scripts=["scripts/fixup_pubsub_v1_keywords.py"], | 102 | 101 | include_package_data=True, | 103 | 102 | zip_safe=False, | 104 | 103 | ) |
Collapse file Copy file name to clipboardExpand all lines: .librarian/state.yaml +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 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 | 2 | 2 | libraries: | 3 | 3 | - id: google-cloud-pubsub | 4 | 4 | version: 2.33.0 | |
Collapse file Copy file name to clipboardExpand all lines: google/pubsub_v1/__init__.py +104Lines changed: 104 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 15 | 15 | # | 16 | 16 | from google.pubsub_v1 import gapic_version as package_version | 17 | 17 |
| | 18 | +import google.api_core as api_core | | 19 | +import sys | | 20 | + | 18 | 21 | __version__ = package_version.__version__ | 19 | 22 |
| | 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 | + | 20 | 30 |
| 21 | 31 | from .services.publisher import PublisherClient | 22 | 32 | from .services.publisher import PublisherAsyncClient | | 94 | 104 | from .types.schema import Encoding | 95 | 105 | from .types.schema import SchemaView | 96 | 106 |
| | 107 | +if hasattr(api_core, "check_python_version") and hasattr( | | 108 | + api_core, "check_dependency_versions" | | 109 | +): # pragma: NO COVER | | 110 | + api_core.check_python_version("google.pubsub_v1") # type: ignore | | 111 | + api_core.check_dependency_versions("google.pubsub_v1") # type: ignore | | 112 | +else: # pragma: NO COVER | | 113 | + # An older version of api_core is installed which does not define the | | 114 | + # functions above. We do equivalent checks manually. | | 115 | + try: | | 116 | + import warnings | | 117 | + import sys | | 118 | + | | 119 | + _py_version_str = sys.version.split()[0] | | 120 | + _package_label = "google.pubsub_v1" | | 121 | + if sys.version_info < (3, 9): | | 122 | + warnings.warn( | | 123 | + "You are using a non-supported Python version " | | 124 | + + f"({_py_version_str}). Google will not post any further " | | 125 | + + f"updates to {_package_label} supporting this Python version. " | | 126 | + + "Please upgrade to the latest Python version, or at " | | 127 | + + f"least to Python 3.9, and then update {_package_label}.", | | 128 | + FutureWarning, | | 129 | + ) | | 130 | + if sys.version_info[:2] == (3, 9): | | 131 | + warnings.warn( | | 132 | + f"You are using a Python version ({_py_version_str}) " | | 133 | + + f"which Google will stop supporting in {_package_label} in " | | 134 | + + "January 2026. Please " | | 135 | + + "upgrade to the latest Python version, or at " | | 136 | + + "least to Python 3.10, before then, and " | | 137 | + + f"then update {_package_label}.", | | 138 | + FutureWarning, | | 139 | + ) | | 140 | + | | 141 | + def parse_version_to_tuple(version_string: str): | | 142 | + """Safely converts a semantic version string to a comparable tuple of integers. | | 143 | + Example: "4.25.8" -> (4, 25, 8) | | 144 | + Ignores non-numeric parts and handles common version formats. | | 145 | + Args: | | 146 | + version_string: Version string in the format "x.y.z" or "x.y.z<suffix>" | | 147 | + Returns: | | 148 | + Tuple of integers for the parsed version string. | | 149 | + """ | | 150 | + parts = [] | | 151 | + for part in version_string.split("."): | | 152 | + try: | | 153 | + parts.append(int(part)) | | 154 | + except ValueError: | | 155 | + # If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here. | | 156 | + # This is a simplification compared to 'packaging.parse_version', but sufficient | | 157 | + # for comparing strictly numeric semantic versions. | | 158 | + break | | 159 | + return tuple(parts) | | 160 | + | | 161 | + def _get_version(dependency_name): | | 162 | + try: | | 163 | + version_string: str = metadata.version(dependency_name) | | 164 | + parsed_version = parse_version_to_tuple(version_string) | | 165 | + return (parsed_version, version_string) | | 166 | + except Exception: | | 167 | + # Catch exceptions from metadata.version() (e.g., PackageNotFoundError) | | 168 | + # or errors during parse_version_to_tuple | | 169 | + return (None, "--") | | 170 | + | | 171 | + _dependency_package = "google.protobuf" | | 172 | + _next_supported_version = "4.25.8" | | 173 | + _next_supported_version_tuple = (4, 25, 8) | | 174 | + _recommendation = " (we recommend 6.x)" | | 175 | + (_version_used, _version_used_string) = _get_version(_dependency_package) | | 176 | + if _version_used and _version_used < _next_supported_version_tuple: | | 177 | + warnings.warn( | | 178 | + f"Package {_package_label} depends on " | | 179 | + + f"{_dependency_package}, currently installed at version " | | 180 | + + f"{_version_used_string}. Future updates to " | | 181 | + + f"{_package_label} will require {_dependency_package} at " | | 182 | + + f"version {_next_supported_version} or higher{_recommendation}." | | 183 | + + " Please ensure " | | 184 | + + "that either (a) your Python environment doesn't pin the " | | 185 | + + f"version of {_dependency_package}, so that updates to " | | 186 | + + f"{_package_label} can require the higher version, or " | | 187 | + + "(b) you manually update your Python environment to use at " | | 188 | + + f"least version {_next_supported_version} of " | | 189 | + + f"{_dependency_package}.", | | 190 | + FutureWarning, | | 191 | + ) | | 192 | + except Exception: | | 193 | + warnings.warn( | | 194 | + "Could not determine the version of Python " | | 195 | + + "currently being used. To continue receiving " | | 196 | + + "updates for {_package_label}, ensure you are " | | 197 | + + "using a supported version of Python; see " | | 198 | + + "https://devguide.python.org/versions/" | | 199 | + ) | | 200 | + | 97 | 201 | __all__ = ( | 98 | 202 | "PublisherAsyncClient", | 99 | 203 | "SchemaServiceAsyncClient", | |
Collapse file Copy file name to clipboardExpand all lines: google/pubsub_v1/services/publisher/client.py +32-14Lines changed: 32 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -168,6 +168,34 @@ def _get_default_mtls_endpoint(api_endpoint): | 168 | 168 | _DEFAULT_ENDPOINT_TEMPLATE = "pubsub.{UNIVERSE_DOMAIN}" | 169 | 169 | _DEFAULT_UNIVERSE = "googleapis.com" | 170 | 170 |
| | 171 | + @staticmethod | | 172 | + def _use_client_cert_effective(): | | 173 | + """Returns whether client certificate should be used for mTLS if the | | 174 | + google-auth version supports should_use_client_cert automatic mTLS enablement. | | 175 | +
| | 176 | + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. | | 177 | +
| | 178 | + Returns: | | 179 | + bool: whether client certificate should be used for mTLS | | 180 | + Raises: | | 181 | + ValueError: (If using a version of google-auth without should_use_client_cert and | | 182 | + GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) | | 183 | + """ | | 184 | + # check if google-auth version supports should_use_client_cert for automatic mTLS enablement | | 185 | + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER | | 186 | + return mtls.should_use_client_cert() | | 187 | + else: # pragma: NO COVER | | 188 | + # if unsupported, fallback to reading from env var | | 189 | + use_client_cert_str = os.getenv( | | 190 | + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | | 191 | + ).lower() | | 192 | + if use_client_cert_str not in ("true", "false"): | | 193 | + raise ValueError( | | 194 | + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" | | 195 | + " either `true` or `false`" | | 196 | + ) | | 197 | + return use_client_cert_str == "true" | | 198 | + | 171 | 199 | @classmethod | 172 | 200 | def from_service_account_info(cls, info: dict, *args, **kwargs): | 173 | 201 | """Creates an instance of this client using the provided credentials | @@ -427,20 +455,16 @@ def get_mtls_endpoint_and_cert_source( | 427 | 455 | ) | 428 | 456 | if client_options is None: | 429 | 457 | client_options = client_options_lib.ClientOptions() | 430 | | - use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") | | 458 | + use_client_cert = PublisherClient._use_client_cert_effective() | 431 | 459 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") | 432 | | - if use_client_cert not in ("true", "false"): | 433 | | - raise ValueError( | 434 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 435 | | - ) | 436 | 460 | if use_mtls_endpoint not in ("auto", "never", "always"): | 437 | 461 | raise MutualTLSChannelError( | 438 | 462 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 439 | 463 | ) | 440 | 464 |
| 441 | 465 | # Figure out the client cert source to use. | 442 | 466 | client_cert_source = None | 443 | | - if use_client_cert == "true": | | 467 | + if use_client_cert: | 444 | 468 | if client_options.client_cert_source: | 445 | 469 | client_cert_source = client_options.client_cert_source | 446 | 470 | elif mtls.has_default_client_cert_source(): | @@ -472,20 +496,14 @@ def _read_environment_variables(): | 472 | 496 | google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT | 473 | 497 | is not any of ["auto", "never", "always"]. | 474 | 498 | """ | 475 | | - use_client_cert = os.getenv( | 476 | | - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | 477 | | - ).lower() | | 499 | + use_client_cert = PublisherClient._use_client_cert_effective() | 478 | 500 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() | 479 | 501 | universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") | 480 | | - if use_client_cert not in ("true", "false"): | 481 | | - raise ValueError( | 482 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 483 | | - ) | 484 | 502 | if use_mtls_endpoint not in ("auto", "never", "always"): | 485 | 503 | raise MutualTLSChannelError( | 486 | 504 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 487 | 505 | ) | 488 | | - return use_client_cert == "true", use_mtls_endpoint, universe_domain_env | | 506 | + return use_client_cert, use_mtls_endpoint, universe_domain_env | 489 | 507 |
| 490 | 508 | @staticmethod | 491 | 509 | def _get_client_cert_source(provided_cert_source, use_cert_flag): | |
Collapse file Copy file name to clipboardExpand all lines: google/pubsub_v1/services/schema_service/client.py +32-14Lines changed: 32 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -153,6 +153,34 @@ def _get_default_mtls_endpoint(api_endpoint): | 153 | 153 | _DEFAULT_ENDPOINT_TEMPLATE = "pubsub.{UNIVERSE_DOMAIN}" | 154 | 154 | _DEFAULT_UNIVERSE = "googleapis.com" | 155 | 155 |
| | 156 | + @staticmethod | | 157 | + def _use_client_cert_effective(): | | 158 | + """Returns whether client certificate should be used for mTLS if the | | 159 | + google-auth version supports should_use_client_cert automatic mTLS enablement. | | 160 | +
| | 161 | + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. | | 162 | +
| | 163 | + Returns: | | 164 | + bool: whether client certificate should be used for mTLS | | 165 | + Raises: | | 166 | + ValueError: (If using a version of google-auth without should_use_client_cert and | | 167 | + GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) | | 168 | + """ | | 169 | + # check if google-auth version supports should_use_client_cert for automatic mTLS enablement | | 170 | + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER | | 171 | + return mtls.should_use_client_cert() | | 172 | + else: # pragma: NO COVER | | 173 | + # if unsupported, fallback to reading from env var | | 174 | + use_client_cert_str = os.getenv( | | 175 | + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | | 176 | + ).lower() | | 177 | + if use_client_cert_str not in ("true", "false"): | | 178 | + raise ValueError( | | 179 | + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" | | 180 | + " either `true` or `false`" | | 181 | + ) | | 182 | + return use_client_cert_str == "true" | | 183 | + | 156 | 184 | @classmethod | 157 | 185 | def from_service_account_info(cls, info: dict, *args, **kwargs): | 158 | 186 | """Creates an instance of this client using the provided credentials | @@ -335,20 +363,16 @@ def get_mtls_endpoint_and_cert_source( | 335 | 363 | ) | 336 | 364 | if client_options is None: | 337 | 365 | client_options = client_options_lib.ClientOptions() | 338 | | - use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") | | 366 | + use_client_cert = SchemaServiceClient._use_client_cert_effective() | 339 | 367 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") | 340 | | - if use_client_cert not in ("true", "false"): | 341 | | - raise ValueError( | 342 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 343 | | - ) | 344 | 368 | if use_mtls_endpoint not in ("auto", "never", "always"): | 345 | 369 | raise MutualTLSChannelError( | 346 | 370 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 347 | 371 | ) | 348 | 372 |
| 349 | 373 | # Figure out the client cert source to use. | 350 | 374 | client_cert_source = None | 351 | | - if use_client_cert == "true": | | 375 | + if use_client_cert: | 352 | 376 | if client_options.client_cert_source: | 353 | 377 | client_cert_source = client_options.client_cert_source | 354 | 378 | elif mtls.has_default_client_cert_source(): | @@ -380,20 +404,14 @@ def _read_environment_variables(): | 380 | 404 | google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT | 381 | 405 | is not any of ["auto", "never", "always"]. | 382 | 406 | """ | 383 | | - use_client_cert = os.getenv( | 384 | | - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | 385 | | - ).lower() | | 407 | + use_client_cert = SchemaServiceClient._use_client_cert_effective() | 386 | 408 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() | 387 | 409 | universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") | 388 | | - if use_client_cert not in ("true", "false"): | 389 | | - raise ValueError( | 390 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 391 | | - ) | 392 | 410 | if use_mtls_endpoint not in ("auto", "never", "always"): | 393 | 411 | raise MutualTLSChannelError( | 394 | 412 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 395 | 413 | ) | 396 | | - return use_client_cert == "true", use_mtls_endpoint, universe_domain_env | | 414 | + return use_client_cert, use_mtls_endpoint, universe_domain_env | 397 | 415 |
| 398 | 416 | @staticmethod | 399 | 417 | def _get_client_cert_source(provided_cert_source, use_cert_flag): | |
Collapse file Copy file name to clipboardExpand all lines: google/pubsub_v1/services/subscriber/client.py +32-14Lines changed: 32 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -172,6 +172,34 @@ def _get_default_mtls_endpoint(api_endpoint): | 172 | 172 | _DEFAULT_ENDPOINT_TEMPLATE = "pubsub.{UNIVERSE_DOMAIN}" | 173 | 173 | _DEFAULT_UNIVERSE = "googleapis.com" | 174 | 174 |
| | 175 | + @staticmethod | | 176 | + def _use_client_cert_effective(): | | 177 | + """Returns whether client certificate should be used for mTLS if the | | 178 | + google-auth version supports should_use_client_cert automatic mTLS enablement. | | 179 | +
| | 180 | + Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var. | | 181 | +
| | 182 | + Returns: | | 183 | + bool: whether client certificate should be used for mTLS | | 184 | + Raises: | | 185 | + ValueError: (If using a version of google-auth without should_use_client_cert and | | 186 | + GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.) | | 187 | + """ | | 188 | + # check if google-auth version supports should_use_client_cert for automatic mTLS enablement | | 189 | + if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER | | 190 | + return mtls.should_use_client_cert() | | 191 | + else: # pragma: NO COVER | | 192 | + # if unsupported, fallback to reading from env var | | 193 | + use_client_cert_str = os.getenv( | | 194 | + "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | | 195 | + ).lower() | | 196 | + if use_client_cert_str not in ("true", "false"): | | 197 | + raise ValueError( | | 198 | + "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be" | | 199 | + " either `true` or `false`" | | 200 | + ) | | 201 | + return use_client_cert_str == "true" | | 202 | + | 175 | 203 | @classmethod | 176 | 204 | def from_service_account_info(cls, info: dict, *args, **kwargs): | 177 | 205 | """Creates an instance of this client using the provided credentials | @@ -414,20 +442,16 @@ def get_mtls_endpoint_and_cert_source( | 414 | 442 | ) | 415 | 443 | if client_options is None: | 416 | 444 | client_options = client_options_lib.ClientOptions() | 417 | | - use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") | | 445 | + use_client_cert = SubscriberClient._use_client_cert_effective() | 418 | 446 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") | 419 | | - if use_client_cert not in ("true", "false"): | 420 | | - raise ValueError( | 421 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 422 | | - ) | 423 | 447 | if use_mtls_endpoint not in ("auto", "never", "always"): | 424 | 448 | raise MutualTLSChannelError( | 425 | 449 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 426 | 450 | ) | 427 | 451 |
| 428 | 452 | # Figure out the client cert source to use. | 429 | 453 | client_cert_source = None | 430 | | - if use_client_cert == "true": | | 454 | + if use_client_cert: | 431 | 455 | if client_options.client_cert_source: | 432 | 456 | client_cert_source = client_options.client_cert_source | 433 | 457 | elif mtls.has_default_client_cert_source(): | @@ -459,20 +483,14 @@ def _read_environment_variables(): | 459 | 483 | google.auth.exceptions.MutualTLSChannelError: If GOOGLE_API_USE_MTLS_ENDPOINT | 460 | 484 | is not any of ["auto", "never", "always"]. | 461 | 485 | """ | 462 | | - use_client_cert = os.getenv( | 463 | | - "GOOGLE_API_USE_CLIENT_CERTIFICATE", "false" | 464 | | - ).lower() | | 486 | + use_client_cert = SubscriberClient._use_client_cert_effective() | 465 | 487 | use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower() | 466 | 488 | universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN") | 467 | | - if use_client_cert not in ("true", "false"): | 468 | | - raise ValueError( | 469 | | - "Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`" | 470 | | - ) | 471 | 489 | if use_mtls_endpoint not in ("auto", "never", "always"): | 472 | 490 | raise MutualTLSChannelError( | 473 | 491 | "Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`" | 474 | 492 | ) | 475 | | - return use_client_cert == "true", use_mtls_endpoint, universe_domain_env | | 493 | + return use_client_cert, use_mtls_endpoint, universe_domain_env | 476 | 494 |
| 477 | 495 | @staticmethod | 478 | 496 | def _get_client_cert_source(provided_cert_source, use_cert_flag): | |
Collapse file Copy file name to clipboard File renamed without changes. Collapse file Copy file name to clipboardExpand all lines: noxfile.py +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 14 | 14 | # See the License for the specific language governing permissions and | 15 | 15 | # limitations under the License. | 16 | 16 |
| | 17 | +# DO NOT EDIT THIS FILE OUTSIDE OF `.librarian/generator-input` | | 18 | +# The source of truth for this file is `.librarian/generator-input` | | 19 | + | | 20 | + | 17 | 21 | # Generated by synthtool. DO NOT EDIT! | 18 | 22 |
| 19 | 23 | from __future__ import absolute_import | |
|