2

I have a function that I'm using to list routes for my Flask site and would like to be sure it is sorting them in the order in which they will be matched by Flask/Werkzeug.

Currently I have

def routes(verbose, wide, nostatic):
    """List routes supported by the application"""

    for rule in sorted(app.url_map.iter_rules()):
        if nostatic and rule.endpoint == 'static':
            continue

        if verbose:
            fmt = "{:45s} {:30s} {:30s}" if wide else "{:35s} {:25s} {:25s}"
            line = fmt.format(rule, rule.endpoint, ','.join(rule.methods))
        else:
            fmt = "{:45s}" if wide else "{:35s}"
            line = fmt.format(rule)

        print(line)

but this just seems to sort the routes in the order I've defined them in my code.

How do I sort Flask/Werkzeug routes in the order in which they will be matched?


Another approach: Specifying a host and a path and seeing what rule applies.

    urls = app.url_map.bind(host)
    try:
        m = urls.match(match, "GET")
        z = '{}({})'.format(m[0], ','.join(["{}='{}'".format(arg, m[1][arg]) for arg in m[1]] +
                                           ["host='{}'".format(host)]))
        return z
    except NotFound:
        return
orome
  • 45,163
  • 57
  • 202
  • 418
  • They are showing in that order because that is how they are stored. See https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/routing.py#L1397 and within that https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/routing.py#L1489 for the inner workings :) – Joe Doherty Feb 23 '15 at 21:06
  • @JoeDoherty: So is `sorted(app.url_map.iter_rules())` putting them in the order in which they will be matched? – orome Feb 23 '15 at 21:09
  • ``for rule in self.map._rules:`` I don't think they are sorted. Can I ask what you need the exact order for? – Joe Doherty Feb 23 '15 at 21:13
  • @JoeDoherty: For the function above. Make sure they're being checked in the order I intend. So: how do I get them in the order they will be matched? – orome Feb 23 '15 at 21:16
  • Get rid of the `sorted()` and that should be correct. There is no reason to sort it. – Joe Doherty Feb 23 '15 at 21:21
  • @JoeDoherty: Ah, I see. Didn't understand the comment at first. That looks right. – orome Feb 23 '15 at 21:23
  • @JoeDoherty: Though, *are* these in the right order: `/` `//` `/` `//` `/static/` ? That seems wrong (for one thing, the static one ought to be first, right?) – orome Feb 23 '15 at 21:26
  • It doesn't really matter. Both will be highlighted but the simplest will be chosen so "static" will be matched before . I use /about and / and about will never be matched in the second. Its more complex than the order they appear in. That is why I asked earlier what you need this for. – Joe Doherty Feb 23 '15 at 22:45
  • @JoeDoherty: So there's no way to list them in an order that corresponds to how they will be matched? – orome Feb 23 '15 at 22:50
  • Not really because they will all be checked and then a sort of weight given to them based of complexity. What issue is this causing? – Joe Doherty Feb 23 '15 at 22:51
  • @JoeDoherty: Mostly I want a way of checking what rule is going to apply to a given path and host, but I'm getting results that make no sense. For example, any path beginning with `static` seems to invoke a special static rule, even if `static_folder` is not `static` (though the first chunk of code above responds as expected, listing the changed `static_folder` as the match; while the second chunk does not, never getting to static at all; even though the actual application, always seems to respond to paths beginning with 'static' with the static endpoint, regardless of `static_folder`). – orome Feb 23 '15 at 22:59
  • @JoeDoherty: The problem began [here](http://stackoverflow.com/q/28663065/656912), though that's not really the issue now, since I can get around that by just changing the name of the folder where things are kept to something other than 'static' (which I had stupidly used before). – orome Feb 23 '15 at 23:03
  • @JoeDoherty: So, to be a bit clearer (I hope): if I navigate to `something.com/static/xyz` the special static rule is invoked, but if I use the second bit of code above to see what rule is invoked for `host=something.com` and `match=/static/xyz`, the static rule is NOT matched. And if I change the `static_folder` to something else, I get the same result. Meanwhile, the first chunk of code lists paths with `static_folder` mapping to the static rule. – orome Feb 23 '15 at 23:35
  • @JoeDoherty: I think you've answered this question though: That's the "order", such as it is; but it doesn't to what I think it does. I'd accept that as an answer here, and I'll take the other issues to a new question about how to figure out what rule will match a given path and host (and why my code doesn't behave the way my application does; and how to get around the sticky/cryptic 'static' rule). – orome Feb 24 '15 at 00:12

1 Answers1

2

On each request Flask/Werkzeug reordered self.map.update() rules if it need with route weights (see my another answer URL routing conflicts for static files in Flask dev server).

You can found that Map.update method update _rules (see https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/routing.py#L1313).

So you can do the same thing:

sorted(current_app.url_map._rules, key=lambda x: x.match_compare_key())

Or if all routes already loaded and any request done (called Map.build or Map.match) just use app.url_map._rules - it will be sorted. In request app.url_map._rules already sorted, because Map.match was called before dispatcher (match rule with dispatcher).

You also can found that Map.update called in Map.iter_rules, so you can just use this method current_app.url_map.iter_rules() (see https://github.com/mitsuhiko/werkzeug/blob/master/werkzeug/routing.py#L1161).

Community
  • 1
  • 1
tbicr
  • 24,790
  • 12
  • 81
  • 106
  • So `sorted(... x.match_compare_key())` and `current_app.url_map.iter_rules()` will give the same result (they do for me) and that result will be "sorted", correct? – orome Feb 24 '15 at 13:10
  • What's confusing me is if that's true, paths that begin with `static` are behaving very strangely. Both on my development machine and my server the first bit of (listing) code lists the static rule last, and that's also how the second bit of code works (matching `static` with a non-static rule). That's all consistent. And so is he actual application behavior on my development machine: my rules and not the static rule match a static path. BUT on the server, (despite both bits of code in my question putting static last), the static rule catches any static paths! – orome Feb 24 '15 at 13:17
  • All examples in my answer should give same result. I not understood completely your second comment, but you should understand that `static` route the same as another - it have rule and dispatcher (endpoint) that return files from some directory. On you prod server static path can intercepted by nginx, apache or another web server according to config. – tbicr Feb 24 '15 at 15:18
  • So what's probably happening is that the server is [intercepting paths that begin with 'static' before Flask/Werkzeug](http://stackoverflow.com/q/28697176/656912) even has a chance to process them? – orome Feb 24 '15 at 15:20
  • Maybe, I do not know, but it look very strange that AWS smarter than developer and intercept this rule (not statics, not assets or something else). You can change `static_url_path` of your application to unpredictable and check is it still correct (it will work correctly when you use `url_for` to generate static urls). – tbicr Feb 24 '15 at 15:31
  • As near as I can tell `static_url_path` doesn't seem to affect what AWS-EB does. – orome Feb 24 '15 at 16:16