110

I understand that Flask has the int, float and path converters, but the application we're developing has more complex patterns in its URLs.

Is there a way we can use regular expressions, as in Django?

Alistair
  • 8,066
  • 14
  • 39
  • 43

3 Answers3

209

Even though Armin beat me to the punch with an accepted answer I thought I'd show an abbreviated example of how I implemented a regex matcher in Flask just in case anyone wants a working example of how this could be done.

from flask import Flask
from werkzeug.routing import BaseConverter

app = Flask(__name__)

class RegexConverter(BaseConverter):
    def __init__(self, url_map, *items):
        super(RegexConverter, self).__init__(url_map)
        self.regex = items[0]


app.url_map.converters['regex'] = RegexConverter

@app.route('/<regex("[abcABC0-9]{4,6}"):uid>-<slug>/')
def example(uid, slug):
    return "uid: %s, slug: %s" % (uid, slug)


if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

this URL should return with 200: http://localhost:5000/abc0-foo/

this URL should will return with 404: http://localhost:5000/abcd-foo/

Philip Southam
  • 15,843
  • 6
  • 28
  • 20
  • 5
    But does this mean that the regular expressions are compiled, or are they evaluated on the fly? – Games Brainiac Jul 13 '13 at 10:38
  • 1
    This looks like the regex will be evaluated directly at runtime. This shouldn't be problematic for smaller apps (or apps that reuse regex's multiple times, I'd think) as the last couple of regex patterns are stored compiled in memory. – bbenne10 Jul 18 '13 at 14:13
  • 9
    How does this work? The pattern is set to `self.regex`, but where does the match happen? – Justin Aug 20 '15 at 20:48
  • @Justin The matching happens in Werkzeug's internals [Here](https://github.com/pallets/werkzeug/blob/master/werkzeug/routing.py#L782) and somewhere in the definition of a rule that I haven't found. – AlexLordThorsen Nov 22 '16 at 03:45
50

You can hook in custom converters that match for arbitrary expressions: Custom Converter

from random import randrange
from werkzeug.routing import Rule, Map, BaseConverter, ValidationError

class BooleanConverter(BaseConverter):

    def __init__(self, url_map, randomify=False):
        super(BooleanConverter, self).__init__(url_map)
        self.randomify = randomify
        self.regex = '(?:yes|no|maybe)'

    def to_python(self, value):
        if value == 'maybe':
            if self.randomify:
                return not randrange(2)
            raise ValidationError()
        return value == 'yes'

    def to_url(self, value):
        return value and 'yes' or 'no'

url_map = Map([
    Rule('/vote/<bool:werkzeug_rocks>', endpoint='vote'),
    Rule('/vote/<bool(randomify=True):foo>', endpoint='foo')
], converters={'bool': BooleanConverter})
Jouni K. Seppänen
  • 43,139
  • 5
  • 71
  • 100
Armin Ronacher
  • 31,998
  • 13
  • 65
  • 69
20

You could also write a catch all type of route and do complex routing within the method:

from flask import Flask
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'], defaults={'path': ''})
@app.route('/<path:path>', methods=['GET', 'POST'])
def catch_all(path):
    return 'You want path: %s' % path

if __name__ == '__main__':
    app.run()

This will match any request. See more details here: Catch-All URL.

Maicon Mauricio
  • 2,052
  • 1
  • 13
  • 29
Zorayr
  • 23,770
  • 8
  • 136
  • 129
  • I have an error, would you give me some clues? File "/app/catch_all.py", line 234, in @app.route('/', methods=['GET']) File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1080, in decorator File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 64, in wrapper_func File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1051, in add_url_rule 'existing endpoint function: %s' % endpoint) AssertionError: View function mapping is overwriting an existing endpoint function: test – spark Jul 26 '18 at 20:39