我想定义一个包含三个变量组件的url规则,例如:
@app.route('/<var_1>/<var_2>/<var3>/')
但是我发现开发服务器在尝试匹配静态文件之前会评估这些规则。所以像:
/static/images/img.jpg
将被我的url规则捕获,而不是被转发到内置的静态文件处理程序。有没有办法强制开发服务器首先匹配静态文件?
P.S。如果规则具有两个以上的变量组件,则这只是一个问题。
答案 0 :(得分:17)
这是werkzeug路由优化功能。请参阅Map.add
,Map.update
和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
有self.arguments
- 当前参数,self._weights
- 路径深度。
对于'/<var_1>/<var_2>/<var3>/'
,我们有(True, -3, [(1, 100), (1, 100), (1, 100)])
。有(1, 100)
- 默认字符串参数,最大长度为100。
对于'/static/<path:filename>'
,我们有(True, -2, [(0, -6), (1, 200)])
。有(0, 1)
- 路径非参数字符串长度static
,(1, 200)
- 路径字符串参数最大长度为200。
所以我找不到任何漂亮的方法来为Map
设置自己的Flask.url_map
实现或为地图规则设置优先级。解决方案:
Flask
应用设置为app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static')
。@app.route('/<var_1>/<var_2>/<var3>/')
更改为@app.route('/prefix/<var_1>/<var_2>/<var3>/')
。@app.route('/<no_static:var_1>/<var_2>/<var3>/')
。werkzeug.routing
,创建自己的地图实施,将werkzeug.routing.Map
更改为自己的实施,导入flask
。答案 1 :(得分:6)
因此,正如tbicr
所指出的,这种行为是在Werkzeug中深入设置的,并且从Flask处理它并不是一种优雅的方式。我能提出的最好的解决方法是:
定义一个互补的静态文件处理程序,如:
@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)
此处,app.config['STATIC_FOLDER']
是运行应用程序的计算机上静态文件夹的完整路径。
现在,这个处理程序捕获/static/images/img.jpg
之类的东西,只留下三个变量组件的视图。
答案 2 :(得分:3)
解决此问题的一种方法是通过欺骗已注册规则的match_compare_key()
方法来欺骗规则排序算法。请注意,此hack仅适用于直接使用app.route()
(Flask对象)注册的路由,而不适用于Blueprints。只有在蓝图在主应用程序上注册时,蓝图的路线才会添加到全局网址地图中,因此修改生成的规则很有挑战性。
# an ordinary route
@app.route('/<var1>/<var2>/<var3>')
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
请注意,在这种情况下,生成的欺骗函数不会绑定到规则对象。因此,在调用rule.match_compare_key()
时,该函数不会收到self
参数。如果要正确绑定函数,请改为:
spoof = lambda self: top_compare_key
rule.match_compare_key = spoof.__get__(rule, type(rule))
我们可以用装饰器
来概括上述内容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
同一个hack实现为上下文管理器。
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