0
# Caching and Versioning
1
2
Advanced caching and versioning system for optimizing build performance and managing asset updates, including cache invalidation, version generation, and manifest management.
3
4
## Capabilities
5
6
### Cache Implementation
7
8
Cache systems for storing processed assets and improving build performance.
9
10
```python { .api }
11
class FilesystemCache:
12
def __init__(self, directory):
13
"""
14
File-based cache implementation.
15
16
Parameters:
17
- directory: Cache directory path
18
"""
19
20
def get(self, key):
21
"""Get cached value by key."""
22
23
def set(self, key, value):
24
"""Set cached value."""
25
26
def has(self, key):
27
"""Check if key exists in cache."""
28
29
def delete(self, key):
30
"""Delete cached value."""
31
32
def clear(self):
33
"""Clear all cached values."""
34
35
class MemoryCache:
36
def __init__(self, max_size=1000):
37
"""
38
In-memory cache implementation.
39
40
Parameters:
41
- max_size: Maximum number of cached items
42
"""
43
44
def get(self, key):
45
"""Get cached value by key."""
46
47
def set(self, key, value):
48
"""Set cached value."""
49
50
def has(self, key):
51
"""Check if key exists in cache."""
52
53
def delete(self, key):
54
"""Delete cached value."""
55
56
def clear(self):
57
"""Clear all cached values."""
58
59
def get_cache(cache_option, env=None):
60
"""
61
Create cache instance from configuration.
62
63
Parameters:
64
- cache_option: Cache configuration (str/bool/Cache instance)
65
- env: Environment for context
66
67
Returns:
68
Cache instance or None
69
"""
70
```
71
72
Example usage:
73
```python
74
from webassets.cache import FilesystemCache, MemoryCache, get_cache
75
76
# Filesystem cache
77
fs_cache = FilesystemCache('./.webassets-cache')
78
fs_cache.set('bundle_123', compiled_content)
79
cached = fs_cache.get('bundle_123')
80
81
# Memory cache
82
mem_cache = MemoryCache(max_size=500)
83
mem_cache.set('filter_abc', processed_data)
84
85
# Get cache from configuration
86
cache = get_cache('filesystem') # Uses default directory
87
cache = get_cache(True) # Uses default cache
88
cache = get_cache(False) # No caching
89
cache = get_cache('memory') # Memory cache
90
```
91
92
### Version Generation
93
94
Version calculation strategies for cache busting and asset update detection.
95
96
```python { .api }
97
class Version:
98
def determine_version(self, bundle, ctx, hunk=None):
99
"""
100
Calculate version string for bundle.
101
102
Parameters:
103
- bundle: Bundle instance
104
- ctx: Version context
105
- hunk: Optional content hunk
106
107
Returns:
108
Version string
109
"""
110
111
class TimestampVersion(Version):
112
def determine_version(self, bundle, ctx, hunk=None):
113
"""Generate version based on file timestamps."""
114
115
class HashVersion(Version):
116
def determine_version(self, bundle, ctx, hunk=None):
117
"""Generate version based on content hash."""
118
119
def get_versioner(versioner_option):
120
"""
121
Create versioner from configuration.
122
123
Parameters:
124
- versioner_option: Versioner specification
125
126
Returns:
127
Version instance
128
"""
129
```
130
131
Example usage:
132
```python
133
from webassets.version import HashVersion, TimestampVersion, get_versioner
134
135
# Hash-based versioning
136
hash_versioner = HashVersion()
137
version = hash_versioner.determine_version(bundle, ctx)
138
139
# Timestamp-based versioning
140
timestamp_versioner = TimestampVersion()
141
version = timestamp_versioner.determine_version(bundle, ctx)
142
143
# Get versioner from config
144
versioner = get_versioner('hash') # Hash versioning
145
versioner = get_versioner('timestamp') # Timestamp versioning
146
versioner = get_versioner(False) # No versioning
147
```
148
149
### Manifest Management
150
151
Manifest systems for tracking generated assets and their versions.
152
153
```python { .api }
154
class Manifest:
155
def remember(self, bundle, ctx, version, output_files):
156
"""
157
Store bundle information in manifest.
158
159
Parameters:
160
- bundle: Bundle instance
161
- ctx: Context
162
- version: Version string
163
- output_files: Generated output file paths
164
"""
165
166
def query(self, bundle, ctx):
167
"""
168
Query manifest for bundle information.
169
170
Parameters:
171
- bundle: Bundle instance
172
- ctx: Context
173
174
Returns:
175
Stored manifest information or None
176
"""
177
178
class FileManifest(Manifest):
179
def __init__(self, filename, format='json'):
180
"""
181
File-based manifest storage.
182
183
Parameters:
184
- filename: Manifest file path
185
- format: Storage format ('json', 'yaml')
186
"""
187
188
def get_manifest(manifest_option, env=None):
189
"""
190
Create manifest from configuration.
191
192
Parameters:
193
- manifest_option: Manifest specification
194
- env: Environment for context
195
196
Returns:
197
Manifest instance or None
198
"""
199
```
200
201
Example usage:
202
```python
203
from webassets.version import FileManifest, get_manifest
204
205
# JSON manifest
206
manifest = FileManifest('manifest.json', format='json')
207
manifest.remember(bundle, ctx, version, ['gen/app-abc123.js'])
208
info = manifest.query(bundle, ctx)
209
210
# Get manifest from config
211
manifest = get_manifest('json:assets.json') # JSON file
212
manifest = get_manifest('yaml:assets.yaml') # YAML file
213
manifest = get_manifest(False) # No manifest
214
```
215
216
### Version Exceptions
217
218
Exception handling for version-related errors.
219
220
```python { .api }
221
class VersionIndeterminableError(Exception):
222
"""Raised when version cannot be determined."""
223
```
224
225
## Advanced Caching and Versioning Examples
226
227
### Environment Configuration
228
229
```python
230
from webassets import Environment
231
232
# Basic caching configuration
233
env = Environment(
234
'./static',
235
'/static',
236
cache=True, # Use default filesystem cache
237
versions='hash' # Hash-based versioning
238
)
239
240
# Advanced caching configuration
241
env = Environment(
242
'./static',
243
'/static',
244
cache='filesystem:./.cache', # Custom cache directory
245
versions='timestamp', # Timestamp versioning
246
manifest='json:manifest.json' # JSON manifest file
247
)
248
249
# Production configuration
250
prod_env = Environment(
251
'./static',
252
'/static',
253
debug=False,
254
cache='filesystem',
255
versions='hash',
256
manifest='json:public/assets.json',
257
auto_build=False
258
)
259
260
# Development configuration
261
dev_env = Environment(
262
'./static',
263
'/static',
264
debug=True,
265
cache=False, # No caching in development
266
versions=False, # No versioning in development
267
auto_build=True
268
)
269
```
270
271
### Custom Cache Implementation
272
273
```python
274
from webassets.cache import Cache
275
import redis
276
277
class RedisCache(Cache):
278
"""Redis-based cache implementation."""
279
280
def __init__(self, host='localhost', port=6379, db=0, prefix='webassets:'):
281
self.redis = redis.Redis(host=host, port=port, db=db)
282
self.prefix = prefix
283
284
def _key(self, key):
285
return f"{self.prefix}{key}"
286
287
def get(self, key):
288
value = self.redis.get(self._key(key))
289
return value.decode('utf-8') if value else None
290
291
def set(self, key, value):
292
self.redis.set(self._key(key), value.encode('utf-8'))
293
294
def has(self, key):
295
return self.redis.exists(self._key(key))
296
297
def delete(self, key):
298
self.redis.delete(self._key(key))
299
300
def clear(self):
301
keys = self.redis.keys(f"{self.prefix}*")
302
if keys:
303
self.redis.delete(*keys)
304
305
# Usage
306
redis_cache = RedisCache(host='localhost', port=6379)
307
env = Environment('./static', '/static', cache=redis_cache)
308
```
309
310
### Custom Version Strategy
311
312
```python
313
from webassets.version import Version
314
import hashlib
315
import os
316
317
class GitHashVersion(Version):
318
"""Version based on git commit hash."""
319
320
def determine_version(self, bundle, ctx, hunk=None):
321
import subprocess
322
323
try:
324
# Get current git commit hash
325
result = subprocess.run(
326
['git', 'rev-parse', '--short', 'HEAD'],
327
capture_output=True,
328
text=True,
329
cwd=ctx.env.directory
330
)
331
332
if result.returncode == 0:
333
git_hash = result.stdout.strip()
334
335
# Combine with bundle content hash for uniqueness
336
if hunk:
337
content_hash = hashlib.md5(hunk.data().encode()).hexdigest()[:8]
338
return f"{git_hash}-{content_hash}"
339
else:
340
return git_hash
341
342
except Exception:
343
pass
344
345
# Fallback to timestamp
346
from webassets.version import TimestampVersion
347
return TimestampVersion().determine_version(bundle, ctx, hunk)
348
349
# Usage
350
env = Environment('./static', '/static', versions=GitHashVersion())
351
```
352
353
### Advanced Manifest Configuration
354
355
```python
356
from webassets.version import FileManifest
357
import json
358
import os
359
360
class CustomManifest(FileManifest):
361
"""Enhanced manifest with additional metadata."""
362
363
def remember(self, bundle, ctx, version, output_files):
364
# Call parent implementation
365
super().remember(bundle, ctx, version, output_files)
366
367
# Add custom metadata
368
manifest_data = self._load_manifest()
369
370
bundle_key = bundle.resolve_output(ctx)
371
if bundle_key in manifest_data:
372
manifest_data[bundle_key].update({
373
'build_time': datetime.utcnow().isoformat(),
374
'bundle_id': bundle.id,
375
'input_files': list(bundle.resolve_contents(ctx)),
376
'filters': [f.id() for f in bundle.filters],
377
'file_sizes': {
378
f: os.path.getsize(os.path.join(ctx.env.directory, f))
379
for f in output_files
380
}
381
})
382
383
self._save_manifest(manifest_data)
384
385
def _load_manifest(self):
386
try:
387
with open(self.filename, 'r') as f:
388
return json.load(f)
389
except (FileNotFoundError, json.JSONDecodeError):
390
return {}
391
392
def _save_manifest(self, data):
393
with open(self.filename, 'w') as f:
394
json.dump(data, f, indent=2, sort_keys=True)
395
396
# Usage
397
custom_manifest = CustomManifest('detailed-manifest.json')
398
env = Environment('./static', '/static', manifest=custom_manifest)
399
```
400
401
### Cache Optimization Strategies
402
403
```python
404
from webassets import Environment, Bundle
405
from webassets.cache import FilesystemCache
406
407
# Hierarchical caching
408
class HierarchicalCache:
409
"""Multi-level cache with memory and filesystem tiers."""
410
411
def __init__(self, memory_size=100, fs_directory='.cache'):
412
from webassets.cache import MemoryCache, FilesystemCache
413
self.memory = MemoryCache(max_size=memory_size)
414
self.filesystem = FilesystemCache(fs_directory)
415
416
def get(self, key):
417
# Try memory first
418
value = self.memory.get(key)
419
if value is not None:
420
return value
421
422
# Fall back to filesystem
423
value = self.filesystem.get(key)
424
if value is not None:
425
# Promote to memory
426
self.memory.set(key, value)
427
428
return value
429
430
def set(self, key, value):
431
# Store in both tiers
432
self.memory.set(key, value)
433
self.filesystem.set(key, value)
434
435
def has(self, key):
436
return self.memory.has(key) or self.filesystem.has(key)
437
438
def delete(self, key):
439
self.memory.delete(key)
440
self.filesystem.delete(key)
441
442
def clear(self):
443
self.memory.clear()
444
self.filesystem.clear()
445
446
# Usage
447
hierarchical_cache = HierarchicalCache(memory_size=200)
448
env = Environment('./static', '/static', cache=hierarchical_cache)
449
```
450
451
### Conditional Versioning
452
453
```python
454
def setup_assets_environment(mode='development'):
455
"""Setup environment with mode-specific caching and versioning."""
456
457
if mode == 'development':
458
return Environment(
459
'./src/assets',
460
'/assets',
461
debug=True,
462
cache=False, # No caching in dev
463
versions=False, # No versioning in dev
464
auto_build=True
465
)
466
467
elif mode == 'staging':
468
return Environment(
469
'./build/assets',
470
'/assets',
471
debug=False,
472
cache='memory', # Fast memory cache
473
versions='timestamp', # Simple versioning
474
auto_build=False
475
)
476
477
elif mode == 'production':
478
return Environment(
479
'./dist/assets',
480
'/assets',
481
debug=False,
482
cache='filesystem:.cache', # Persistent cache
483
versions='hash', # Content-based versioning
484
manifest='json:public/assets.json',
485
auto_build=False
486
)
487
488
# Usage based on environment
489
import os
490
mode = os.environ.get('NODE_ENV', 'development')
491
env = setup_assets_environment(mode)
492
```
493
494
### Bundle-Specific Versioning
495
496
```python
497
from webassets import Bundle
498
499
# Vendor bundle with stable versioning
500
vendor_bundle = Bundle(
501
'vendor/jquery.js',
502
'vendor/bootstrap.js',
503
filters='jsmin',
504
output='gen/vendor-stable.js',
505
version='vendor-v1.0' # Fixed version for stable vendor code
506
)
507
508
# App bundle with dynamic versioning
509
app_bundle = Bundle(
510
'src/app.js',
511
'src/utils.js',
512
filters=['babel', 'uglifyjs'],
513
output='gen/app-%(version)s.js' # Dynamic version placeholder
514
)
515
516
# CSS with hash versioning
517
css_bundle = Bundle(
518
'scss/main.scss',
519
filters=['libsass', 'autoprefixer', 'cssmin'],
520
output='gen/app-%(version)s.css'
521
)
522
523
env.register('vendor_js', vendor_bundle)
524
env.register('app_js', app_bundle)
525
env.register('app_css', css_bundle)
526
```