导航
导航

Flask 蓝图的一个 BUG

项目有个需求需要将部分蓝图加上认证权限,所以我在部分蓝图使用了before_request方法。例子简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from flask import Flask, Blueprint, request, abort
from flask.views import MethodView


app = Flask(__name__)
system = Blueprint('system', __name__, url_prefix='/system')


@system.before_request
def auth():
if not request.headers.get('token', None):
abort(401)


class UserView(MethodView):
def get(self):
return 'user list'


class UserProfileView(MethodView):
def get(self, id):
return 'hello {}'.format(id)


system.add_url_rule('/user', view_func=UserView.as_view('user'))
system.add_url_rule('/user/<int:id>/profile', view_func=UserProfileView.as_view('user.profile'))

app.register_blueprint(system)

app.run(debug=True)

当我运行测试/system/user这个路由时,auth方法是生效的。但是测试/system/user/1/profile时,居然没有调用auth方法。同一个蓝图下的两个视图函数一个有认证,一个没认证,这真的太奇怪了。

然后在 shell 里面测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [1]: from test import app

In [2]: app
Out[2]: <Flask 'test'>

In [3]: app.blueprints
Out[3]: {'system': <flask.blueprints.Blueprint at 0x1020bc850>}

In [4]: from flask import request

In [5]: with app.test_request_context('/system/user'):
...: print request.blueprint
...:
system

In [6]: with app.test_request_context('/system/user/1/profile'):
...: print request.blueprint
...:
system.user

当访问/system/user/1/profile时,请求的蓝图居然变成了system.user.

于是我查看了一下源码 flask/wrapper.py

1
2
3
4
5
@property
def blueprint(self):
"""The name of the current blueprint"""
if self.url_rule and '.' in self.url_rule.endpoint:
return self.url_rule.endpoint.rsplit('.', 1)[0]

蓝图的命名是根据当前路由的endpoint取的,如果注册路由的时候没有显示命名endpoint, 那么视图函数的函数名将会默认为endpoint.

这里我用的是 Flask 中的 View 来编写视图,在注册路由时, 我把UserProfileView命名为了user.profile, 所以蓝图的名字最终变成了system.user.

其实 Flask 有考虑过这个问题,flask/blueprints.py

1
2
3
4
5
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
if endpoint:
assert '.' not in endpoint, "Blueprint endpoint's should not contain dot's"
self.record(lambda s:
s.add_url_rule(rule, endpoint, view_func, **options))

当在注册路由的时候,endpoint是不允许有.的,否侧就会报错。一般编写视图的时候都使用函数来编写。类似于下面这样

1
2
3
@app.route('/')
def test():
return 'hello world'

所以函数名是不存在有.这种情况的,但是如果使用 View 的话,UserProfileView.as_view('user.profile')这里面传的字符串就是对这个类的命名,所以导致了上述的问题。

其实,这个问题也是由于我自己命名没有规范导致的,完全可以在命名时不用., 但是想想有可能还会有哪个二逼像我一样命名时加.,所以我就向 Flask 提了一个 PR. 没想到居然接受了!

恩, 我也是给世界级开源项目贡献过代码的人了😐