URL routing contexts for static files in Flask dev server

I want to define a URL rule with three variable components, for example:

@app.route('/<var_1>/<var_2>/<var3>/') 

But I believe that the development server evaluates such rules before trying to map static files. So anything:

 /static/images/img.jpg 

will be caught by my url rule, and will not be redirected to the built-in handler of static files. Is there a way to get the development server to match static files in the first place?

PS This is only a problem if the rule contains more than two variable components.

+7
source share
3 answers

This is the werkzeug route optimization feature. See Map.add , Map.update and Rule.match_compare_key :

 def match_compare_key(self): """The match compare key for sorting. Current implementation: 1. rules without any arguments come first for performance reasons only as we expect them to match faster and some common ones usually don't have any arguments (index pages etc.) 2. The more complex rules come first so the second argument is the negative length of the number of weights. 3. lastly we order by the actual weights. :internal: """ return bool(self.arguments), -len(self._weights), self._weights 

There are self.arguments - current arguments, self._weights - the depth of the path.

For '/<var_1>/<var_2>/<var3>/' we have (True, -3, [(1, 100), (1, 100), (1, 100)]) . There is (1, 100) - the default string argument with a maximum length of 100.

For '/static/<path:filename>' we have (True, -2, [(0, -6), (1, 200)]) . There is (0, 1) - path non argument string length static , (1, 200) - the length of the argument string max length 200.

Therefore, I did not find a beautiful way to set my own implementation of Map for Flask.url_map or set priority for a map rule. Solutions:

  • Setting the Flask application as app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static') .
  • Change @app.route('/<var_1>/<var_2>/<var3>/') to @app.route('/prefix/<var_1>/<var_2>/<var3>/') .
  • Add your own converter and use it as @app.route('/<no_static:var_1>/<var_2>/<var3>/') .
  • Import werkzeug.routing , create your own map implementation, change werkzeug.routing.Map to your own implementation, import Flask .
  • Use the server as in production.
+16
source

So, as tbicr pointed tbicr , this behavior is set deep inside Werkzeug, and is actually not a very elegant way to deal with it from Flask. The best workaround I could come up with is:

Define an additional static file handler, for example:

 @app.route('/static/<subdir>/<path:filename>/') def static_subdir(subdir=None, filename=None): directory = app.config['STATIC_FOLDER'] + subdir return send_from_directory(directory, filename) 

Here app.config['STATIC_FOLDER'] is the full path to the static folder on the machine on which the application is running.

Now this handler catches things like /static/images/img.jpg , leaving my view with only three variable components.

+6
source

One way around this is to trick the rule sorting algorithm match_compare_key() registered match_compare_key() rule. Please note that this hack only works with routes that were registered directly with app.route() (Flask object), and not with drawings. Blueprints routes are added to the global URL map only when the drawing is registered in the main application, which makes it difficult to modify the created rules.

 # an ordinary route @app.route('/<var1>/<var2>/<var3>') def some_view(var1, var2, var3): pass # let find the rule that was just generated rule = app.url_map._rules[-1] # we create some comparison keys: # increase probability that the rule will be near or at the top top_compare_key = False, -100, [(-2, 0)] # increase probability that the rule will be near or at the bottom bottom_compare_key = True, 100, [(2, 0)] # rig rule.match_compare_key() to return the spoofed compare_key rule.match_compare_key = lambda: top_compare_key 

Note that in this case, the resulting forged function is not tied to the rule object. Therefore, when calling rule.match_compare_key() function does not receive the self argument. If you want to bind the function correctly, do this instead:

 spoof = lambda self: top_compare_key rule.match_compare_key = spoof.__get__(rule, type(rule)) 

We can summarize the above with a decorator

 def weighted_route(*args, **kwargs): def decorator(view_func): compare_key = kwargs.pop('compare_key', None) # register view_func with route app.route(*args, **kwargs)(view_func) if compare_key is not None: rule = app.url_map._rules[-1] rule.match_compare_key = lambda: compare_key return view_func return decorator # can be used like @app.route(). To weight the rule, just provide # the `compare_key` param. @weighted_route('/<var1>/<var2>/<var3>', compare_key=bottom_compare_key) def some_view(var1, var2, var3): pass 

The same hack implemented as a context manager.

 import contextlib @contextlib.contextmanager def weighted_route(compare_key=None): yield if compare_key is not None: rule = app.url_map._rules[-1] rule.match_compare_key = lambda: compare_key # and to use with weighted_route(compare_key): @app.route('/<var1>/<var2>/<var3>') def some_view(var1, var2, var3): pass 
+3
source

All Articles