Flask中处理依赖的技巧

Flask应用中通常会用工厂模式 来创建应用对象,这样方便配置和测试。

应用代码

# app/__init__.py
from flask import Flask
from flask_xxxext import Xxx
from flask_yyyext import Yyy
# ... 一些flask拓展
xx = Xxx()
yy = Yyy()

def create_app(config=None):
    app = Flask(__name__)

    xx.init_app(app)
    yy.init_app(app)

启动脚本

# manage.py
from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run()

测试代码

# test_app.py
from app import create_app

def test_xxx():
    app = create_app()
    # ... tests

在稍大一些的项目里,不可避免会用到许多flask插件,也会用到一些其他的库, 这些库通常没有特意去支持flask的应用工厂模式。

应用代码不仅依赖于这些外部的库,也会产生一些相互依赖。比如A模块依赖B模块,B模块又依赖A模块, 这样在导入模块的时候就会遇到循环导入的问题,有时还会产生A依赖B,B依赖C…N依赖A这样复杂的依赖关系。

一方面就是应用架构的问题,要解决这类依赖问题,首先是要让整体架构清晰,各个模块直接形成清晰的 职责边界,不要在同一个模块做职责不同的事。 比如A模块依赖B模块,B模块又依赖A模块,这种情况就是A模块或B模块做了职责之外的事,把它们 职责之外的事拆分出来,放到C模块中,问题就解决了。

另一方面就是使用全局对象带来的依赖问题,flask中通常会定义一些全局对象,在要用到的地方 直接导入需要的对象,这样使用的时候很方便。flask的插件基本都支持这样用,因为这些插件都没有 显式的依赖,而是提供一个init_app方法,用于在运行时初始化插件。

但是还会用到一些其他的库,这些库没有提供init_app方法。 有两种办法,第一种就是写一个新的类,把原来的库包装一下,提供一个init_app方法用来延迟初始化。 这种方法有点繁琐,也不灵活,对每一个依赖都要写一个类。

另一种方法就是用 werkzeug.local.LocalProxy 实现延迟初始化。

所有被依赖的全局对象

# app/dependency.py
from werkzeug.local import LocalProxy


class Dependency:
    """Dependency"""

d = Dependency()
xx = LocalProxy(lambda: d.xx)
yy = LocalProxy(lambda: d.yy)

__all__ = ['d', 'xx', 'yy']

应用代码

# app/__init__.py
from xxx import Xxx
from yyy import Yyy
from dependency import d

def create_app(config=None):
    app = create_app()
    d.xx = Xxx(...)
    d.yy = Yyy(...)
# app/some_module.py
from .dependency import xx

def view():
    # 业务代码
    xx.xxxx()

使用这种方式方便又灵活,唯一的限制是被依赖的全局对象只能在应用初始化之后使用,不过这些业务代码都是 在接受到请求才会执行,这时候应用早就初始化了,所以这点限制也没什么影响。

Back

留言 - Github Issues | 主题 - The Plain