Skip to content


Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP


Generate Url Content based on headers as well. #18

wants to merge 1 commit into from

2 participants


Curious if you were interested in this idea. I needed a way to generate alternate files based on the headers sent to the servers, specifically Ajax requests (request.is_xhr).

So this enhancement will allow you to create a url generate and yield an additional type FreezerTarget, which allows the passing of headers, which in turn make the url unique.

If it's something that might be interesting, lemme know any notes you have or any concerns and I would be happy to look into it.

So the usage would look like this:

    def mycutom_test():
        endpoint = "myblueprint.test"
        values = {'product_id': 1}
        headers = {'X-Requested-With': 'XMLHttpRequest'}
        yield endpoint, values
        yield FreezerTarget(endpoint=endpoint, values=values, headers=headers, filename="index_ajax")
        yield FreezerTarget(endpoint=endpoint, values=values, headers={'Accept': 'application/json'}, filename="index_json")

It basically doesn't change the current availability of what can be yielded, it just adds the ability to also yield a FreezerTarget object in addition to string, tuples and dicts.


I had some comments on implementation details (FreezerTarget feels like a dict; just using a dict could be simpler. No need for SHA1, tuples are fine in a Python set.)

But first, the bigger picture: I think this isn’t gonna work. If you’re asking for this, I imagine that you have some JavaScript code that makes requests to the same URL with different headers and except different responses. Once you app is frozen all files are static, so a request an URL will always give the same responses; that’s why you want different filenames. But then your JavaScript code doesn’t work anymore, or it needs to behave differently that in dev mode and make requests to different URLs. If you do this, why not have different URLs in the first place in your app?

Maybe I’m wrong and this does work for you. In that case: how, and what’s your use case?

Digression: Frozen-Flask isn’t really meant to take any dynamic app and freeze it into something useful. In my mind it is a tool closer to GNU Make where apps are makefiles that instruct how to generate a set of files. Flask is just a nice way to describe parameterized URLs/filenames. (And it happens to give a nice server that generate pages dynamically for development without constant rebuilds.) My point is: if your app is made to be frozen, try changing your app before changing Frozen-Flask.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 12, 2012
  1. @aventurella

    added ability to generate frozen files based on unique request header…

    aventurella authored
    …s, specifically, AJAX based requests
This page is out of date. Refresh to see the latest.
Showing with 72 additions and 15 deletions.
  1. +72 −15 flask_frozen/
87 flask_frozen/
@@ -20,9 +20,9 @@
import urllib
import warnings
import collections
+import hashlib
from unicodedata import normalize
from threading import Lock
from werkzeug.exceptions import HTTPException
from flask import (Flask, Blueprint, url_for, request, send_from_directory,
@@ -50,6 +50,27 @@ class MimetypeMismatchWarning(Warning):
+class FreezerTarget(object):
+ def __init__(self, values={}, endpoint=None, headers=None, url=None, filename=None):
+ self.values = values
+ self.endpoint = endpoint
+ self.headers = headers
+ self.url = url
+ self.filename = filename
+ @property
+ def context_url(self):
+ context = ""
+ if self.headers:
+ for key in sorted(self.headers.iterkeys()):
+ context = context + "%s:%s\n" % (key.lower(), self.headers[key])
+ context = hashlib.sha1(context).hexdigest()
+ return '%s|%s' % (self.url, context)
+ return self.url
class Freezer(object):
:param app: your application or None if you use :meth:`init_app`
@@ -135,13 +156,19 @@ def freeze(self):
seen_urls = set()
seen_endpoints = set()
built_files = set()
- for url, endpoint in self._generate_all_urls():
+ for freezer_target in self._generate_all_urls():
+ url = freezer_target.url
+ endpoint = freezer_target.endpoint
- if url in seen_urls:
+ if freezer_target.context_url in seen_urls:
# Don't build the same URL more than once
- seen_urls.add(url)
- new_filename = self._build_one(url)
+ seen_urls.add(freezer_target.context_url)
+ # if freezer_target.filename:
+ # self._build_one(url, filename=freezer_target.filename, headers=freezer_target.headers)
+ # else:
+ # new_filename = self._build_one(url, headers=freezer_target.headers)
+ new_filename = self._build_one(url, filename=freezer_target.filename, headers=freezer_target.headers)
built_files.add(normalize('NFC', new_filename))
if remove_extra:
@@ -164,7 +191,9 @@ def all_urls(self):
generated from :func:`~flask.url_for` calls will not be included
- for url, _endpoint in self._generate_all_urls():
+ for freezer_target in self._generate_all_urls():
+ url = freezer_target.url
+ #_endpoint = freezer_target.endpoint
yield url
def _script_name(self):
@@ -186,26 +215,45 @@ def _generate_all_urls(self):
for generator in url_generators:
for generated in generator():
+ freezer_target = FreezerTarget()
if isinstance(generated, basestring):
+ freezer_target.url = generated
url = generated
- endpoint = None
+ freezer_target.url
+ freezer_target.endpoint = None
+ ###endpoint = None
if is_mapping(generated):
- values = generated
+ freezer_target.values = generated
+ # The endpoint defaults to the name of the
+ # generator function, just like with Flask views.
+ freezer_target.endpoint = generator.__name__
+ ###values = generated
# The endpoint defaults to the name of the
# generator function, just like with Flask views.
- endpoint = generator.__name__
+ ###endpoint = generator.__name__
+ elif isinstance(generated, FreezerTarget):
+ freezer_target = generated
+ if freezer_target.url is None and \
+ freezer_target.endpoint is None:
+ freezer_target.endpoint = generator.__name__
# Assume a tuple.
endpoint, values = generated
- url = url_for(endpoint, **values)
+ freezer_target.endpoint = endpoint
+ freezer_target.values = values
+ url = url_for(freezer_target.endpoint,
+ **freezer_target.values)
assert url.startswith(script_name), (
'url_for returned an URL %r not starting with '
'script_name %r. Bug in Werkzeug?'
% (url, script_name)
- url = url[len(script_name):]
+ ###url = url[len(script_name):]
+ freezer_target.url = url[len(script_name):]
# flask.url_for "quotes" URLs, eg. a space becomes %20
+ url = freezer_target.url
url = urllib.unquote(url)
parsed_url = urlparse.urlsplit(url)
if parsed_url.scheme or parsed_url.netloc:
@@ -215,7 +263,9 @@ def _generate_all_urls(self):
url = parsed_url.path
if not isinstance(url, unicode):
url = url.decode(url_encoding)
- yield url, endpoint
+ freezer_target.url = url
+ ###yield url, endpoint
+ yield freezer_target
def _check_endpoints(self, seen_endpoints):
@@ -238,7 +288,7 @@ def _check_endpoints(self, seen_endpoints):
- def _build_one(self, url):
+ def _build_one(self, url, filename=None, **kwargs):
"""Get the given ``url`` from the app and write the matching file.
client =
@@ -246,7 +296,7 @@ def _build_one(self, url):
with self.url_for_logger:
response = client.get(url, follow_redirects=True,
- base_url=base_url)
+ base_url=base_url, **kwargs)
# The client follows redirects by itself
# Any other status code is probably an error
@@ -255,7 +305,14 @@ def _build_one(self, url):
% (response.status, url))
destination_path = self.urlpath_to_filepath(url)
- filename = os.path.join(self.root, *destination_path.split('/'))
+ if filename:
+ name, ext = os.path.basename(destination_path).split('.')
+ parts = destination_path.split('/')[:-1]
+ parts.append('%s.%s' % (filename, ext))
+ filename = os.path.join(self.root, *parts)
+ else:
+ filename = os.path.join(self.root, *destination_path.split('/'))
# Most web servers guess the mime type of static files by their
Something went wrong with that request. Please try again.