Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
6316d70
feat: provide Python run-time version support
vchudnov-g Jul 29, 2025
a136ad7
feat: apply Python version suport warnings to api_core
vchudnov-g Jul 29, 2025
45cd647
feat: add deprecation check for the protobuf package
vchudnov-g Jul 30, 2025
25225fa
format files
vchudnov-g Jul 30, 2025
1d567c0
fix lint warning
vchudnov-g Jul 30, 2025
f1dbbb4
add docstring to `warn_deprecation_for_versions_less_than`
vchudnov-g Jul 30, 2025
e3fd56f
Add/fix docstrings
vchudnov-g Jul 30, 2025
8b1dfb1
fix typo
vchudnov-g Jul 31, 2025
4b9208e
add test for _python_package_support.py
vchudnov-g Jul 31, 2025
5cf8652
add constants for various buffer periods
vchudnov-g Jul 31, 2025
7d8b1c7
Update warning code to only require import package names
vchudnov-g Jul 31, 2025
cda8e27
Fix messaegs and test
vchudnov-g Jul 31, 2025
db92fac
Add TODO: provide the functionality in previous versions of api_core
vchudnov-g Jul 31, 2025
118a7c8
Fix mypy failures
vchudnov-g Aug 12, 2025
f1c46aa
Try to remove a round-off error causing a test mock failure
vchudnov-g Aug 12, 2025
474ec0d
Remove more potential test failures/warnings
vchudnov-g Aug 12, 2025
2083b49
Format
vchudnov-g Aug 12, 2025
4ea124e
Try making the specified_timeout a float
vchudnov-g Aug 12, 2025
c5949c4
fix: tweak message parameter names
vchudnov-g Sep 3, 2025
6fc1473
fix: add PYTHON_VERSION_STATUS_UNSPECIFIED enum value
vchudnov-g Sep 3, 2025
8829e20
docs: tweak TODOs
vchudnov-g Sep 3, 2025
bdb6260
fix test to match code changes
vchudnov-g Sep 3, 2025
7896664
fix: restore asyncio designator for async tests
vchudnov-g Sep 4, 2025
54a5611
Remove some workarounds trying to fix presubmit errors (since fixed)
vchudnov-g Sep 5, 2025
2e2990b
Additional test tweaks to prevent non-significant failures
vchudnov-g Sep 5, 2025
35a2074
chore: fix test that was waiting before initiating operation
vchudnov-g Sep 5, 2025
cd64832
fix: skip coverage checks for code specific to Pyton 3.7
vchudnov-g Sep 5, 2025
e36414a
chore: try to address coverage failures
vchudnov-g Sep 8, 2025
b6245ca
chore: try to address more coverage failures
vchudnov-g Sep 8, 2025
3a897ba
chore: fix comments and exported function
vchudnov-g Sep 9, 2025
814ea63
fix lint
vchudnov-g Sep 9, 2025
8dee04b
chore: fix unit test
vchudnov-g Sep 9, 2025
95f4777
chore: lint
vchudnov-g Sep 9, 2025
a34ec15
Use warnings module instead of logging module
vchudnov-g Sep 10, 2025
a04e2e6
fix: print the current package versions correctly
vchudnov-g Sep 15, 2025
8b3f337
wip: before gemini doc fixes
vchudnov-g Sep 15, 2025
29896cf
fix: return tuple of version tuple and version string, fix tests
vchudnov-g Sep 16, 2025
d421f98
lint
vchudnov-g Sep 22, 2025
ac76f7a
feat: add grace period for 3.9; tweak warning
vchudnov-g Oct 1, 2025
e093a96
lint
vchudnov-g Oct 1, 2025
187f848
allow providing a recommended version
vchudnov-g Oct 1, 2025
b575a16
refactor: used namedtuple
vchudnov-g Oct 13, 2025
ecb7211
refactor: s/"--"/UNKNOWN_VERSION_STRING/ in code, not test
vchudnov-g Oct 13, 2025
3c587cf
refactor s/next_supported_version/minimum_fully_supported_version/
vchudnov-g Oct 13, 2025
fbe0f0b
refactor: s/dependent_/consumer_/
vchudnov-g Oct 13, 2025
28b0b32
refactor: add _PACKAGE_DEPENDENCY_WARNINGS
vchudnov-g Oct 13, 2025
21c9aec
refactor: reorder and comment on _init__ statements for clarity
vchudnov-g Oct 13, 2025
e24c13d
fix: exapnd VersionInfo with version field; clarify fake dates
vchudnov-g Oct 13, 2025
dbc3a26
doc: document PythonVersionSupportStatus
vchudnov-g Oct 13, 2025
d472bae
refactor: make fake past and future versions into constants
vchudnov-g Oct 14, 2025
0550f83
refactor: reword default string for min_python
vchudnov-g Oct 14, 2025
491b695
fix: fix populating PYTHON_VERSION_INFO
vchudnov-g Oct 14, 2025
6338389
fix: type hints
vchudnov-g Oct 14, 2025
05bcf6f
feat: allow check_dependency_versions() to take any number of Depende…
vchudnov-g Oct 17, 2025
fc224b7
add unit test to satisfy coverage requirement
vchudnov-g Oct 17, 2025
a6c40ce
fix lint
vchudnov-g Oct 17, 2025
d0414b2
fix lint
vchudnov-g Oct 17, 2025
835df53
test: add test for _flatten_message
vchudnov-g Oct 23, 2025
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
Prev Previous commit
Next Next commit
refactor: used namedtuple
  • Loading branch information
vchudnov-g committed Oct 23, 2025
commit b575a168c0882e1c30bec97a6e91e45a10bd6871
35 changes: 19 additions & 16 deletions google/api_core/_python_package_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,23 @@

import warnings
import sys
from typing import Optional, Tuple
from typing import Optional

from collections import namedtuple

from ._python_version_support import (
_flatten_message,
_get_distribution_and_import_packages,
)

from packaging.version import parse as parse_version, Version as PackagingVersion
from packaging.version import parse as parse_version

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that this package is missing defining packaging as a explicit dependency.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also just now experiencing an error on this:

Traceback (most recent call last):
  File "/home/runner/work/xxx.py", line 24, in <module>
    from google.cloud import storage
  File "/home/runner/.local/lib/python3.9/site-packages/google/cloud/storage/__init__.py", line 35, in <module>
    from google.cloud.storage.batch import Batch
  File "/home/runner/.local/lib/python3.9/site-packages/google/cloud/storage/batch.py", line 29, in <module>
    from google.cloud import exceptions
  File "/home/runner/.local/lib/python3.9/site-packages/google/cloud/exceptions/__init__.py", line 24, in <module>
    from google.api_core import exceptions
  File "/home/runner/.local/lib/python3.9/site-packages/google/api_core/__init__.py", line 20, in <module>
    from google.api_core import _python_package_support
  File "/home/runner/.local/lib/python3.9/site-packages/google/api_core/_python_package_support.py", line 28, in <module>
    from packaging.version import parse as parse_version
ModuleNotFoundError: No module named 'packaging'

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that there is already an issue created #848


DependencyVersion = namedtuple("DependencyVersion", ["version", "version_string"])


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

This function checks for an installed package and returns its version
Expand All @@ -38,16 +43,16 @@ def get_dependency_version(
dependency_name: The distribution name of the package (e.g., 'requests').

Returns:
A tuple containing the `packaging.version.Version` object and the
version string, or `(None, '--')` if the package is not found or
another error occurs during version discovery.
A DependencyVersion namedtuple with `version` and `version_string`
attributes, or `DependencyVersion(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), version_string)
return DependencyVersion(parse_version(version_string), version_string)

# TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
# this code path once we drop support for Python 3.7
Expand All @@ -56,10 +61,10 @@ def get_dependency_version(
import pkg_resources

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

except Exception:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we know what exceptions to expect? Or does this need to be broad?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any exception related that comes up trying to find the version. I could try to track them down, but at the end of the day I aim for the function to return without an error, but indicating if the version could not be found. So I lean towards catching broadly, but I'm open to counterarguments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I think that makes sense to be extra cautious here

return (None, "--")
return DependencyVersion(None, "--")


def warn_deprecation_for_versions_less_than(
Expand Down Expand Up @@ -107,12 +112,10 @@ def warn_deprecation_for_versions_less_than(
or not next_supported_version
): # pragma: NO COVER
return
(version_used, version_used_string) = get_dependency_version(
dependency_import_package
)
if not version_used:
dependency_version = get_dependency_version(dependency_import_package)
if not dependency_version.version:
return
if version_used < parse_version(next_supported_version):
if dependency_version.version < parse_version(next_supported_version):
(
dependency_package,
dependency_distribution_package,
Expand Down Expand Up @@ -151,8 +154,8 @@ def warn_deprecation_for_versions_less_than(
dependent_package=dependent_package,
next_supported_version=next_supported_version,
recommendation=recommendation,
version_used=version_used,
version_used_string=version_used_string,
version_used=dependency_version.version,
version_used_string=dependency_version.version_string,
),
FutureWarning,
)
Expand Down
28 changes: 16 additions & 12 deletions tests/unit/test_python_package_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from google.api_core._python_package_support import (
get_dependency_version,
warn_deprecation_for_versions_less_than,
DependencyVersion,
)


Expand All @@ -32,12 +33,13 @@
def test_get_dependency_version_py38_plus(mock_version):
"""Test get_dependency_version on Python 3.8+."""
mock_version.return_value = "1.2.3"
assert get_dependency_version("some-package") == (parse_version("1.2.3"), "1.2.3")
expected = DependencyVersion(parse_version("1.2.3"), "1.2.3")
assert get_dependency_version("some-package") == expected
mock_version.assert_called_once_with("some-package")

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


# TODO(https://github.com/googleapis/python-api-core/issues/835): Remove
Expand All @@ -49,17 +51,15 @@ def test_get_dependency_version_py37(mock_get_distribution):
mock_dist = MagicMock()
mock_dist.version = "4.5.6"
mock_get_distribution.return_value = mock_dist
assert get_dependency_version("another-package") == (
parse_version("4.5.6"),
"4.5.6",
)
expected = DependencyVersion(parse_version("4.5.6"), "4.5.6")
assert get_dependency_version("another-package") == expected
mock_get_distribution.assert_called_once_with("another-package")

# Test package not found
mock_get_distribution.side_effect = (
Exception # pkg_resources has its own exception types
)
assert get_dependency_version("not-a-package") == (None, "--")
assert get_dependency_version("not-a-package") == DependencyVersion(None, "--")


@patch("google.api_core._python_package_support._get_distribution_and_import_packages")
Expand All @@ -72,7 +72,7 @@ def test_warn_deprecation_for_versions_less_than(mock_get_version, mock_get_pack
("my-package (my.package)", "my-package"),
]

mock_get_version.return_value = (parse_version("1.0.0"), "1.0.0")
mock_get_version.return_value = DependencyVersion(parse_version("1.0.0"), "1.0.0")
with pytest.warns(FutureWarning) as record:
warn_deprecation_for_versions_less_than("my.package", "dep.package", "2.0.0")
assert len(record) == 1
Expand All @@ -87,17 +87,21 @@ def test_warn_deprecation_for_versions_less_than(mock_get_version, mock_get_pack

# Case 2: Installed version is equal to required, should not warn.
mock_get_packages.reset_mock()
mock_get_version.return_value = (parse_version("2.0.0"), "2.0.0")
mock_get_version.return_value = DependencyVersion(
parse_version("2.0.0"), "2.0.0"
)
warn_deprecation_for_versions_less_than("my.package", "dep.package", "2.0.0")

# Case 3: Installed version is greater than required, should not warn.
mock_get_packages.reset_mock()
mock_get_version.return_value = (parse_version("3.0.0"), "3.0.0")
mock_get_version.return_value = DependencyVersion(
parse_version("3.0.0"), "3.0.0"
)
warn_deprecation_for_versions_less_than("my.package", "dep.package", "2.0.0")

# Case 4: Dependency not found, should not warn.
mock_get_packages.reset_mock()
mock_get_version.return_value = (None, "--")
mock_get_version.return_value = DependencyVersion(None, "--")
warn_deprecation_for_versions_less_than("my.package", "dep.package", "2.0.0")

# Assert that no warnings were recorded
Expand All @@ -109,7 +113,7 @@ def test_warn_deprecation_for_versions_less_than(mock_get_version, mock_get_pack
("dep-package (dep.package)", "dep-package"),
("my-package (my.package)", "my-package"),
]
mock_get_version.return_value = (parse_version("1.0.0"), "1.0.0")
mock_get_version.return_value = DependencyVersion(parse_version("1.0.0"), "1.0.0")
template = "Custom warning for {dependency_package} used by {dependent_package}."
with pytest.warns(FutureWarning) as record:
warn_deprecation_for_versions_less_than(
Expand Down