Flaskz介绍7:运行上下文管理

在 Web 请求、后台任务和独立脚本中,经常需要在一次运行过程中共享数据,例如当前用户、请求 ID、数据库 Session 或临时缓存。

Flaskz Context 基于 Python ContextVar 实现,提供框架无关的运行上下文管理机制,可用于 Flask、FastAPI、Celery 和普通 Python 脚本。

主要功能包括:

  • 数据共享与隔离:在同一运行上下文中共享数据,并隔离不同运行流程。
  • 上下文生命周期管理:支持创建、访问和结束 Context。
  • 嵌套与命名 Context:支持上下文栈,并通过 context_key 访问指定 Context。
  • 数据与控制选项分离:分别管理业务数据(Values)和控制选项(Options)。
  • 资源自动清理:Context 结束时自动运行注册的清理函数。
  • 异步支持:基于 ContextVar,支持 asyncio 运行环境。

Context 生命周期

Context 通过 begin_context() 创建,通过 end_context() 结束。

                
                    context_key = begin_context()  # 创建 Context

                    try:
                        print(has_context())       # True
                        print(get_context_key())   # 当前 Context Key

                        # Business logic

                    finally:
                        end_context(context_key=context_key)  # 结束 Context
                
            

begin_context() 支持传入初始数据和指定 context_key:

                
                    context_key = begin_context( {"user_id": 1001}, context_key="request-001")
                
            

如果没有指定 context_key,会自动生成唯一标识。如果指定的 Key 已存在,则抛出 ValueError。

在 Flask、FastAPI、Celery 等应用中,通常通过框架的请求或任务生命周期钩子统一调用 begin_context() 和 end_context(),业务代码只需要访问已有的 Context。

Context 数据管理

Context 提供了一组 API,用于在同一个运行上下文中共享数据。

                
                    # 设置单个值
                    set_context_value("user_id", 1001)

                    # 批量设置
                    set_context_values({
                        "username": "admin",
                        "role": "administrator"
                    })

                    # 获取值
                    user_id = get_context_value("user_id")

                    # 获取值,支持默认值
                    role = get_context_value("role", "guest")

                    # 判断值是否存在
                    has_context_value("user_id")

                    # 获取全部值(浅拷贝)
                    values = get_context_values()

                    # 删除指定值
                    remove_context_value("user_id")

                    # 清空全部业务数据
                    clear_context_values()
                
            

这些操作默认针对当前 Context,不会自动创建新的 Context。

典型场景是跨函数共享请求数据:

                
                    def authenticate(user):
                        set_context_value("current_user", user)

                    def process_order():
                        user = get_context_value("current_user")
                        # Process order
                
            

这样就不需要在多个业务函数之间反复传递运行上下文数据。

Context 控制选项

除了业务数据,Context 还提供独立的 Options 存储空间,用于保存运行过程中的控制参数。

                
                    # 保存业务数据
                    set_context_value("user_id", 1001)

                    # 设置控制选项
                    set_context_option("cache_db_session", True)

                    # 获取控制选项
                    enabled = get_context_option("cache_db_session", False)
                
            

Values 和 Options 相互独立,即使使用相同的 Key,也不会发生冲突。

调用 clear_context_values() 只会清空业务数据,不影响控制选项和已注册的资源清理函数。

嵌套与命名 Context

通过上下文栈管理 Context,允许在一个运行流程中创建多个独立的上下文。

                
                    outer_key = begin_context({"name": "outer"})

                    inner_key = begin_context({"name": "inner"})

                    # 默认访问当前 Context
                    print(get_context_value("name"))  # inner

                    # 通过 context_key 访问外层 Context
                    print(get_context_value("name", context_key=outer_key))  # outer

                    end_context(context_key=inner_key)

                    print(get_context_value("name"))  # outer

                    end_context(context_key=outer_key)
                
            

默认情况下,操作的是栈顶 Context;通过 context_key,可以访问和修改栈中的其他 Context。

这种方式适合在同一个请求或任务中管理不同作用域的数据。

Context 资源清理

Context 支持通过 register_context_cleanup() 注册资源清理函数。

当 Context 结束时,系统自动运行已注册的清理函数,运行顺序与注册顺序相反。

                
                    context_key = begin_context()

                    try:
                        session = Session(engine)

                        # 注册资源清理
                        register_context_cleanup(session.close, context_key=context_key)

                        # 保存 Session
                        set_context_value("db_session", session)

                        # Business logic

                    finally:
                        # 自动运行 session.close()
                        end_context(context_key=context_key)
                
            

这种方式适合管理数据库 Session、文件对象、网络连接等需要主动释放的资源。

Context 负责资源清理,但不会自动处理数据库事务提交或回滚。当前清理回调采用同步 ExitStack 实现。

context_scope() 简化生命周期管理

对于独立脚本或局部运行代码,可以通过 context_scope() 自动创建和关闭 Context。

                
                    with context_scope({"task_id": "task-001"}) as context_key:
                        set_context_value("status", "running")

                        # Business logic

                    # 自动结束 Context
                
            

context_scope() 会在代码块退出时自动调用 end_context(),包括发生异常的情况。

异步运行支持

flaskz Context 基于 ContextVar,可以在 asyncio 环境中使用。

                
                    import asyncio

                    async def worker(task_id):
                        context_key = begin_context({"task_id": task_id})
                        try:
                            await asyncio.sleep(0.1)
                            print(get_context_value("task_id"))
                        finally:
                            end_context(context_key=context_key)

                    async def main():
                        await asyncio.gather(
                            worker("task-1"),
                            worker("task-2")
                        )

                    asyncio.run(main())
                
            

异步子任务会继承创建时的 ContextVar 状态。由于 Context 中包含可变字典,继承的 Context 数据可能被多个任务共享。

因此,需要独立数据空间时,应为子任务创建新的 Context。

典型使用场景

场景 Context 的作用
Flask / FastAPI 请求 共享请求数据、当前用户、数据库 Session
Celery 后台任务 管理任务数据和资源生命周期
定时任务 / 独立脚本 管理单次任务的临时数据与资源
异步任务 提供协程级上下文管理与数据隔离
复杂业务流程 通过嵌套 Context 管理不同作用域的数据

总结

flaskz Context 将运行上下文的生命周期管理、数据共享、控制选项和资源清理整合为一套统一的 API。

在实际应用中,通常由框架或任务调度器负责 Context 的创建与结束,业务代码使用 get_context_value()、set_context_value() 等 API 访问上下文数据。

通过这种方式,业务逻辑可以独立于具体的 Web 或任务框架,同时保持明确的数据作用域和资源生命周期。