├── LICENSE ├── README.md ├── flask_cache_bust └── __init__.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Christopher Mitchell, CloudBolt Software 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Flask-CacheBust is a Flask extension that adds a hash to the URL of each static 2 | file. This lets you safely declare your static resources as indefinitely 3 | cacheable because they automatically get new URLs when their contents change. 4 | 5 | # Usage 6 | 7 | Install Flask-CacheBust by placing the "flask_cache_bust" folder somewhere 8 | importable from Python. Import the extension and use it to augment your app: 9 | 10 | ```python 11 | from flask.ext import cache_bust 12 | 13 | # ... 14 | 15 | cache_bust.init_cache_busting(app) 16 | ``` 17 | 18 | The `url_for` function will now cache-bust your static files. For example, this 19 | template: 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | will render like this: 26 | 27 | ```html 28 | 29 | ``` 30 | 31 | The "1fc6e32" part will change whenever "main.min.js" changes. Now you can 32 | configure long cache expiration dates on your static files with a header like 33 | `Cache-Control: max-age=31536000`! 34 | 35 | # Motivation 36 | 37 | This was originally written for www.cloudboltsoftware.com while exercising an 38 | obsession for minimizing page load time and delivering a snappy browsing 39 | experience. It improves over Last-Modified or ETag cache schemes by enabling an 40 | expiration date to be set, which removes round-trip checks to see if a 41 | browser's cached version of a resource is still valid. 42 | -------------------------------------------------------------------------------- /flask_cache_bust/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | 4 | 5 | def init_cache_busting(app): 6 | """ 7 | Configure `app` to so that `url_for` adds a unique prefix to URLs generated 8 | for the `'static'` endpoint. Also make the app able to serve cache-busted 9 | static files. 10 | 11 | This allows setting long cache expiration values on static resources 12 | because whenever the resource changes, so does its URL. 13 | """ 14 | 15 | static_folder = app.static_folder # the rooted path to the static file folder 16 | 17 | bust_table = {} # map from an unbusted filename to a busted one 18 | unbust_table = {} # map from a busted filename to an unbusted one 19 | 20 | app.logger.debug('Computing cache-busting values...') 21 | # compute (un)bust tables. 22 | for dirpath, dirnames, filenames in os.walk(static_folder): 23 | for filename in filenames: 24 | # compute version component 25 | rooted_filename = os.path.join(dirpath, filename) 26 | with open(rooted_filename, 'r') as f: 27 | version = hashlib.md5(f.read()).hexdigest()[:7] 28 | 29 | # add version 30 | unbusted = os.path.relpath(rooted_filename, static_folder) 31 | busted = os.path.join(version, unbusted) 32 | 33 | # save computation to tables 34 | bust_table[unbusted] = busted 35 | unbust_table[busted] = unbusted 36 | app.logger.debug('Finished computing cache-busting values') 37 | 38 | def bust_filename(filename): 39 | return bust_table.get(filename, filename) 40 | 41 | def unbust_filename(filename): 42 | return unbust_table.get(filename, filename) 43 | 44 | @app.url_defaults 45 | def reverse_to_cache_busted_url(endpoint, values): 46 | """ 47 | Make `url_for` produce busted filenames when using the 'static' endpoint. 48 | """ 49 | if endpoint == 'static': 50 | values['filename'] = bust_filename(values['filename']) 51 | 52 | def debusting_static_view(filename): 53 | """ 54 | Serve a request for a static file having a busted name. 55 | """ 56 | return original_static_view(filename=unbust_filename(filename)) 57 | 58 | # Replace the default static file view with our debusting view. 59 | original_static_view = app.view_functions['static'] 60 | app.view_functions['static'] = debusting_static_view 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | setup( 3 | name='Flask-CacheBust', 4 | version='1.0.0', 5 | description='Flask extension that cache-busts static files', 6 | packages=['flask_cache_bust'], 7 | license='MIT', 8 | url='https://github.com/ChrisTM/Flask-CacheBust', 9 | install_requires=[ 10 | 'Flask', 11 | ], 12 | ) 13 | --------------------------------------------------------------------------------