app/__init__.py
中被创建为一个全局变量,然后又被很多应用模块导入。 虽然这本身并不是问题,但将应用实例作为全局变量可能会使某些情况复杂化,特别是与测试相关的情景。 想象一下你想要在不同的配置下测试这个应用。 由于应用被定义为全局变量,实际上没有办法使用不同配置变量来实例化的两个应用实例。 另一种糟心的情况是,所有测试都使用相同的应用,因此测试可能会对应用进行更改,就会影响稍后运行的其他测试。 理想情况下,你希望所有测试都在原始应用实例上运行的。render_template()
调用以使用新的errors模板子目录。 之后,我将blueprint创建添加到app/errors/__init__.py
模块,并在创建应用实例之后,将blueprint注册到app/__init__.py
。Blueprint()
构造函数添加template_folder='templates'
参数,则可以将错误blueprint的模板存储在app/errors/templates目录中。___init__.py
模块中完成的:app/errors/__init__.py
:错误blueprint。Blueprint
类获取blueprint的名称,基础模块的名称(通常在Flask应用实例中设置为__name__
)以及一些可选参数(在这种情况下我不需要这些参数)。 Blueprint对象创建后,我导入了handlers.py模块,以便其中的错误处理程序在blueprint中注册。 该导入位于底部以避免循环依赖。@app.errorhandler
装饰器将错误处理程序附加到应用程序,而是使用blueprint的@bp.app_errorhandler
装饰器。 尽管两个装饰器最终都达到了相同的结果,但这样做的目的是试图使blueprint独立于应用,使其更具可移植性。我还需要修改两个错误模板的路径,因为它们被移动到了新errors子目录。app/__init__.py
:向应用注册错误blueprint。register_blueprint()
方法。 在注册blueprint时,任何视图函数,模板,静态文件,错误处理程序等均连接到应用。 我将blueprint的导入放在app.register_blueprint()
的上方,以避免循环依赖。@bp.route
装饰器来代替@app.route
装饰器。 在url_for()
中用于构建URL的语法也需要进行更改。 对于直接附加到应用的常规视图函数,url_for()
的第一个参数是视图函数名称。 但当在blueprint中定义路由时,该参数必须包含blueprint名称和视图函数名称,并以句点分隔。 因此,我不得不用诸如url_for('auth.login')
的代码替换所有出现的url_for('login')
代码,对于其余的视图函数也是如此。auth
blueprint到应用时,我使用了些许不同的格式:app/__init__.py
:注册用户认证blueprint到应用。register_blueprint()
调用接收了一个额外的参数,url_prefix
。 这完全是可选的,Flask提供了给blueprint的路由添加URL前缀的选项,因此blueprint中定义的任何路由都会在其完整URL中获取此前缀。 在许多情况下,这可以用来当成“命名空间”,它可以将blueprint中的所有路由与应用或其他blueprint中的其他路由分开。 对于用户认证,我认为让所有路由以 /auth 开头很不错,所以我添加了该前缀。 所以现在登录URL将会是 http://localhost:5000/auth/login 。 因为我使用url_for()
来生成URL,所有URL都会自动合并前缀。main
,因此所有引用视图函数的url_for()
调用都必须添加一个main.
前缀。 鉴于这是应用的核心功能,我决定将模板留在原来的位置。 这不会有什么问题,因为我已将其他两个blueprint中的模板移动到子目录中了。app
的装饰器来修饰,比如@app.route
。 但是现在所有的路由和错误处理程序都被转移到了blueprint中,因此保持应用全局性的理由就不够充分了。create_app()
的函数来构造一个Flask应用实例,并消除全局变量。 转换并非容易,我不得不理清一些复杂的东西,但我们先来看看应用工厂函数:app/__init__.py
:应用工厂函数。init_app()
方法,以将其绑定到现在已知的应用。not app.testing
子句,用于决定是否启用电子邮件和文件日志,以便在单元测试期间跳过所有这些日志记录。 由于在配置中TESTING
变量在单元测试时会被设置为True
,因此app.testing
标志在运行单元测试时将变为True
。app
的引用都是随着blueprint的引入而消失的,但是我仍然需要解决代码中的一些问题。 例如,app/models.py、app/translate.py和app/main/routes.py模块都引用了app.config
。 幸运的是,Flask开发人员试图使视图函数很容易地访问应用实例,而不必像我一直在做的那样导入它。 Flask提供的current_app
变量是一个特殊的“上下文”变量,Flask在分派请求之前使用应用初始化该变量。 你之前已经看到另一个上下文变量,即存储当前语言环境的g
变量。 这两个变量,以及Flask-Login的current_user
和其他一些你还没有看到的东西,是“魔法”变量,因为它们像全局变量一样工作,但只能在处理请求期间且在处理它的线程中访问。current_app
变量替换app
就不需要将应用实例作为全局变量导入。 通过简单的搜索和替换,我可以毫无困难地用current_app.config
替换对app.config
的所有引用。send_email()
函数中,应用实例作为参数传递给后台线程,后台线程将发送电子邮件而不阻塞主应用程序。在作为后台线程运行的send_async_email()
函数中直接使用current_app
将不会奏效,因为current_app
是一个与处理客户端请求的线程绑定的上下文感知变量。在另一个线程中,current_app
没有赋值。直接将current_app
作为参数传递给线程对象也不会有效,因为current_app
实际上是一个代理对象,它被动态地映射到应用实例。因此,传递代理对象与直接在线程中使用current_app
相同。我需要做的是访问存储在代理对象中的实际应用程序实例,并将其作为app
参数传递。 current_app._get_current_object()
表达式从代理对象中提取实际的应用实例,所以它就是我作为参数传递给线程的。current_app
变量不起作用,因为这些命令是在启动时注册的,而不是在处理请求期间(这是唯一可以使用current_app
的时间段)注册的。 为了在这个模块中删除对app
的引用,我使用了另一个技巧,将这些自定义命令移动到一个将app
实例作为参数的register()
函数中:register()
函数。 以下是完成重构后的microblog.py:create_app()
函数现在接受一个配置类作为参数。 默认情况下,使用在config.py中定义的Config
类,但现在我可以通过将新类传递给工厂函数来创建使用不同配置的应用实例。 下面是一个适用于我的单元测试的示例配置类:Config
类的子类,并覆盖SQLAlchemy配置以使用内存SQLite数据库。 我还添加了一个TESTING
属性,并设置为True
,我目前不需要该属性,但如果应用需要确定它是否在单元测试下运行,它就派上用场了。setUp()
和tearDown()
方法,它们由单元测试框架自动调用,以创建和销毁每次测试运行的环境。 我现在可以使用这两种方法为每个测试创建和销毁一个测试专用的应用:self.app
中,但光是创建一个应用不足以使所有的工作都成功。 思考创建数据库表的db.create_all()
语句。 db
实例需要注册到应用实例,因为它需要从app.config
获取数据库URI,但是当你使用应用工厂时,应用就不止一个了。 那么db
如何关联到我刚刚创建的self.app
实例呢?current_app
变量吗?当不存在全局应用实例导入时,该变量以代理的形式来引用应用实例。 这个变量在当前线程中查找活跃的应用上下文,如果找到了,它会从中获取应用实例。 如果没有上下文,那么就没有办法知道哪个应用实例处于活跃状态,所以current_app
就会引发一个异常。 下面你可以看到它是如何在Python控制台中工作的。 这需要通过运行python
启动,因为flask shell
命令会自动激活应用程序上下文以方便使用。current_app
和g
生效。 当请求完成时,上下文将与这些变量一起被删除。 为了使db.create_all()
调用在单元测试setUp()
方法中工作,我为刚刚创建的应用程序实例推送了一个应用上下文,这样db.create_all()
可以使用 current_app.config
知道数据库在哪里。 然后在tearDown()
方法中,我弹出上下文以将所有内容重置为干净状态。request
、session
以及Flask-Login的current_user
变量才会变成可用状态。译者注:可以通过将环境变量设置到开机启动中,来保持它们在该计算机中的所有终端中都生效。
python-dotenv
。 所以让我们安装这个包:FLASK_APP
和FLASK_DEBUG
环境变量,因为它们在应用启动的早期(应用实例和配置对象存在之前)就被使用了。pip freeze
命令将安装在虚拟环境中的所有软件包以正确的格式输入到requirements.txt文件中。 现在,如果你需要在另一台计算机上创建相同的虚拟环境,无需逐个安装软件包,可以直接运行一条命令实现: