spotipy/spotipy/cache_handler.py
andrewcara 62a27a20e0
Added MemcacheCacheHandler (#1042)
* Added MemcacheCacheHandler

* Import MemcacheError where used

* Update index.rst

---------

Co-authored-by: Stéphane Bruckert <stephane.bruckert@gmail.com>
2024-05-30 19:01:16 +01:00

243 lines
7.2 KiB
Python

__all__ = [
'CacheHandler',
'CacheFileHandler',
'DjangoSessionCacheHandler',
'FlaskSessionCacheHandler',
'MemoryCacheHandler',
'RedisCacheHandler',
'MemcacheCacheHandler']
import errno
import json
import logging
import os
from spotipy.util import CLIENT_CREDS_ENV_VARS
from redis import RedisError
logger = logging.getLogger(__name__)
class CacheHandler():
"""
An abstraction layer for handling the caching and retrieval of
authorization tokens.
Custom extensions of this class must implement get_cached_token
and save_token_to_cache methods with the same input and output
structure as the CacheHandler class.
"""
def get_cached_token(self):
"""
Get and return a token_info dictionary object.
"""
# return token_info
raise NotImplementedError()
def save_token_to_cache(self, token_info):
"""
Save a token_info dictionary object to the cache and return None.
"""
raise NotImplementedError()
return None
class CacheFileHandler(CacheHandler):
"""
Handles reading and writing cached Spotify authorization tokens
as json files on disk.
"""
def __init__(self,
cache_path=None,
username=None,
encoder_cls=None):
"""
Parameters:
* cache_path: May be supplied, will otherwise be generated
(takes precedence over `username`)
* username: May be supplied or set as environment variable
(will set `cache_path` to `.cache-{username}`)
* encoder_cls: May be supplied as a means of overwriting the
default serializer used for writing tokens to disk
"""
self.encoder_cls = encoder_cls
if cache_path:
self.cache_path = cache_path
else:
cache_path = ".cache"
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
if username:
cache_path += "-" + str(username)
self.cache_path = cache_path
def get_cached_token(self):
token_info = None
try:
f = open(self.cache_path)
token_info_string = f.read()
f.close()
token_info = json.loads(token_info_string)
except OSError as error:
if error.errno == errno.ENOENT:
logger.debug("cache does not exist at: %s", self.cache_path)
else:
logger.warning("Couldn't read cache at: %s", self.cache_path)
return token_info
def save_token_to_cache(self, token_info):
try:
f = open(self.cache_path, "w")
f.write(json.dumps(token_info, cls=self.encoder_cls))
f.close()
except OSError:
logger.warning('Couldn\'t write token to cache at: %s',
self.cache_path)
class MemoryCacheHandler(CacheHandler):
"""
A cache handler that simply stores the token info in memory as an
instance attribute of this class. The token info will be lost when this
instance is freed.
"""
def __init__(self, token_info=None):
"""
Parameters:
* token_info: The token info to store in memory. Can be None.
"""
self.token_info = token_info
def get_cached_token(self):
return self.token_info
def save_token_to_cache(self, token_info):
self.token_info = token_info
class DjangoSessionCacheHandler(CacheHandler):
"""
A cache handler that stores the token info in the session framework
provided by Django.
Read more at https://docs.djangoproject.com/en/3.2/topics/http/sessions/
"""
def __init__(self, request):
"""
Parameters:
* request: HttpRequest object provided by Django for every
incoming request
"""
self.request = request
def get_cached_token(self):
token_info = None
try:
token_info = self.request.session['token_info']
except KeyError:
logger.debug("Token not found in the session")
return token_info
def save_token_to_cache(self, token_info):
try:
self.request.session['token_info'] = token_info
except Exception as e:
logger.warning("Error saving token to cache: " + str(e))
class FlaskSessionCacheHandler(CacheHandler):
"""
A cache handler that stores the token info in the session framework
provided by flask.
"""
def __init__(self, session):
self.session = session
def get_cached_token(self):
token_info = None
try:
token_info = self.session["token_info"]
except KeyError:
logger.debug("Token not found in the session")
return token_info
def save_token_to_cache(self, token_info):
try:
self.session["token_info"] = token_info
except Exception as e:
logger.warning("Error saving token to cache: " + str(e))
class RedisCacheHandler(CacheHandler):
"""
A cache handler that stores the token info in the Redis.
"""
def __init__(self, redis, key=None):
"""
Parameters:
* redis: Redis object provided by redis-py library
(https://github.com/redis/redis-py)
* key: May be supplied, will otherwise be generated
(takes precedence over `token_info`)
"""
self.redis = redis
self.key = key if key else 'token_info'
def get_cached_token(self):
token_info = None
try:
token_info = self.redis.get(self.key)
if token_info:
return json.loads(token_info)
except RedisError as e:
logger.warning('Error getting token from cache: ' + str(e))
return token_info
def save_token_to_cache(self, token_info):
try:
self.redis.set(self.key, json.dumps(token_info))
except RedisError as e:
logger.warning('Error saving token to cache: ' + str(e))
class MemcacheCacheHandler(CacheHandler):
"""A Cache handler that stores the token info in Memcache using the pymemcache client
"""
def __init__(self, memcache, key=None) -> None:
"""
Parameters:
* memcache: memcache client object provided by pymemcache
(https://pymemcache.readthedocs.io/en/latest/getting_started.html)
* key: May be supplied, will otherwise be generated
(takes precedence over `token_info`)
"""
self.memcache = memcache
self.key = key if key else 'token_info'
def get_cached_token(self):
from pymemcache import MemcacheError
try:
token_info = self.memcache.get(self.key)
if token_info:
return json.loads(token_info.decode())
except MemcacheError as e:
logger.warning('Error getting token from cache' + str(e))
def save_token_to_cache(self, token_info):
from pymemcache import MemcacheError
try:
self.memcache.set(self.key, json.dumps(token_info))
except MemcacheError as e:
logger.warning('Error saving token to cache' + str(e))