Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,8 @@
from .types.assets import TimeWindow
from .types.assets import VersionedResource

api_core.check_python_version("google.cloud.asset_v1")
api_core.check_dependency_versions("google.cloud.asset_v1")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'AssetServiceAsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@
from .types.common import SignJwtRequest
from .types.common import SignJwtResponse

api_core.check_python_version("google.iam.credentials_v1")
api_core.check_dependency_versions("google.iam.credentials_v1")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'IAMCredentialsAsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@
from .types.logging_metrics import LogMetric
from .types.logging_metrics import UpdateLogMetricRequest

api_core.check_python_version("google.cloud.logging_v2")
api_core.check_dependency_versions("google.cloud.logging_v2")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'ConfigServiceV2AsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@
from .types.logging_metrics import LogMetric
from .types.logging_metrics import UpdateLogMetricRequest

api_core.check_python_version("google.cloud.logging_v2")
api_core.check_dependency_versions("google.cloud.logging_v2")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'BaseConfigServiceV2AsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
from .types.cloud_redis import WeeklyMaintenanceWindow
from .types.cloud_redis import ZoneMetadata

api_core.check_python_version("google.cloud.redis_v1")
api_core.check_dependency_versions("google.cloud.redis_v1")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'CloudRedisAsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@
from .types.storage_batch_operations_types import RewriteObject
from .types.storage_batch_operations_types import UpdateObjectCustomContext

api_core.check_python_version("google.cloud.storagebatchoperations_v1")
api_core.check_dependency_versions("google.cloud.storagebatchoperations_v1")
api_core.check_python_version("")
api_core.check_dependency_versions("")

__all__ = (
'StorageBatchOperationsAsyncClient',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,37 @@
# about deprecated and unsupported versions.
DependencyConstraint = namedtuple(
"DependencyConstraint",
["package_name", "minimum_fully_supported_version", "recommended_version"],
[
"package_name",
"minimum_fully_supported_version",
"recommended_version",
"message_template",
],
defaults=(None, None),
)

PQC_GRPC_WARNING_TEMPLATE = (
"Package {consumer_package} depends on {dependency_package}, currently installed at version {version_used_string}. "
"grpcio < 1.83.0 does not support Post-Quantum Cryptography (PQC). "
"Support for non-PQC environments is deprecated. In October 2026, "
"Google Cloud Python packages will raise their minimum requirements "
"(including google-api-core, grpcio, and grpcio-status) to enforce grpcio >= 1.83.0. "
"For more details on Google Cloud's post-quantum security migration, visit: "
"https://cloud.google.com/security/resources/post-quantum-cryptography"
)

_PACKAGE_DEPENDENCY_WARNINGS = [
DependencyConstraint(
"google.protobuf",
minimum_fully_supported_version="6.33.5",
recommended_version="6.x",
)
),
DependencyConstraint(
"grpcio",
minimum_fully_supported_version="1.83.0",
recommended_version="1.83.x",
message_template=PQC_GRPC_WARNING_TEMPLATE,
),
]


Expand All @@ -50,8 +73,8 @@
def parse_version_to_tuple(version_string: str) -> ParsedVersion:
"""Safely converts a semantic version string to a comparable tuple of integers.

Example: "6.33.5" -> (6, 33, 5)
Ignores non-numeric parts and handles common version formats.
Example: "6.33.5" -> (6, 33, 5), "1.83.1rc1" -> (1, 83, 1)
Parses leading digits of each component to correctly handle pre-releases.

Args:
version_string: Version string in the format "x.y.z" or "x.y.z<suffix>"
Expand All @@ -61,12 +84,14 @@ def parse_version_to_tuple(version_string: str) -> ParsedVersion:
"""
parts = []
for part in version_string.split("."):
try:
parts.append(int(part))
except ValueError:
# If it's a non-numeric part (e.g., '1.0.0b1' -> 'b1'), stop here.
# This is a simplification compared to 'packaging.parse_version', but sufficient
# for comparing strictly numeric semantic versions.
digits = ""
for c in part:
if not c.isdigit():
break
digits += c
if digits:
parts.append(int(digits))
else:
break
return tuple(parts)

Expand Down Expand Up @@ -162,7 +187,7 @@ def warn_deprecation_for_versions_less_than(
) = _get_distribution_and_import_packages(consumer_import_package)

recommendation = (
" (we recommend {recommended_version})" if recommended_version else ""
f" (we recommend {recommended_version})" if recommended_version else ""
)
message_template = message_template or _flatten_message(
"""
Expand Down Expand Up @@ -222,4 +247,5 @@ def check_dependency_versions(
package_info.package_name,
package_info.minimum_fully_supported_version,
recommended_version=package_info.recommended_version,
message_template=package_info.message_template,
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import pytest

from google.api_core._python_package_support import (
PQC_GRPC_WARNING_TEMPLATE,
DependencyConstraint,
DependencyVersion,
check_dependency_versions,
Expand All @@ -37,14 +38,20 @@ def test_get_dependency_version(mocker, version_string_to_test):
parse_version_to_tuple(version_string_to_test), version_string_to_test
)
assert get_dependency_version("some-package") == expected

mock_importlib.assert_called_once_with("some-package")

# Test package not found
mock_importlib.side_effect = ImportError
mock_importlib.side_effect = Exception
assert get_dependency_version("not-a-package") == DependencyVersion(None, "--")


def test_parse_version_to_tuple_prerelease():
"""Test parse_version_to_tuple with pre-release versions."""
assert parse_version_to_tuple("1.83.1rc1") == (1, 83, 1)
assert parse_version_to_tuple("1.83.0dev0") == (1, 83, 0)
assert parse_version_to_tuple("6.33.5b2") == (6, 33, 5)


@patch("google.api_core._python_package_support._get_distribution_and_import_packages")
@patch("google.api_core._python_package_support.get_dependency_version")
def test_warn_deprecation_for_versions_less_than(mock_get_version, mock_get_packages):
Expand Down Expand Up @@ -125,8 +132,40 @@ def test_check_dependency_versions_with_custom_warnings(mock_warn):

assert mock_warn.call_count == 2
mock_warn.assert_any_call(
"my-consumer", "pkg1", "1.0.0", recommended_version="2.0.0"
"my-consumer",
"pkg1",
"1.0.0",
recommended_version="2.0.0",
message_template=None,
)
mock_warn.assert_any_call(
"my-consumer",
"pkg2",
"2.0.0",
recommended_version="3.0.0",
message_template=None,
)


@patch(
"google.api_core._python_package_support.warn_deprecation_for_versions_less_than"
)
def test_check_dependency_versions_default(mock_warn):
"""Test check_dependency_versions with default package dependency warnings."""
check_dependency_versions("my-consumer")

assert mock_warn.call_count == 2
mock_warn.assert_any_call(
"my-consumer",
"google.protobuf",
"6.33.5",
recommended_version="6.x",
message_template=None,
)
mock_warn.assert_any_call(
"my-consumer", "pkg2", "2.0.0", recommended_version="3.0.0"
"my-consumer",
"grpcio",
"1.83.0",
recommended_version="1.83.x",
message_template=PQC_GRPC_WARNING_TEMPLATE,
)
16 changes: 16 additions & 0 deletions packages/google-auth/google/auth/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,3 +534,19 @@ def response_log(logger: logging.Logger, response: Any) -> None:
if is_logging_enabled(logger):
json_response = _parse_response(response)
_response_log_base(logger, json_response)


def _parse_version_to_tuple(version_string):
"""Safely converts a semantic version string to a comparable tuple of integers."""
parts = []
for part in version_string.split("."):
digits = ""
for c in part:
if not c.isdigit():
break
digits += c
if digits:
parts.append(int(digits))
else:
break
return tuple(parts)
20 changes: 20 additions & 0 deletions packages/google-auth/google/auth/transport/grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import logging

from google.auth import _helpers
from google.auth import exceptions
from google.auth.transport import _mtls_helper
from google.auth.transport import mtls
Expand All @@ -30,6 +31,25 @@
"gRPC is not installed from please install the grpcio package to use the gRPC transport."
) from caught_exc


try:
import importlib.metadata
import warnings

_grpc_ver_str = importlib.metadata.version("grpcio")
if _helpers._parse_version_to_tuple(_grpc_ver_str) < (1, 83, 0):
warnings.warn(
"grpcio < 1.83.0 does not support Post-Quantum Cryptography (PQC). "
"Support for non-PQC environments is deprecated. In October 2026, "
"google-auth will raise its minimum requirements "
"to enforce grpcio >= 1.83.0. "
"For more details on Google Cloud's post-quantum security migration, visit: "
"https://cloud.google.com/security/resources/post-quantum-cryptography",
FutureWarning,
)
except Exception:
pass

_LOGGER = logging.getLogger(__name__)


Expand Down
7 changes: 7 additions & 0 deletions packages/google-auth/tests/test__helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,3 +697,10 @@ def test_out_of_range(self):
with pytest.raises((OverflowError, OSError, ValueError)):
# Large enough to fail on most systems (Year 300,000+)
_helpers.utcfromtimestamp(9999999999999)


def test_parse_version_to_tuple():
assert _helpers._parse_version_to_tuple("1.83.0") == (1, 83, 0)
assert _helpers._parse_version_to_tuple("1.83.1rc1") == (1, 83, 1)
assert _helpers._parse_version_to_tuple("1.83.0dev0") == (1, 83, 0)
assert _helpers._parse_version_to_tuple("2.0") == (2, 0)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@

__version__ = bigquery_version.__version__

import google.api_core as api_core

api_core.check_python_version("google.cloud.bigquery")
api_core.check_dependency_versions("google.cloud.bigquery")
from google.cloud.bigquery.client import Client
from google.cloud.bigquery.dataset import AccessEntry
from google.cloud.bigquery.dataset import Dataset
Expand Down Expand Up @@ -123,14 +127,6 @@
except ImportError:
bigquery_magics = None

if sys.version_info < (3, 10): # pragma: NO COVER
warnings.warn(
"The python-bigquery library no longer supports Python <= 3.9. "
f"Your Python version is {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}. We "
"recommend that you update soon to ensure ongoing support. For "
"more details, see: [Google Cloud Client Libraries Supported Python Versions policy](https://cloud.google.com/python/docs/supported-python-versions)",
FutureWarning,
)

__all__ = [
"__version__",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1214,3 +1214,4 @@ def test__valid_isinstance_or_raise(self, value, dtype, none_allowed, expected):
def test__invalid_isinstance_or_raise(self, value, dtype, none_allowed, expected):
with expected:
_isinstance_or_raise(value, dtype, none_allowed=none_allowed)

7 changes: 7 additions & 0 deletions packages/google-cloud-ndb/google/cloud/ndb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@
.. autodata:: __all__
"""

from google.cloud.ndb import utils
from google.cloud.ndb import version


import google.api_core as api_core

api_core.check_python_version("google.cloud.ndb")
api_core.check_dependency_versions("google.cloud.ndb")

__version__: str = version.__version__

from google.cloud.ndb._datastore_api import EVENTUAL, EVENTUAL_CONSISTENCY, STRONG
Expand Down
1 change: 1 addition & 0 deletions packages/google-cloud-ndb/google/cloud/ndb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,4 @@ def tweak_logging(*args, **kwargs):
def wrapping(*args, **kwargs):
"""Use functools.wraps instead"""
raise NotImplementedError

1 change: 1 addition & 0 deletions packages/google-cloud-ndb/tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ def test_tweak_logging():
def test_wrapping():
with pytest.raises(NotImplementedError):
utils.wrapping()

Loading