0
# Cache Management
1
2
Token caching strategies and utility functions for efficient session management and scope handling. Spotipy provides multiple cache handler implementations for different environments and use cases.
3
4
## Capabilities
5
6
### Base Cache Handler
7
8
Abstract base class defining the cache handler interface.
9
10
```python { .api }
11
class CacheHandler:
12
"""
13
Abstract base class for token caching implementations.
14
15
All cache handlers must implement get_cached_token and save_token_to_cache methods.
16
"""
17
18
def get_cached_token(self):
19
"""
20
Get cached token information.
21
22
Returns:
23
dict: Token information dictionary or None if no valid token cached
24
"""
25
26
def save_token_to_cache(self, token_info):
27
"""
28
Save token information to cache.
29
30
Args:
31
token_info (dict): Token information dictionary to cache
32
33
Returns:
34
None
35
"""
36
```
37
38
### File-Based Caching
39
40
Store tokens in local files for persistent authentication across sessions.
41
42
```python { .api }
43
class CacheFileHandler(CacheHandler):
44
def __init__(self, cache_path=None, username=None, encoder_cls=None):
45
"""
46
File-based token caching.
47
48
Args:
49
cache_path (str, optional): Path to cache file (default: .cache-{username})
50
username (str, optional): Username for cache file naming
51
encoder_cls (class, optional): JSON encoder class for serialization
52
"""
53
54
def get_cached_token(self):
55
"""
56
Get cached token from file.
57
58
Returns:
59
dict: Token information or None if file doesn't exist or invalid
60
"""
61
62
def save_token_to_cache(self, token_info):
63
"""
64
Save token to file.
65
66
Args:
67
token_info (dict): Token information to save
68
"""
69
```
70
71
### Memory Caching
72
73
Store tokens in memory for the duration of the application session.
74
75
```python { .api }
76
class MemoryCacheHandler(CacheHandler):
77
def __init__(self, token_info=None):
78
"""
79
In-memory token caching.
80
81
Args:
82
token_info (dict, optional): Initial token information
83
"""
84
85
def get_cached_token(self):
86
"""
87
Get cached token from memory.
88
89
Returns:
90
dict: Token information or None if not cached
91
"""
92
93
def save_token_to_cache(self, token_info):
94
"""
95
Save token to memory.
96
97
Args:
98
token_info (dict): Token information to save
99
"""
100
```
101
102
### Web Framework Integration
103
104
Integration with popular Python web frameworks for session-based token storage.
105
106
```python { .api }
107
class DjangoSessionCacheHandler(CacheHandler):
108
def __init__(self, request):
109
"""
110
Django session-based token caching.
111
112
Args:
113
request: Django HttpRequest object with session
114
"""
115
116
def get_cached_token(self):
117
"""Get cached token from Django session."""
118
119
def save_token_to_cache(self, token_info):
120
"""Save token to Django session."""
121
122
class FlaskSessionCacheHandler(CacheHandler):
123
def __init__(self, session=None):
124
"""
125
Flask session-based token caching.
126
127
Args:
128
session: Flask session object (uses flask.session if not provided)
129
"""
130
131
def get_cached_token(self):
132
"""Get cached token from Flask session."""
133
134
def save_token_to_cache(self, token_info):
135
"""Save token to Flask session."""
136
```
137
138
### Database and External Storage
139
140
Integration with external storage systems for scalable token management.
141
142
```python { .api }
143
class RedisCacheHandler(CacheHandler):
144
def __init__(self, redis_instance=None, key=None, encoder_cls=None):
145
"""
146
Redis-based token caching.
147
148
Args:
149
redis_instance: Redis client instance (creates default if None)
150
key (str, optional): Redis key for token storage (default: spotipy_token)
151
encoder_cls (class, optional): JSON encoder class for serialization
152
"""
153
154
def get_cached_token(self):
155
"""Get cached token from Redis."""
156
157
def save_token_to_cache(self, token_info):
158
"""Save token to Redis."""
159
160
class MemcacheCacheHandler(CacheHandler):
161
def __init__(self, memcache_instance=None, key=None, encoder_cls=None):
162
"""
163
Memcache-based token caching.
164
165
Args:
166
memcache_instance: Memcache client instance
167
key (str, optional): Memcache key for token storage (default: spotipy_token)
168
encoder_cls (class, optional): JSON encoder class for serialization
169
"""
170
171
def get_cached_token(self):
172
"""Get cached token from Memcache."""
173
174
def save_token_to_cache(self, token_info):
175
"""Save token to Memcache."""
176
```
177
178
### Utility Functions
179
180
Helper functions for OAuth scope management and URL parsing.
181
182
```python { .api }
183
def prompt_for_user_token(username=None, scope=None, client_id=None,
184
client_secret=None, redirect_uri=None, cache_path=None,
185
oauth_manager=None, show_dialog=False):
186
"""
187
Prompt user for token (deprecated - use SpotifyOAuth instead).
188
189
Args:
190
username (str, optional): Spotify username
191
scope (str, optional): Desired scope of the request
192
client_id (str, optional): Client ID of your app
193
client_secret (str, optional): Client secret of your app
194
redirect_uri (str, optional): Redirect URI of your app
195
cache_path (str, optional): Path to location to save tokens
196
oauth_manager: OAuth manager object
197
show_dialog (bool): Show login prompt always (default: False)
198
199
Returns:
200
str: Access token or None if authentication fails
201
"""
202
203
def normalize_scope(scope):
204
"""
205
Normalize scope to verify that it is a list or tuple.
206
207
Args:
208
scope (str, list, tuple): Scope string or list/tuple of scopes
209
210
Returns:
211
str: Space-separated scope string or None
212
"""
213
214
def get_host_port(netloc):
215
"""
216
Split network location string into host and port.
217
218
Args:
219
netloc (str): Network location string
220
221
Returns:
222
tuple: (host, port) where host is string and port is int or None
223
"""
224
```
225
226
### Custom Retry Handler
227
228
Enhanced retry logic with rate limit warnings.
229
230
```python { .api }
231
class Retry(urllib3.Retry):
232
"""
233
Custom retry class with rate limit warnings.
234
235
Extends urllib3.Retry to provide user-friendly warnings when rate limits are hit.
236
"""
237
238
def increment(self, method=None, url=None, response=None, error=None,
239
_pool=None, _stacktrace=None):
240
"""
241
Handle retry logic with enhanced rate limit messaging.
242
243
Args:
244
method (str, optional): HTTP method
245
url (str, optional): Request URL
246
response (urllib3.BaseHTTPResponse, optional): HTTP response
247
error (Exception, optional): Request error
248
_pool (urllib3.connectionpool.ConnectionPool, optional): Connection pool
249
_stacktrace (TracebackType, optional): Stack trace
250
251
Returns:
252
urllib3.Retry: Updated retry object
253
"""
254
```
255
256
## Usage Examples
257
258
### File-Based Caching
259
260
```python
261
import spotipy
262
from spotipy.oauth2 import SpotifyOAuth
263
from spotipy.cache_handler import CacheFileHandler
264
265
# Custom cache file location
266
cache_handler = CacheFileHandler(cache_path=".spotify_cache", username="myuser")
267
268
auth_manager = SpotifyOAuth(
269
client_id="your_client_id",
270
client_secret="your_client_secret",
271
redirect_uri="http://localhost:8080/callback",
272
scope="user-library-read",
273
cache_handler=cache_handler
274
)
275
276
sp = spotipy.Spotify(auth_manager=auth_manager)
277
278
# Token will be saved to .spotify_cache file
279
user = sp.current_user()
280
print(f"Authenticated as: {user['display_name']}")
281
```
282
283
### Memory Caching
284
285
```python
286
from spotipy.cache_handler import MemoryCacheHandler
287
288
# Memory-only caching (tokens lost when application exits)
289
memory_cache = MemoryCacheHandler()
290
291
auth_manager = SpotifyOAuth(
292
client_id="your_client_id",
293
client_secret="your_client_secret",
294
redirect_uri="http://localhost:8080/callback",
295
scope="user-library-read",
296
cache_handler=memory_cache
297
)
298
299
# Manually set token if you have one
300
token_info = {
301
'access_token': 'your_access_token',
302
'token_type': 'Bearer',
303
'expires_in': 3600,
304
'refresh_token': 'your_refresh_token',
305
'scope': 'user-library-read',
306
'expires_at': 1640995200 # Unix timestamp
307
}
308
309
memory_cache.save_token_to_cache(token_info)
310
sp = spotipy.Spotify(auth_manager=auth_manager)
311
```
312
313
### Redis Integration
314
315
```python
316
import redis
317
from spotipy.cache_handler import RedisCacheHandler
318
319
# Redis setup
320
redis_client = redis.Redis(host='localhost', port=6379, db=0)
321
322
# Redis cache handler
323
redis_cache = RedisCacheHandler(
324
redis_instance=redis_client,
325
key="spotify_token_user123" # Unique key per user
326
)
327
328
auth_manager = SpotifyOAuth(
329
client_id="your_client_id",
330
client_secret="your_client_secret",
331
redirect_uri="http://localhost:8080/callback",
332
scope="user-library-read user-library-modify",
333
cache_handler=redis_cache
334
)
335
336
sp = spotipy.Spotify(auth_manager=auth_manager)
337
338
# Token will be stored in Redis with expiration
339
user = sp.current_user()
340
print(f"User: {user['display_name']}")
341
342
# Check Redis for stored token
343
stored_token = redis_client.get("spotify_token_user123")
344
if stored_token:
345
print("Token successfully stored in Redis")
346
```
347
348
### Django Integration
349
350
```python
351
# views.py
352
from django.shortcuts import render, redirect
353
from spotipy.oauth2 import SpotifyOAuth
354
from spotipy.cache_handler import DjangoSessionCacheHandler
355
import spotipy
356
357
def spotify_login(request):
358
"""Handle Spotify OAuth login."""
359
cache_handler = DjangoSessionCacheHandler(request)
360
361
auth_manager = SpotifyOAuth(
362
client_id="your_client_id",
363
client_secret="your_client_secret",
364
redirect_uri="http://localhost:8000/spotify/callback/",
365
scope="user-library-read user-top-read",
366
cache_handler=cache_handler
367
)
368
369
# Get authorization URL
370
auth_url = auth_manager.get_authorization_url()
371
return redirect(auth_url)
372
373
def spotify_callback(request):
374
"""Handle Spotify OAuth callback."""
375
cache_handler = DjangoSessionCacheHandler(request)
376
377
auth_manager = SpotifyOAuth(
378
client_id="your_client_id",
379
client_secret="your_client_secret",
380
redirect_uri="http://localhost:8000/spotify/callback/",
381
scope="user-library-read user-top-read",
382
cache_handler=cache_handler
383
)
384
385
# Handle the callback
386
code = request.GET.get('code')
387
if code:
388
auth_manager.get_access_token(code)
389
return redirect('spotify_dashboard')
390
391
return redirect('spotify_login')
392
393
def spotify_dashboard(request):
394
"""Dashboard showing user's Spotify data."""
395
cache_handler = DjangoSessionCacheHandler(request)
396
397
auth_manager = SpotifyOAuth(
398
client_id="your_client_id",
399
client_secret="your_client_secret",
400
redirect_uri="http://localhost:8000/spotify/callback/",
401
scope="user-library-read user-top-read",
402
cache_handler=cache_handler
403
)
404
405
# Check if user is authenticated
406
token_info = cache_handler.get_cached_token()
407
if not token_info:
408
return redirect('spotify_login')
409
410
sp = spotipy.Spotify(auth_manager=auth_manager)
411
412
try:
413
user = sp.current_user()
414
top_tracks = sp.current_user_top_tracks(limit=10)
415
416
context = {
417
'user': user,
418
'top_tracks': top_tracks['items']
419
}
420
421
return render(request, 'spotify_dashboard.html', context)
422
423
except Exception as e:
424
# Token might be expired, redirect to login
425
return redirect('spotify_login')
426
```
427
428
### Flask Integration
429
430
```python
431
from flask import Flask, session, request, redirect, url_for, render_template
432
from spotipy.oauth2 import SpotifyOAuth
433
from spotipy.cache_handler import FlaskSessionCacheHandler
434
import spotipy
435
436
app = Flask(__name__)
437
app.secret_key = 'your-secret-key'
438
439
@app.route('/')
440
def index():
441
cache_handler = FlaskSessionCacheHandler(session)
442
auth_manager = SpotifyOAuth(
443
client_id="your_client_id",
444
client_secret="your_client_secret",
445
redirect_uri=url_for('spotify_callback', _external=True),
446
scope="user-library-read",
447
cache_handler=cache_handler
448
)
449
450
if request.args.get("code"):
451
# Handle callback
452
auth_manager.get_access_token(request.args.get("code"))
453
return redirect(url_for('dashboard'))
454
455
if not auth_manager.validate_token(cache_handler.get_cached_token()):
456
# Need authentication
457
auth_url = auth_manager.get_authorization_url()
458
return f'<a href="{auth_url}">Login with Spotify</a>'
459
460
return redirect(url_for('dashboard'))
461
462
@app.route('/callback')
463
def spotify_callback():
464
return redirect(url_for('index'))
465
466
@app.route('/dashboard')
467
def dashboard():
468
cache_handler = FlaskSessionCacheHandler(session)
469
auth_manager = SpotifyOAuth(
470
client_id="your_client_id",
471
client_secret="your_client_secret",
472
redirect_uri=url_for('spotify_callback', _external=True),
473
scope="user-library-read",
474
cache_handler=cache_handler
475
)
476
477
if not auth_manager.validate_token(cache_handler.get_cached_token()):
478
return redirect(url_for('index'))
479
480
sp = spotipy.Spotify(auth_manager=auth_manager)
481
user = sp.current_user()
482
483
return f"Hello {user['display_name']}! <a href='{url_for('logout')}'>Logout</a>"
484
485
@app.route('/logout')
486
def logout():
487
session.clear()
488
return redirect(url_for('index'))
489
490
if __name__ == '__main__':
491
app.run(debug=True)
492
```
493
494
### Custom Cache Handler
495
496
```python
497
from spotipy.cache_handler import CacheHandler
498
import json
499
import os
500
from datetime import datetime
501
502
class DatabaseCacheHandler(CacheHandler):
503
"""Custom cache handler using database storage."""
504
505
def __init__(self, user_id, db_connection):
506
self.user_id = user_id
507
self.db = db_connection
508
509
def get_cached_token(self):
510
"""Get token from database."""
511
cursor = self.db.cursor()
512
cursor.execute(
513
"SELECT token_data FROM spotify_tokens WHERE user_id = %s",
514
(self.user_id,)
515
)
516
result = cursor.fetchone()
517
518
if result:
519
try:
520
token_info = json.loads(result[0])
521
# Check if token is expired
522
if token_info.get('expires_at', 0) > datetime.now().timestamp():
523
return token_info
524
except json.JSONDecodeError:
525
pass
526
527
return None
528
529
def save_token_to_cache(self, token_info):
530
"""Save token to database."""
531
cursor = self.db.cursor()
532
cursor.execute("""
533
INSERT INTO spotify_tokens (user_id, token_data, updated_at)
534
VALUES (%s, %s, %s)
535
ON DUPLICATE KEY UPDATE
536
token_data = VALUES(token_data),
537
updated_at = VALUES(updated_at)
538
""", (
539
self.user_id,
540
json.dumps(token_info),
541
datetime.now()
542
))
543
self.db.commit()
544
545
# Usage with custom handler
546
# db_cache = DatabaseCacheHandler("user123", mysql_connection)
547
# auth_manager = SpotifyOAuth(cache_handler=db_cache)
548
```
549
550
### Multi-User Cache Management
551
552
```python
553
class MultiUserCacheManager:
554
"""Manage tokens for multiple users with different cache strategies."""
555
556
def __init__(self):
557
self.cache_handlers = {}
558
559
def get_user_cache(self, user_id, cache_type="file"):
560
"""Get cache handler for specific user."""
561
if user_id not in self.cache_handlers:
562
if cache_type == "file":
563
cache_handler = CacheFileHandler(
564
cache_path=f".cache-{user_id}",
565
username=user_id
566
)
567
elif cache_type == "memory":
568
cache_handler = MemoryCacheHandler()
569
elif cache_type == "redis":
570
redis_client = redis.Redis()
571
cache_handler = RedisCacheHandler(
572
redis_instance=redis_client,
573
key=f"spotify_token_{user_id}"
574
)
575
else:
576
raise ValueError(f"Unknown cache type: {cache_type}")
577
578
self.cache_handlers[user_id] = cache_handler
579
580
return self.cache_handlers[user_id]
581
582
def get_spotify_client(self, user_id, scope="user-library-read", cache_type="file"):
583
"""Get authenticated Spotify client for user."""
584
cache_handler = self.get_user_cache(user_id, cache_type)
585
586
auth_manager = SpotifyOAuth(
587
client_id="your_client_id",
588
client_secret="your_client_secret",
589
redirect_uri="http://localhost:8080/callback",
590
scope=scope,
591
cache_handler=cache_handler
592
)
593
594
return spotipy.Spotify(auth_manager=auth_manager)
595
596
def clear_user_cache(self, user_id):
597
"""Clear cached token for user."""
598
if user_id in self.cache_handlers:
599
# For file cache, remove the file
600
cache_handler = self.cache_handlers[user_id]
601
if isinstance(cache_handler, CacheFileHandler):
602
try:
603
os.remove(f".cache-{user_id}")
604
except FileNotFoundError:
605
pass
606
607
del self.cache_handlers[user_id]
608
609
# Usage
610
cache_manager = MultiUserCacheManager()
611
612
# Get client for different users
613
user1_sp = cache_manager.get_spotify_client("user1", cache_type="redis")
614
user2_sp = cache_manager.get_spotify_client("user2", cache_type="file")
615
616
# Use clients
617
user1_profile = user1_sp.current_user()
618
user2_profile = user2_sp.current_user()
619
```
620
621
### Scope Management Utilities
622
623
```python
624
from spotipy.util import normalize_scope
625
626
# Normalize different scope formats
627
scopes = [
628
"user-library-read,user-library-modify", # Comma-separated string
629
["user-library-read", "user-library-modify"], # List
630
("user-library-read", "user-library-modify"), # Tuple
631
]
632
633
for scope in scopes:
634
normalized = normalize_scope(scope)
635
print(f"Input: {scope}")
636
print(f"Normalized: '{normalized}'")
637
print()
638
639
# Build scopes programmatically
640
def build_scope_for_features(features):
641
"""Build OAuth scope based on required features."""
642
scope_mapping = {
643
'library': ['user-library-read', 'user-library-modify'],
644
'playlists': ['playlist-read-private', 'playlist-modify-private', 'playlist-modify-public'],
645
'playback': ['user-read-playback-state', 'user-modify-playback-state'],
646
'following': ['user-follow-read', 'user-follow-modify'],
647
'top_content': ['user-top-read'],
648
'recently_played': ['user-read-recently-played'],
649
'profile': ['user-read-private', 'user-read-email']
650
}
651
652
required_scopes = []
653
for feature in features:
654
if feature in scope_mapping:
655
required_scopes.extend(scope_mapping[feature])
656
657
# Remove duplicates and normalize
658
unique_scopes = list(set(required_scopes))
659
return normalize_scope(unique_scopes)
660
661
# Example usage
662
app_features = ['library', 'playlists', 'playback']
663
required_scope = build_scope_for_features(app_features)
664
print(f"Required scope: {required_scope}")
665
```
666
667
## Environment Variables
668
669
Configure cache and authentication settings using environment variables:
670
671
```bash
672
# Client credentials
673
export SPOTIPY_CLIENT_ID='your_client_id'
674
export SPOTIPY_CLIENT_SECRET='your_client_secret'
675
export SPOTIPY_REDIRECT_URI='http://localhost:8080/callback'
676
677
# Cache settings
678
export SPOTIPY_CACHE_PATH='.spotify_cache'
679
export SPOTIPY_CACHE_USERNAME='default_user'
680
681
# Redis settings (if using Redis cache)
682
export REDIS_URL='redis://localhost:6379/0'
683
export SPOTIPY_REDIS_KEY='spotify_tokens'
684
```
685
686
## Best Practices
687
688
1. **Choose appropriate cache type** based on your application architecture
689
2. **Use unique cache keys** for multi-user applications
690
3. **Handle cache expiration** gracefully with token refresh
691
4. **Secure token storage** especially in production environments
692
5. **Clear caches on logout** to prevent unauthorized access
693
6. **Monitor cache performance** for high-traffic applications