Skip to content

feat: provide and use Python version support check #832

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
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
12 changes: 12 additions & 0 deletions google/api_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@
This package contains common code and utilities used by Google client libraries.
"""

from google.api_core import _python_package_support
from google.api_core import _python_version_support
from google.api_core import version as api_core_version

__version__ = api_core_version.__version__

# TODO: Until dependent artifacts require this version of
# google.api_core, the functionality below must be made available
# manually in those artifacts.

check_python_version = _python_version_support.check_python_version
check_python_version(package="google.api_core")

check_dependency_versions = _python_package_support.check_dependency_versions
check_dependency_versions("google.api_core")
153 changes: 153 additions & 0 deletions google/api_core/_python_package_support.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Copyright 2025 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Code to check versions of dependencies used by Google Cloud Client Libraries."""

import logging
import sys
from typing import Optional
from ._python_version_support import (
_flatten_message,
_get_distribution_and_import_packages,
)

# It is a good practice to alias the Version class for clarity in type hints.
from packaging.version import parse as parse_version, Version as PackagingVersion


def get_dependency_version(dependency_name: str) -> Optional[PackagingVersion]:
"""Get the parsed version of an installed package dependency.

This function checks for an installed package and returns its version
as a `packaging.version.Version` object for safe comparison. It handles
both modern (Python 3.8+) and legacy (Python 3.7) environments.

Args:
dependency_name: The distribution name of the package (e.g., 'requests').

Returns:
A `packaging.version.Version` object, or `None` if the package
is not found or another error occurs during version discovery.
"""
try:
if sys.version_info >= (3, 8):
from importlib import metadata

version_string = metadata.version(dependency_name)
return parse_version(version_string)

# TODO: Remove this code path once we drop support for Python 3.7
else:
# Use pkg_resources, which is part of setuptools.
import pkg_resources

version_string = pkg_resources.get_distribution(dependency_name).version
return parse_version(version_string)

except Exception:
return None


def warn_deprecation_for_versions_less_than(
dependent_import_package: str,
dependency_import_package: str,
next_supported_version: str,
message_template: Optional[str] = None,
):
"""Issue any needed deprecation warnings for `dependency_import_package`.

If `dependency_import_package` is installed at a version less than
`next_supported_versions`, this issues a warning using either a
default `message_template` or one provided by the user. The
default `message_template informs users that they will not receive
future updates `dependent_import_package` if
`dependency_import_package` is somehow pinned to a version lower
than `next_supported_version`.

Args:
dependent_import_package: The import name of the package that
needs `dependency_import_package`.
dependency_import_package: The import name of the dependency to check.
next_supported_version: The version number below which a deprecation
warning will be logged.
message_template: A custom default message template to replace
the default. This `message_template` is treated as an
f-string, where the following variables are defined:
`dependency_import_package`, `dependent_import_package`;
`dependency_packages` and `dependent_packages`, which contain both the
distribution and import packages for the dependency and the dependent,
respectively; and `next_supported_version`, and `version_used`, which
refer to supported and currently-used versions of the dependency.

"""
if (
not dependent_import_package
or not dependency_import_package
or not next_supported_version
):
return
version_used = get_dependency_version(dependency_import_package)
if not version_used:
return
if version_used < parse_version(next_supported_version):
(
dependency_packages,
dependency_distribution_package,
) = _get_distribution_and_import_packages(dependency_import_package)
(
dependent_packages,
dependent_distribution_package,
) = _get_distribution_and_import_packages(dependent_import_package)
message_template = message_template or _flatten_message(
"""
DEPRECATION: Package {dependent_packages} depends on
{dependency_packages}, currently installed at version
{version_used.__str__}. Future updates to
{dependent_packages} will require {dependency_packages} at
version {next_supported_version} or higher. Please ensure
that either (a) your Python environment doesn't pin the
version of {dependency_packages}, so that updates to
{dependent_packages} can require the higher version, or
(b) you manually update your Python environment to use at
least version {next_supported_version} of
{dependency_packages}.
"""
)
logging.warning(
message_template.format(
dependent_import_package=dependent_import_package,
dependency_import_package=dependency_import_package,
dependency_packages=dependency_packages,
dependent_packages=dependent_packages,
next_supported_version=next_supported_version,
version_used=version_used,
)
)


def check_dependency_versions(dependent_import_package: str):
"""Bundle checks for all package dependencies.

This function can be called by all dependents of google.api_core,
to emit needed deprecation warnings for any of their
dependencies. The dependencies to check should be updated here.

Args:
dependent_import_package: The distribution name of the calling package, whose
dependencies we're checking.

"""
warn_deprecation_for_versions_less_than(
dependent_import_package, "google.protobuf", "4.25.8"
)
Loading
Loading