65.9K
CodeProject 正在变化。 阅读更多。
Home

Flask 上的 Web 应用:如何处理循环导入

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020 年 4 月 23 日

GPL3

3分钟阅读

viewsIcon

16704

关于 Python 和 Flask 的一些内容

Flask和循环导入

在使用Flask时,开发人员经常面临模块之间依赖关系的问题。为了创建视图和模型,开发人员使用在主模块(在“前端控制器”中)中创建和初始化的全局对象。 同时,存在发生循环导入的风险,并且难以维护项目。

Flask 文档和基本教程建议在__init__.py 中编写项目初始化代码以解决问题。 此代码创建Flask类的实例并配置应用程序。 它允许从包的可见区域访问所有全局对象。
使用这种方法,结构如下所示

.
├── app
│   ├── __init__.py
│   ├── forms.py
│   ├── models.py
│   ├── views.py
│   └── templates
├── config.py
└── migrations

app/__init__.py

import flask
from flask_mail import Mail
# other extensions

app = Flask(__name__)
mail = Mail(app)
# configure flask app

from app import views, models

app/views.py

from app import app

@app.route('/view_name/'):
def view_name():
     pass

显然,这种架构不是很好,因为所有组件都紧密相连。 随后,很难详细说明这样的项目,因为在一个地方更改代码将导致在十几个其他地方进行更改。

通常,我们按如下方式解决问题

  • 我们避免标准路由。
  • 我们更喜欢库的原始版本,没有“包装”。
  • 使用依赖注入。

让我们更深入地关注这一点。

使用Classy

您可以采用classy方法,而不是使用文档中描述的标准路由方法。 使用这种方法,您不必手动编写视图的路由:它将根据您的类和方法的名称自动配置。 这种方法可以改善代码的结构,以及创建没有应用程序对象的视图。 结果,解决了循环导入的问题。

使用flask-classful库时,项目结构的示例

.
├── app
│   ├── static
│   ├── templates
│   ├── forms.py
│   ├── routes.py
│   ├── views.py
│   └── tasks.py
├── models
├── app.py
├── config.py
└── handlers.py

app.py

import flask
from flask_mail import Mail
# other extensions

from app import routes as app_route

app = Flask(__name__)
mail = Mail(app)
# configure flask app

app.register_blueprint(app_route.app_blueprint)

app/routes.py

from flask import Blueprint
from app import views

app_blueprint = Blueprint(...)
views.AccountView.register(app_blueprint)
# register other views

app/views.py

from flask_classy import FlaskView, route
from flask_login import login_required

class AccountView(FlaskView):

     def login(self):
          pass

     # other views

     @login_required
     def logout(self):
          pass

在检查代码时,您应该注意初始化现在发生在位于根目录的app.py中。 应用程序分为子项目,这些子项目由蓝图配置,然后仅用一行代码注册到应用程序对象中。

原始库更受欢迎

上面提供的代码显示了flask-classful如何帮助应对循环导入。 经典Flask项目中出现此问题的原因是视图声明和某些扩展。 最好的例子之一是flask-sqlalchemy
flask-sqlalchemy扩展旨在改善sqlalchemyflask之间的集成,但实际上,它带来的问题多于好处

  • 该扩展促进了使用全局对象来处理数据库,包括模型的创建,这再次导致循环导入的问题。
  • 需要使用您自己的类来描述模型,这导致模型与Flask项目的紧密绑定。 结果,这些模型不能在子项目或支持脚本中使用。

由于这些原因,我们尽量不使用flask-sqlalchemy

使用依赖注入模式

实现classy方法和拒绝flask-sqlalchemy只是解决循环导入问题的第一步。 接下来,您需要实现逻辑以获取应用程序中全局对象的访问权限。 为此,最好使用在dependency-injector library中实现的依赖注入模式。

在带有dependency-injector库的代码中使用模式的示例

app.py

import dependency_injector.containers as di_cnt
import dependency_injector.providers as di_prv
from flask import Flask
from flask_mail import Mail

from app import views as app_views
from app import routes as app_routes

app = Flask(__name__)
mail = Mail(app)

# blueprints registration
app.register_blueprint(app_routes.app_blueprint)

# providers creation
class DIServices(di_cnt.DeclarativeContainer):
    mail = di_prv.Object(mail)

# injection
app_views.DIServices.override(DIServices)

app/routes.py

from os.path import join

from flask import Blueprint

import config
from app import views

conf = config.get_config()

app_blueprint = Blueprint(
    'app', __name__, template_folder=join(conf.BASE_DIR, 'app/templates'),
    static_url_path='/static/app', static_folder='static'
)

views.AccountView.register(app_blueprint, route_base='/')

app/views.py

import dependency_injector.containers as di_cnt
import dependency_injector.providers as di_prv
from flask_classy import FlaskView
from flask_login import login_required

class DIServices(di_cnt.DeclarativeContainer):
    mail = di_prv.Provider()

class AccountView(FlaskView):

    def registration(self):
        # registration implementation
        msg = 'text'
        DIServices.mail().send(msg)

    def login(self):
        pass

    @login_required
    def logout(self):
        pass

本文中提到的措施可以消除循环导入,并提高代码质量。 我们建议通过以web app形式设计的“公牛母牛”游戏的示例,使用上述方法查看Flask项目。

结论

我们已经考虑了克服与循环导入相关的Flask应用程序的常见架构问题的技巧。 使用它们,您可以简化应用程序的维护和重构。

感谢您的关注! 我们希望本文对您有所帮助。

历史

  • 2020 年 4 月 23 日:初始版本
© . All rights reserved.