在Flask中建立持久复杂对象的正确方法
我想了解一下在Flask中处理持久对象的“好做法”。
我自己有一些类,用来管理用户、用户组、用户在组里的成员身份以及用户/组的权限。其中有一个叫做Passport的类,它保存了当前用户的信息和他们的权限。
我的想法是,每个用户的会话应该和自己的Passport对象关联,这个对象在不同的视图中都能保持不变。这样,用户登录时可以初始化某些权限,之后在使用视图和进行AJAX请求时可以随时检查这些权限。
目前,我在Passport类中有序列化和反序列化的方法,还有一个FlaskPassport类,它在views.py的全局范围内初始化。这个类有一个只读的“passport”属性,它从会话变量中读取序列化的护照数据,并返回反序列化的对象。它还有一个save()方法,做的正好相反。这个FlaskPassport类还提供了一个装饰器方法,用于检查视图中的权限。不过,访问存储在会话中的序列化护照数据的代码看起来有点笨重。而且,护照对象在修改后必须手动保存,这让我觉得不太对——应该是Flask在处理完请求后自动把修改过的护照对象保存到会话中。
所以,我在寻找一种聪明的模式,能够提供一个全局的护照对象,所有视图都能访问,并且让我可以给需要权限检查的视图添加装饰器。
1 个回答
有几种方法可以做到这一点,包括:
把
passport
实例存储在g
里,然后使用before_request
和after_request
这对处理器来从会话中读取和写入这个实例:@app.before_request def load_passport(): if "passport_id" in session: g.passport = create_passport_from_id(session["passport_id"]) @app.after_request def serialize_passport(response): if hasattr(g, "passport"): session["passport_id"] = g.passport.id return response
使用Flask为
request
和g
等提供的线程局部模式。底层使用的是Werkzeug的LocalProxy
,这个代理会根据对象的生命周期挂载在应用上下文或请求上下文上:from flask import (_request_ctx_stack as request_ctx, has_request_context, session) from werkzeug.local import LocalProxy current_passport = LocalProxy(get_passport) def get_passport(): if has_request_context() and "passport_id" in session: if not hasattr(request_ct.top, "passport"): passport_id = session["passport_id"] request_ctx.top.passport = construct_passport_from_id(passport_id) return getattr(request_ctx.top, "passport", None) return EmptyPassport() @app.after_request def serialize(response): if current_passport.is_not_empty(): session["passport_id"] = current_passport.id return response
值得注意的是,我选择不把整个护照信息序列化到会话中,因为这些信息在每次请求时都会来回传递(这取决于你在护照中存储了多少信息,这可能会影响到你)。
另外要提到的是,这两种方法本身并不安全。Flask确实会对会话cookie进行签名,以防止被篡改,但你仍然需要关注登出、新鲜度等问题。可以看看Flask-Login的代码,了解一些你需要考虑的其他事项。