mirror of
https://github.com/spotipy-dev/spotipy.git
synced 2026-06-19 09:13:53 +00:00
Add type annotations to cache_handler.py (#1153)
This commit is contained in:
parent
935e3c16d7
commit
3fe3eb829d
@ -83,6 +83,7 @@ Rebasing master onto v3 doesn't require a changelog update.
|
|||||||
- featured_playlists
|
- featured_playlists
|
||||||
- category_playlists
|
- category_playlists
|
||||||
- Added FAQ entry for inaccessible playlists
|
- Added FAQ entry for inaccessible playlists
|
||||||
|
- Type annotations to `spotipy.cache_handler`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,40 @@
|
|||||||
__all__ = [
|
from __future__ import annotations
|
||||||
'CacheHandler',
|
|
||||||
'CacheFileHandler',
|
|
||||||
'DjangoSessionCacheHandler',
|
|
||||||
'FlaskSessionCacheHandler',
|
|
||||||
'MemoryCacheHandler',
|
|
||||||
'RedisCacheHandler',
|
|
||||||
'MemcacheCacheHandler']
|
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from spotipy.util import CLIENT_CREDS_ENV_VARS
|
|
||||||
|
|
||||||
from redis import RedisError
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from json import JSONEncoder
|
||||||
|
from typing import TypedDict
|
||||||
|
import redis
|
||||||
|
from redis import RedisError
|
||||||
|
import redis.client
|
||||||
|
|
||||||
|
from .util import CLIENT_CREDS_ENV_VARS
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CacheHandler",
|
||||||
|
"CacheFileHandler",
|
||||||
|
"DjangoSessionCacheHandler",
|
||||||
|
"FlaskSessionCacheHandler",
|
||||||
|
"MemoryCacheHandler",
|
||||||
|
"RedisCacheHandler",
|
||||||
|
"MemcacheCacheHandler",
|
||||||
|
]
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TokenInfo(TypedDict):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
expires_in: int
|
||||||
|
scope: str
|
||||||
|
expires_at: int
|
||||||
|
refresh_token: str
|
||||||
|
|
||||||
|
|
||||||
class CacheHandler(ABC):
|
class CacheHandler(ABC):
|
||||||
"""
|
"""
|
||||||
An abstraction layer for handling the caching and retrieval of
|
An abstraction layer for handling the caching and retrieval of
|
||||||
@ -30,49 +46,43 @@ class CacheHandler(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_cached_token(self):
|
def get_cached_token(self) -> TokenInfo | None:
|
||||||
"""
|
"""Get and return a token_info dictionary object."""
|
||||||
Get and return a token_info dictionary object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info: TokenInfo) -> None:
|
||||||
"""
|
"""Save a token_info dictionary object to the cache and return None."""
|
||||||
Save a token_info dictionary object to the cache and return None.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class CacheFileHandler(CacheHandler):
|
class CacheFileHandler(CacheHandler):
|
||||||
"""
|
"""Read and write cached Spotify authorization tokens as json files on disk."""
|
||||||
Handles reading and writing cached Spotify authorization tokens
|
|
||||||
as json files on disk.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
cache_path=None,
|
self,
|
||||||
username=None,
|
cache_path: str | None = None,
|
||||||
encoder_cls=None):
|
username: str | None = None,
|
||||||
|
encoder_cls: type[JSONEncoder] | None = None,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Initialize CacheFileHandler instance.
|
||||||
* cache_path: May be supplied, will otherwise be generated
|
|
||||||
(takes precedence over `username`)
|
:param cache_path: (Optional) Path to cache. (Will override 'username')
|
||||||
* username: May be supplied or set as environment variable
|
:param username: (Optional) Client username. (Can also be supplied via env var.)
|
||||||
(will set `cache_path` to `.cache-{username}`)
|
:param encoder_cls: (Optional) JSON encoder class to override default.
|
||||||
* encoder_cls: May be supplied as a means of overwriting the
|
|
||||||
default serializer used for writing tokens to disk
|
|
||||||
"""
|
"""
|
||||||
self.encoder_cls = encoder_cls
|
self.encoder_cls = encoder_cls
|
||||||
if cache_path:
|
if cache_path:
|
||||||
self.cache_path = cache_path
|
self.cache_path = cache_path
|
||||||
else:
|
else:
|
||||||
cache_path = ".cache"
|
cache_path = ".cache"
|
||||||
username = (username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"]))
|
username = username or os.getenv(CLIENT_CREDS_ENV_VARS["client_username"])
|
||||||
if username:
|
if username:
|
||||||
cache_path += f"-{username}"
|
cache_path += f"-{username}"
|
||||||
self.cache_path = cache_path
|
self.cache_path = cache_path
|
||||||
|
|
||||||
def get_cached_token(self):
|
def get_cached_token(self) -> TokenInfo | None:
|
||||||
token_info = None
|
"""Get cached token from file."""
|
||||||
|
token_info: TokenInfo | None = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path)
|
f = open(self.cache_path)
|
||||||
@ -88,7 +98,8 @@ class CacheFileHandler(CacheHandler):
|
|||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info: TokenInfo) -> None:
|
||||||
|
"""Save token cache to file."""
|
||||||
try:
|
try:
|
||||||
f = open(self.cache_path, "w")
|
f = open(self.cache_path, "w")
|
||||||
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
f.write(json.dumps(token_info, cls=self.encoder_cls))
|
||||||
@ -98,23 +109,22 @@ class CacheFileHandler(CacheHandler):
|
|||||||
|
|
||||||
|
|
||||||
class MemoryCacheHandler(CacheHandler):
|
class MemoryCacheHandler(CacheHandler):
|
||||||
"""
|
"""Cache handler that stores the token non-persistently as an instance attribute."""
|
||||||
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):
|
def __init__(self, token_info: TokenInfo | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Initialize MemoryCacheHandler instance.
|
||||||
* token_info: The token info to store in memory. Can be None.
|
|
||||||
|
:param token_info: Optional initial cached token
|
||||||
"""
|
"""
|
||||||
self.token_info = token_info
|
self.token_info = token_info
|
||||||
|
|
||||||
def get_cached_token(self):
|
def get_cached_token(self) -> TokenInfo | None:
|
||||||
|
"""Retrieve the cached token from the instance."""
|
||||||
return self.token_info
|
return self.token_info
|
||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info: TokenInfo) -> None:
|
||||||
|
"""Cache the token in this instance."""
|
||||||
self.token_info = token_info
|
self.token_info = token_info
|
||||||
|
|
||||||
|
|
||||||
@ -137,7 +147,7 @@ class DjangoSessionCacheHandler(CacheHandler):
|
|||||||
def get_cached_token(self):
|
def get_cached_token(self):
|
||||||
token_info = None
|
token_info = None
|
||||||
try:
|
try:
|
||||||
token_info = self.request.session['token_info']
|
token_info = self.request.session["token_info"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.debug("Token not found in the session")
|
logger.debug("Token not found in the session")
|
||||||
|
|
||||||
@ -145,7 +155,7 @@ class DjangoSessionCacheHandler(CacheHandler):
|
|||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info):
|
||||||
try:
|
try:
|
||||||
self.request.session['token_info'] = token_info
|
self.request.session["token_info"] = token_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error saving token to cache: {e}")
|
logger.warning(f"Error saving token to cache: {e}")
|
||||||
|
|
||||||
@ -176,33 +186,32 @@ class FlaskSessionCacheHandler(CacheHandler):
|
|||||||
|
|
||||||
|
|
||||||
class RedisCacheHandler(CacheHandler):
|
class RedisCacheHandler(CacheHandler):
|
||||||
"""
|
"""A cache handler that stores the token info in the Redis."""
|
||||||
A cache handler that stores the token info in the Redis.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, redis, key=None):
|
def __init__(self, redis_obj: redis.client.Redis, key: str | None = None) -> None:
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Initialize RedisCacheHandler instance.
|
||||||
* 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):
|
:param redis: The Redis object to function as the cache
|
||||||
|
:param key: (Optional) The key to used to store the token in the cache
|
||||||
|
"""
|
||||||
|
self.redis = redis_obj
|
||||||
|
self.key = key or "token_info"
|
||||||
|
|
||||||
|
def get_cached_token(self) -> TokenInfo | None:
|
||||||
|
"""Fetch cache token from the Redis."""
|
||||||
token_info = None
|
token_info = None
|
||||||
try:
|
try:
|
||||||
token_info = self.redis.get(self.key)
|
token_info = self.redis.get(self.key)
|
||||||
if token_info:
|
if token_info is not None:
|
||||||
return json.loads(token_info)
|
token_info = json.loads(token_info)
|
||||||
except RedisError as e:
|
except RedisError as e:
|
||||||
logger.warning(f"Error getting token from cache: {e}")
|
logger.warning(f"Error getting token from cache: {e}")
|
||||||
|
|
||||||
return token_info
|
return token_info
|
||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info: TokenInfo) -> None:
|
||||||
|
"""Cache token in the Redis."""
|
||||||
try:
|
try:
|
||||||
self.redis.set(self.key, json.dumps(token_info))
|
self.redis.set(self.key, json.dumps(token_info))
|
||||||
except RedisError as e:
|
except RedisError as e:
|
||||||
@ -222,10 +231,11 @@ class MemcacheCacheHandler(CacheHandler):
|
|||||||
(takes precedence over `token_info`)
|
(takes precedence over `token_info`)
|
||||||
"""
|
"""
|
||||||
self.memcache = memcache
|
self.memcache = memcache
|
||||||
self.key = key or 'token_info'
|
self.key = key or "token_info"
|
||||||
|
|
||||||
def get_cached_token(self):
|
def get_cached_token(self):
|
||||||
from pymemcache import MemcacheError
|
from pymemcache import MemcacheError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token_info = self.memcache.get(self.key)
|
token_info = self.memcache.get(self.key)
|
||||||
if token_info:
|
if token_info:
|
||||||
@ -235,6 +245,7 @@ class MemcacheCacheHandler(CacheHandler):
|
|||||||
|
|
||||||
def save_token_to_cache(self, token_info):
|
def save_token_to_cache(self, token_info):
|
||||||
from pymemcache import MemcacheError
|
from pymemcache import MemcacheError
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.memcache.set(self.key, json.dumps(token_info))
|
self.memcache.set(self.key, json.dumps(token_info))
|
||||||
except MemcacheError as e:
|
except MemcacheError as e:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user