Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 1 addition & 1 deletion CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ A few notes on making changes to ``google-auth-library-python``.
using ``nox -s docs``.

- The change must work fully on the following CPython versions:
3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and 3.13 across macOS, Linux, and Windows.
3.8, 3.9, 3.10, 3.11, 3.12, 3.13 and 3.14 across macOS, Linux, and Windows.

- The codebase *must* have 100% test statement coverage after each commit.
You can test coverage via ``nox -e cover``.
Expand Down
19 changes: 12 additions & 7 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,16 @@ Note that the extras pyopenssl and enterprise_cert should not be used together b

Supported Python Versions
^^^^^^^^^^^^^^^^^^^^^^^^^
Python >= 3.7
Python >= 3.8

**NOTE**:
Python 3.7 was marked as `unsupported`_ by the python community in June 2023.
We recommend that all developers upgrade to Python 3.8 and newer as soon as
they can. Support for Python 3.7 will be removed from this library after
January 1 2024. Previous releases that support Python 3.7 will continue to be available
for download, but releases after January 1 2024 will only target Python 3.8 and
newer.
Python 3.8 and Python 3.9 were marked as `unsupported`_ by the python community in
October 2024 and October 2025 respectively.
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This line break occurs in the middle of a phrase, which can be slightly awkward to read in the raw RST file. For better readability, it would be best to have this sentence on a single line.

Suggested change
Python 3.8 and Python 3.9 were marked as `unsupported`_ by the python community in
October 2024 and October 2025 respectively.
Python 3.8 and Python 3.9 were marked as `unsupported`_ by the python community in October 2024 and October 2025 respectively.

We recommend that all developers upgrade to Python 3.10 and newer as soon as
they can. Support for end-of-life Python runtimes will be removed from this
library in future updates.
Previous releases that support end-of-life Python versions will continue to be available
for download, but future releases will only target supported versions.

.. _unsupported: https://devguide.python.org/versions/#unsupported-versions

Expand All @@ -58,6 +59,10 @@ Unsupported Python Versions
- Python 3.6: The last version of this library with support for Python 3.6
was `google.auth == 2.22.0`.

- Python 3.7: The last version of this library with support for Python 3.7
was `google.auth == 2.45.0`.


Copy link
Contributor

Choose a reason for hiding this comment

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

medium

There's an extra blank line here. In reStructuredText, a single blank line is sufficient to separate paragraphs or list items. This extra line is unnecessary and can be removed.

Documentation
-------------

Expand Down
18 changes: 11 additions & 7 deletions google/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,17 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER
pass


# Checks if the current runtime is Python 3.7.
if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER
message = (
"After January 1, 2024, new releases of this library will drop support "
"for Python 3.7."
)
warnings.warn(message, Python37DeprecationWarning)
# Raise warnings for deprecated versions
eol_message = """
You are using a Python version {} past its end of life. Google will update
google-auth with critical bug fixes on a best-effort basis, but not
with any other fixes or features. Please upgrade your Python version,
and then update google-auth.
"""
if sys.version_info.major == 3 and sys.version_info.minor == 8: # pragma: NO COVER
warnings.warn(eol_message.format("3.8"), FutureWarning)
elif sys.version_info.major == 3 and sys.version_info.minor == 9: # pragma: NO COVER
warnings.warn(eol_message.format("3.9"), FutureWarning)

# Set default logging handler to avoid "No handler found" warnings.
logging.getLogger(__name__).addHandler(logging.NullHandler())
3 changes: 2 additions & 1 deletion google/auth/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,8 @@ def is_python_3():
Returns:
bool: True if the Python interpreter is Python 3 and False otherwise.
"""
return sys.version_info > (3, 0)

return sys.version_info > (3, 0) # pragma: NO COVER


def _hash_sensitive_info(data: Union[dict, list]) -> Union[dict, list, str]:
Expand Down
10 changes: 0 additions & 10 deletions google/auth/pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,6 @@ def retrieve_subject_token(self, request):
else:
return subject_token

if not _helpers.is_python_3():
raise exceptions.RefreshError(
"Pluggable auth is only supported for python 3.7+"
)

# Inject env vars.
env = os.environ.copy()
self._inject_env_variables(env)
Expand Down Expand Up @@ -263,11 +258,6 @@ def revoke(self, request):
)
self._validate_running_mode()

if not _helpers.is_python_3():
raise exceptions.RefreshError(
"Pluggable auth is only supported for python 3.7+"
)

# Inject variables
env = os.environ.copy()
self._inject_env_variables(env)
Expand Down
18 changes: 11 additions & 7 deletions google/oauth2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,14 @@ class Python37DeprecationWarning(DeprecationWarning): # pragma: NO COVER
pass


# Checks if the current runtime is Python 3.7.
if sys.version_info.major == 3 and sys.version_info.minor == 7: # pragma: NO COVER
message = (
"After January 1, 2024, new releases of this library will drop support "
"for Python 3.7."
)
warnings.warn(message, Python37DeprecationWarning)
# Raise warnings for deprecated versions
eol_message = """
You are using a Python version {} past its end of life. Google will update
google-auth with critical bug fixes on a best-effort basis, but not
with any other fixes or features. Please upgrade your Python version,
and then update google-auth.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do google/auth and google/oauth2 get released as part of the same PyPI package? In that case, could we re-use this check rather than duplicating it? If not, should this message say "google-oauth2"?

Copy link
Collaborator Author

@daniel-sanche daniel-sanche Jan 6, 2026

Choose a reason for hiding this comment

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

I believe it's all released as a single package

I worry a bit about complications importing from one package to the other, or adding shared logic to the root /google directory. I'm not entirely sure if that would be a problem, but I can imagine a few ways that could get messy. I thought it would be safer to use the existing logic, which was duplicated.

I can try to look into it a bit more, but we do need something working today, since this is blocking a release

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, it looks like:

  • adding an init.py to /google isn't an option, since it's a shared namespace, so that could interfere with other packages
  • if we kept the shared logic in auth and imported it in oauth2, there would be circular dependencies
  • we may be able to get away with importing oauth2 from oauth. But I worry that will still cause import headaches, and import bloat

These lines are meant to be temporary, and will be replaced by your more robust version check system soon. I opened a bug to track that work. Let me know what you think

Copy link
Contributor

Choose a reason for hiding this comment

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

sgtm. (I'd make the bug a p2 chore, but we can discuss that separately.)

"""
if sys.version_info.major == 3 and sys.version_info.minor == 8: # pragma: NO COVER
warnings.warn(eol_message.format("3.8"), FutureWarning)
elif sys.version_info.major == 3 and sys.version_info.minor == 9: # pragma: NO COVER
warnings.warn(eol_message.format("3.9"), FutureWarning)
6 changes: 1 addition & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@
"rsa>=3.1.4,<5",
)

# TODO(https://github.com/googleapis/google-auth-library-python/issues/1737): Unit test fails with
# `No module named 'cryptography.hazmat.backends.openssl.x509' for Python 3.7``.
cryptography_base_require = [
"cryptography >= 38.0.3",
"cryptography < 39.0.0; python_version < '3.8'",
]

requests_extra_require = ["requests >= 2.20.0, < 3.0.0"]
Expand Down Expand Up @@ -116,12 +113,11 @@
package_data={"google.auth": ["py.typed"], "google.oauth2": ["py.typed"]},
install_requires=DEPENDENCIES,
extras_require=extras,
python_requires=">=3.7",
python_requires=">=3.8",
license="Apache 2.0",
keywords="google auth oauth client",
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand Down
24 changes: 0 additions & 24 deletions tests/test_pluggable.py
Original file line number Diff line number Diff line change
Expand Up @@ -1230,16 +1230,6 @@ def test_revoke_successfully(self):
)
_ = credentials.revoke(None)

@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_python_2(self):
with mock.patch("sys.version_info", (2, 7)):
credentials = self.make_pluggable(credential_source=self.CREDENTIAL_SOURCE)

with pytest.raises(exceptions.RefreshError) as excinfo:
_ = credentials.retrieve_subject_token(None)

assert excinfo.match(r"Pluggable auth is only supported for python 3.7+")

@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_retrieve_subject_token_with_quoted_command(self):
command_with_spaces = '"/path/with spaces/to/executable" "arg with spaces"'
Expand Down Expand Up @@ -1269,17 +1259,3 @@ def test_retrieve_subject_token_with_quoted_command(self):
stderr=subprocess.STDOUT,
env=mock.ANY,
)

@mock.patch.dict(os.environ, {"GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES": "1"})
def test_revoke_subject_token_python_2(self):
with mock.patch("sys.version_info", (2, 7)):
credentials = self.make_pluggable(
audience=WORKFORCE_AUDIENCE,
credential_source=self.CREDENTIAL_SOURCE,
interactive=True,
)

with pytest.raises(exceptions.RefreshError) as excinfo:
_ = credentials.revoke(None)

assert excinfo.match(r"Pluggable auth is only supported for python 3.7+")
63 changes: 63 additions & 0 deletions tests/test_version_warnings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2026 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.

import importlib
import sys
from unittest import mock
import warnings

import pytest

import google.auth
import google.oauth2


@pytest.mark.parametrize("module", [google.auth, google.oauth2])
@pytest.mark.parametrize(
"version, expected_warning",
[
((3, 8), True),
((3, 9), True),
((3, 10), False),
((3, 13), False),
],
)
def test_python_version_warnings(module, version, expected_warning):
# Mock sys.version_info
# We use a MagicMock that has major and minor attributes
mock_version = mock.Mock()
mock_version.major = version[0]
mock_version.minor = version[1]

with mock.patch.object(sys, "version_info", mock_version):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
importlib.reload(module)

future_warnings = [
w
for w in caught_warnings
if issubclass(w.category, FutureWarning)
and "past its end of life" in str(w.message)
]

if expected_warning:
assert (
len(future_warnings) > 0
), f"Expected FutureWarning for Python {version} in {module.__name__}"
assert str(version[1]) in str(future_warnings[0].message)
else:
assert (
len(future_warnings) == 0
), f"Did not expect FutureWarning for Python {version} in {module.__name__}"