or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

authentication.mdbrowse.mdcache.mdclient.mdindex.mdplayback.mdplaylists.mdpodcasts.mduser-library.md

cache.mddocs/

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