Conflictos de enrutamiento de URL para archivos estáticos en el servidor de dev de Flask

Quiero definir una regla de url con tres componentes variables, como:

@app.route('////') 

Pero encuentro que el servidor de desarrollo evalúa tales reglas antes de intentar hacer coincidir los archivos estáticos. Así que algo como:

 /static/images/img.jpg 

será capturado por mi regla de url, en lugar de reenviarse al controlador de archivos estáticos integrado. ¿Hay alguna forma de forzar al servidor de desarrollo a que coincida primero con los archivos estáticos?

PS Esto es solo un problema si la regla tiene más de dos componentes variables.

Esta es la característica de optimización de ruta werkzeug. Ver Map.add , Map.update y 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 

Hay self.arguments : argumentos actuales, self._weights , profundidad del camino.

Para '////' tenemos (True, -3, [(1, 100), (1, 100), (1, 100)]) . Hay (1, 100) – argumento de cadena predeterminado con longitud máxima de 100.

Para '/static/' tenemos (True, -2, [(0, -6), (1, 200)]) . Hay (0, 1) – longitud static cadena sin argumentos de la ruta de acceso, (1, 200) – longitud máxima de la cadena de la ruta del argumento de la longitud máxima 200

Por lo tanto, no encuentro una manera hermosa de establecer una implementación de Map propia para Flask.url_map o establecer una prioridad para la regla de mapa. Soluciones:

  1. Configure la aplicación del Flask como app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static') .
  2. Cambie @app.route('////') a @app.route('/prefix////') .
  3. Agregue su propio convertidor y utilícelo como @app.route('////') .
  4. Importe werkzeug.routing , cree su propia implementación del mapa, cambie werkzeug.routing.Map para poseer la implementación, importe el flask .
  5. Utilice el servidor como en la producción.

Entonces, como tbicr señaló tbicr , este comportamiento se establece profundamente en Werkzeug, y no hay realmente una forma elegante de manejarlo desde Flask. La mejor solución que se me ocurre es:

Defina un manejador de archivos estáticos complementarios como:

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

Aquí, app.config['STATIC_FOLDER'] es la ruta completa a la carpeta estática en la máquina que ejecuta la aplicación.

Ahora, este controlador capta cosas como /static/images/img.jpg , dejando mi vista solo con los tres componentes variables.

Una forma de evitar esto es engañar al algoritmo de clasificación de reglas falsificando el método match_compare_key() la regla registrada. Tenga en cuenta que este truco solo funciona con rutas que se han registrado directamente con app.route() (el objeto Flask), no con Blueprints. Las rutas de Blueprints se agregan al mapa de la URL global solo al registrarse en la aplicación principal, lo que dificulta la modificación de las reglas generadas.

 # an ordinary route @app.route('///') def some_view(var1, var2, var3): pass # let's 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 

Tenga en cuenta que, en este caso, la función suplantada resultante no está vinculada al objeto de la regla. Por lo tanto, al llamar a rule.match_compare_key() , la función no recibe un argumento self . Si desea vincular la función correctamente, haga esto en su lugar:

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

Podemos generalizar lo anterior con un decorador.

 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('///', compare_key=bottom_compare_key) def some_view(var1, var2, var3): pass 

El mismo hack implementado como gestor de contexto.

 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('///') def some_view(var1, var2, var3): pass