Skip to content
This repository was archived by the owner on Mar 6, 2026. It is now read-only.

Commit 18d3d68

Browse files
feat: drop cachetools dependency in favor of simple local implementation
This change removes the external dependency `cachetools` and replaces it with a simple, local `LRUCache` implementation in `google/auth/_cache.py`. The local implementation: - Inherits from `dict`. - Uses `collections.OrderedDict` to maintain LRU order. - Overrides `__getitem__`, `__setitem__`, `__delitem__`, and `get` to ensure LRU order is updated on access. This reduces the dependency footprint of the library and resolves compatibility issues with newer versions of `cachetools`. Tests have been added to cover the cache behavior, including verifying that `get()` updates the LRU order and that `clear()`/`del` work as expected.
1 parent 3f1aeea commit 18d3d68

5 files changed

Lines changed: 129 additions & 6 deletions

File tree

google/auth/_cache.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from collections import OrderedDict
2+
3+
4+
class LRUCache(dict):
5+
def __init__(self, maxsize):
6+
super().__init__()
7+
self._order = OrderedDict()
8+
self.maxsize = maxsize
9+
10+
def clear(self):
11+
super().clear()
12+
self._order.clear()
13+
14+
def __getitem__(self, key):
15+
value = super().__getitem__(key)
16+
self._update(key)
17+
return value
18+
19+
def __setitem__(self, key, value):
20+
maxsize = self.maxsize
21+
if maxsize <= 0:
22+
return
23+
if key not in self:
24+
while len(self) >= maxsize:
25+
self.popitem()
26+
super().__setitem__(key, value)
27+
self._update(key)
28+
29+
def __delitem__(self, key):
30+
super().__delitem__(key)
31+
del self._order[key]
32+
33+
def get(self, key, default=None):
34+
try:
35+
value = super().__getitem__(key)
36+
self._update(key)
37+
return value
38+
except KeyError:
39+
return default
40+
41+
def popitem(self):
42+
"""Remove and return the least recently used key-value pair."""
43+
key, _ = self._order.popitem(last=False)
44+
return key, super().pop(key)
45+
46+
def _update(self, key):
47+
try:
48+
self._order.move_to_end(key)
49+
except KeyError:
50+
self._order[key] = None

google/auth/jwt.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@
5050
import json
5151
import urllib
5252

53-
import cachetools
54-
53+
from google.auth import _cache
5554
from google.auth import _helpers
5655
from google.auth import _service_account_info
5756
from google.auth import crypt
@@ -630,7 +629,7 @@ def __init__(
630629
token_lifetime (int): The amount of time in seconds for
631630
which the token is valid. Defaults to 1 hour.
632631
max_cache_size (int): The maximum number of JWT tokens to keep in
633-
cache. Tokens are cached using :class:`cachetools.LRUCache`.
632+
cache. Tokens are cached using :class:`google.auth._cache.LRUCache`.
634633
quota_project_id (Optional[str]): The project ID used for quota
635634
and billing.
636635
@@ -646,7 +645,7 @@ def __init__(
646645
additional_claims = {}
647646

648647
self._additional_claims = additional_claims
649-
self._cache = cachetools.LRUCache(maxsize=max_cache_size)
648+
self._cache = _cache.LRUCache(maxsize=max_cache_size)
650649

651650
@classmethod
652651
def _from_signer_and_info(cls, signer, info, **kwargs):

noxfile.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ def mypy(session):
9797
session.install("-e", ".")
9898
session.install(
9999
"mypy",
100-
"types-cachetools",
101100
"types-certifi",
102101
"types-freezegun",
103102
"types-pyOpenSSL",

setup.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121

2222
DEPENDENCIES = (
23-
"cachetools>=2.0.0,<7.0",
2423
"pyasn1-modules>=0.2.1",
2524
# rsa==4.5 is the last version to support 2.7
2625
# https://github.com/sybrenstuvel/python-rsa/issues/152#issuecomment-643470233

tests/test__cache.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from google.auth._cache import LRUCache
2+
3+
4+
def test_lru_cache():
5+
lru_cache = LRUCache(2)
6+
lru_cache["a"] = 1
7+
lru_cache["b"] = 2
8+
assert lru_cache["a"] == 1
9+
lru_cache["c"] = 3
10+
assert "b" not in lru_cache
11+
assert lru_cache["a"] == 1
12+
assert lru_cache["c"] == 3
13+
lru_cache["d"] = 4
14+
assert "a" not in lru_cache
15+
assert lru_cache["c"] == 3
16+
assert lru_cache["d"] == 4
17+
18+
19+
def test_zero_size_lru_cache():
20+
lru_cache = LRUCache(0)
21+
lru_cache["a"] = 1
22+
assert "a" not in lru_cache
23+
24+
25+
def test_lru_cache_get_updates_lru():
26+
lru_cache = LRUCache(2)
27+
lru_cache["a"] = 1
28+
lru_cache["b"] = 2
29+
30+
# Access "a" via get(), making it MRU.
31+
assert lru_cache.get("a") == 1
32+
33+
# Add "c", which should evict "b" (LRU), not "a".
34+
lru_cache["c"] = 3
35+
36+
assert "a" in lru_cache
37+
assert "b" not in lru_cache
38+
assert "c" in lru_cache
39+
40+
41+
def test_lru_cache_get_missing():
42+
lru_cache = LRUCache(2)
43+
assert lru_cache.get("missing") is None
44+
assert lru_cache.get("missing", "default") == "default"
45+
46+
47+
def test_lru_cache_clear():
48+
lru_cache = LRUCache(2)
49+
lru_cache["a"] = 1
50+
lru_cache["b"] = 2
51+
assert len(lru_cache) == 2
52+
53+
lru_cache.clear()
54+
assert len(lru_cache) == 0
55+
assert "a" not in lru_cache
56+
assert "b" not in lru_cache
57+
# Ensure internal order is also cleared
58+
assert len(lru_cache._order) == 0
59+
60+
61+
def test_lru_cache_delitem():
62+
lru_cache = LRUCache(2)
63+
lru_cache["a"] = 1
64+
lru_cache["b"] = 2
65+
66+
del lru_cache["a"]
67+
assert "a" not in lru_cache
68+
assert len(lru_cache) == 1
69+
# Ensure it's removed from internal order
70+
assert "a" not in lru_cache._order
71+
72+
# Test that we can continue using the cache
73+
lru_cache["c"] = 3
74+
assert "c" in lru_cache
75+
assert "b" in lru_cache
76+
assert len(lru_cache) == 2

0 commit comments

Comments
 (0)