2013-03-11 0:32

Flask is one of the countless web frameworks available for Python. It’s probably my favorite, because it’s rather minimal, simple and easy to use. All the expected features are there, too, although they might not be as powerful as in some more advanced tools.

As an example, here’s how you define some simple request handler, bound to a parametrized URL pattern:

from flask import abort , render_template from myapplication import app , db_session , Post @ app. route ( '/post/<int:post_id>' ) def blogpost ( post_id ) : post = db_session. query ( Post ) . get ( post_id ) if not post: abort ( 404 ) return render_template ( 'post.html' , post = post )

This handler responds to requests that go to /post/42 and similar paths. The syntax for those URL patterns is not very advanced: parameters can only be captured as path segments rather than arbitrary groups within a regular expression. (You can still use query string arguments, of course).

On the flip side, reversing the URL – building it from handler name and parameters – is always possible. There is a url_for function which does just that. It can be used both from Python code and, perhaps more usefully, from HTML (Jinja) templates:

h2 > / h2 > Recent posts ul > {% for post in posts %} li > a href = "{{ url_for('blogpost', post_id=post.id) }}" > {{ post.title }} / a > / li > {% endfor %} / ul >

Parameters can have types, too. We’ve seen, for example, that post_id was defined as int in the URL pattern for blogpost handler. These types are checked during the actual routing of HTTP requests, but also by the url_for function:

pattern = url_for ( 'blogpost' , post_id = '<id>' ) # raises ValueError

Most of the time, this little bit of “static typing” is a nice feature. However, there are some cases where this behavior of url_for is a bit too strict. Anytime we don’t intend to invoke the resulting URL directly, we might want a little more flexibility.

Biggest case-in-point are various client-side templates, used by JavaScript code to update small pieces of HTML without reloading the whole page. If you, for example, wanted to rewrite the template above to use Underscore templates, you would still want url_for to format the blogpost URL pattern:

h2 > / h2 > Recent posts ul > <% _.each ( posts, function ( post ) { %> a href = "{{ url_for('blogpost', post_id='<%= post.id %> ') }}"> <%- post. name %> / a > <% } ) ; %> / ul >

Assuming you don’t feel dizzy from seeing two templating languages at once, you will obviously notice that '' is not a valid int value. But it’s a correct value for post_id parameter, because the resulting URL ( /post/ ) would not be used immediately. Instead, it would be just sent to the browser, where some JS code would pick it up and replace the Underscore placeholder with an actual ID.

Unfortunately, bypassing the default strictness of url_for is not exactly easy.



Actually, it doesn’t seem possible, at least not without forking Flask and fiddling with its internals. So if you are not in the mood for that, here’s a hack that I devised:

import re from flask import url_for from myflaskapp import app def url_for_ex(endpoint, **values): """Improved version of standard Flask's :func:`url_for` that accepts an additional, optional ``_strict`` argument. :param _strict: If ``False``, values for the endpoint are not checked for compatibility with types defined in the URL rule. Default: ``True``. """ strict = values.pop('_strict', True) if not strict: # search for matching URL rule; if that fails, we will just # invoke url_for() normally so that Flask can handle the error url_arguments = set(key for key in values.iterkeys() if not key.startswith('_')) url_rule = None for rule in app.url_map.iter_rules(endpoint): rule_arguments = set(rule._converters.iterkeys()) if rule_arguments == url_arguments: url_rule = rule.rule break if url_rule: # replace argument placeholders in the URL rule (``<int:foo>``...) # with values provided for those arguments regex = '|'.join(r'\<(?:\w+\:)?(%s)\>' % arg for arg in values) return re.sub(regex, lambda m: str(values[m.group(1)]), url_rule) return url_for(endpoint, **values) app.jinja_env.globals['url_for'] = url_for_ex

So, what the hell is this doing?… Well, it’s quite simple, really. I just wrap a normal url_for call in a function that works almost exactly the same – but with one additional feature.

Namely, it accepts a brand new _strict argument, which allows the caller to turn type checking on and off. The former case is the default, of course. For the latter, though, there is hardly any point in actually using url_for : we can perform the URL “building” through a simple replacement:

regex = '|' . join ( r ' \< (?: \w + \: )?(%s) \> ' % arg for arg in values ) return re . sub ( regex , lambda m: str ( values [ m. group ( 1 ) ] ) , url_rule )

I’m sure most of you well versed in intricacies of regular expressions (*cough*), but just in case…

What we’re doing here is building a regex that matches any form of argument placeholder – such as <post> or <int:post> – of any URL argument given to the function. Resulting regex is then applied to the actual url_rule (e.g. /post/<int:post_id> ) and every match is replaced with the intended value of corresponding parameter.

As for getting the url_rule , that’s what the loop above is doing. Thankfully, Flask exposes its URL routing rules as the url_map attribute. We just need to find the one that has the exact same arguments as requested by the caller.