Skip to content

Commit d702d86

Browse files
committed
Merge branch 'serializable-cache' into dev
2 parents 9b7ba25 + d2f6d88 commit d702d86

File tree

2 files changed

+53
-11
lines changed

2 files changed

+53
-11
lines changed

msal/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@
3131
ConfidentialClientApplication,
3232
PublicClientApplication,
3333
)
34-
from .token_cache import TokenCache
34+
from .token_cache import TokenCache, SerializableTokenCache
3535

msal/token_cache.py

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,22 @@ def base64decode(raw): # This can handle a padding-less raw input
1616

1717

1818
class TokenCache(object):
19+
"""This is considered as a base class containing minimal cache behavior.
20+
21+
Although this class already maintains tokens using unified schema,
22+
it does not serialize/persist them. See subclass SerializableTokenCache
23+
for more details.
24+
"""
1925

2026
class CredentialType:
2127
ACCESS_TOKEN = "AccessToken"
2228
REFRESH_TOKEN = "RefreshToken"
2329
ACCOUNT = "Account" # Not exactly a credential type, but we put it here
2430
ID_TOKEN = "IdToken"
2531

26-
def __init__(self, state=None):
27-
"""Initialize a token_cache instance, with an optional initial state,
28-
which can come from a previous run of token cache instance.
29-
30-
Although this class already maintains cached tokens using unified schema,
31-
it does not actually persist them.
32-
The persistence layer would be implemented in a subclass which provides
33-
a serialize() and deserialize() wrapping the self._cache internal structure.
34-
"""
35-
self._cache = state or {}
32+
def __init__(self):
3633
self._lock = threading.RLock()
34+
self._cache = {}
3735

3836
def find(self, credential_type, target=None, query=None):
3937
target = target or []
@@ -166,3 +164,47 @@ def update_rt(self, rt_item, new_rt):
166164
rt = self._cache.setdefault(self.CredentialType.REFRESH_TOKEN, {})[key]
167165
rt["secret"] = new_rt
168166

167+
168+
class SerializableTokenCache(TokenCache):
169+
"""This serialization can be a starting point to implement your own persistence.
170+
171+
This class does NOT actually persist the cache on disk/db/etc..
172+
Depends on your need, the following file-based persistence may be sufficient:
173+
174+
import atexit
175+
cache = SerializableTokenCache()
176+
cache.deserialize(open("my_cache.bin", "rb").read())
177+
atexit.register(lambda:
178+
open("my_cache.bin", "wb").write(cache.serialize())
179+
# Hint: The following optional line persists only when state changed
180+
if cache.has_state_changed else None
181+
)
182+
app = ClientApplication(..., token_cache=cache)
183+
...
184+
"""
185+
def add(self, event):
186+
super(SerializableTokenCache, self).add(event)
187+
self.has_state_changed = True
188+
189+
def remove_rt(self, rt_item):
190+
super(SerializableTokenCache, self).remove_rt(rt_item)
191+
self.has_state_changed = True
192+
193+
def update_rt(self, rt_item, new_rt):
194+
super(SerializableTokenCache, self).update_rt(rt_item, new_rt)
195+
self.has_state_changed = True
196+
197+
def deserialize(self, state):
198+
# type: (Optional[str]) -> None
199+
"""Deserialize the cache from a state previously obtained by serialize()"""
200+
with self._lock:
201+
self._cache = json.loads(state) if state else {}
202+
self.has_state_changed = False # reset
203+
204+
def serialize(self):
205+
# type: () -> str
206+
"""Serialize the current cache state into a string."""
207+
with self._lock:
208+
self.has_state_changed = False
209+
return json.dumps(self._cache)
210+

0 commit comments

Comments
 (0)