__all__ = [ 'CacheHandler', 'CacheFileHandler', 'DjangoSessionCacheHandler', 'FlaskSessionCacheHandler', 'MemoryCacheHandler'] import errno import json import logging import os from spotipy.util import CLIENT_CREDS_ENV_VARS 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 IOError 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 IOError: 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))