在Flask中建立持久复杂对象的正确方法

3 投票
1 回答
2191 浏览
提问于 2025-04-18 08:39

我想了解一下在Flask中处理持久对象的“好做法”。

我自己有一些类,用来管理用户、用户组、用户在组里的成员身份以及用户/组的权限。其中有一个叫做Passport的类,它保存了当前用户的信息和他们的权限。

我的想法是,每个用户的会话应该和自己的Passport对象关联,这个对象在不同的视图中都能保持不变。这样,用户登录时可以初始化某些权限,之后在使用视图和进行AJAX请求时可以随时检查这些权限。

目前,我在Passport类中有序列化和反序列化的方法,还有一个FlaskPassport类,它在views.py的全局范围内初始化。这个类有一个只读的“passport”属性,它从会话变量中读取序列化的护照数据,并返回反序列化的对象。它还有一个save()方法,做的正好相反。这个FlaskPassport类还提供了一个装饰器方法,用于检查视图中的权限。不过,访问存储在会话中的序列化护照数据的代码看起来有点笨重。而且,护照对象在修改后必须手动保存,这让我觉得不太对——应该是Flask在处理完请求后自动把修改过的护照对象保存到会话中。

所以,我在寻找一种聪明的模式,能够提供一个全局的护照对象,所有视图都能访问,并且让我可以给需要权限检查的视图添加装饰器。

1 个回答

3

有几种方法可以做到这一点,包括:

  1. passport实例存储在g里,然后使用before_requestafter_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
    
  2. 使用Flask为requestg等提供的线程局部模式。底层使用的是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的代码,了解一些你需要考虑的其他事项。

撰写回答