网站建设免费维护内容,营销网站开发方案,搭建网页教程,网站系统建设申请报告学习Flask之七、大型应用架构
尽管存放在单一脚本的小型网络应用很方便#xff0c;但是这种应用不能很好的放大。随着应用变得复杂#xff0c;维护一个大的源文件会出现问题。不像别的网络应用#xff0c;Flask没有强制的大型项目组织结构。构建应用的方法完全留给开发者。…学习Flask之七、大型应用架构
尽管存放在单一脚本的小型网络应用很方便但是这种应用不能很好的放大。随着应用变得复杂维护一个大的源文件会出现问题。不像别的网络应用Flask没有强制的大型项目组织结构。构建应用的方法完全留给开发者。本章呈现一种组织大型应用到包或模块的方法。
这种结构用于维护本书余下的例子。
项目结构
Example 7-1 展示Flask应用的基础布局
Example 7-1. 基础的多文件Flask应用架构
|-flasky
|-app/
|-templates/
|-static/
|-main/
|-__init__.py
|-errors.py
|-forms.py
|-views.py
|-__init__.py
|-email.py
|-models.py
|-migrations/
|-tests/
|-__init__.py
|-test*.py
|-venv/
|-requirements.txt
|-config.py
|-manage.py
这个结构有4个顶层目录
• Flask应用通常放在名为app的包里。
• migrations目录包含数据库迁移脚本如前所述。
• Unit tests 放在tests包里。
• venv目录包含Python虚拟环境如前所述。
也有一些新的文件
• requirements.txt列出了依赖包以便在不同的计算机产生相同的虚拟环境。
• config.py存贮配置设置。
• manage.py启动应用和其它应用任务。
为了帮助你理解这个结构下一节描述如何将hello.py应用转换到这个结构。
配置选项
应用通常需要多种配置设置。最好的例子是在开发、测试和生产过程中需要不同的数据库以免相互干扰。 不像hello.py里用简单的字典结构配置而是使用一个层级的配置类。
Example 7-2展示了config.py文件。
Example 7-2. config.py: Application configuration
import os
basedir os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY os.environ.get(SECRET_KEY) or hard to guess string
SQLALCHEMY_COMMIT_ON_TEARDOWN True
FLASKY_MAIL_SUBJECT_PREFIX [Flasky]
FLASKY_MAIL_SENDER Flasky Admin flaskyexample.com
FLASKY_ADMIN os.environ.get(FLASKY_ADMIN)
staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG True
MAIL_SERVER smtp.googlemail.com
MAIL_PORT 587
MAIL_USE_TLS True
MAIL_USERNAME os.environ.get(MAIL_USERNAME)
MAIL_PASSWORD os.environ.get(MAIL_PASSWORD)
SQLALCHEMY_DATABASE_URI os.environ.get(DEV_DATABASE_URL) or \
sqlite:/// os.path.join(basedir, data-dev.sqlite)
class TestingConfig(Config):
TESTING True
SQLALCHEMY_DATABASE_URI os.environ.get(TEST_DATABASE_URL) or \
sqlite:/// os.path.join(basedir, data-test.sqlite)
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI os.environ.get(DATABASE_URL) or \
sqlite:/// os.path.join(basedir, data.sqlite)
config {
development: DevelopmentConfig,
testing: TestingConfig,
production: ProductionConfig,
default: DevelopmentConfig
}
Config基类包含所有配置常用的设置不同的子类定义特定的配置设置。 额外的配置可以按需添加。要使配置更灵活和安全有些设置可以可选的从环境变量导入。例如 SECRET_KEY值由于它的敏感性可以设置在环境里但是可以提供黙认值以免环境里没有定义。
SQLALCHEMY_DATABASE_URI三种配置有不同的值。这可以使应用在不同的配置下运行每个使用不同的数据库。 配置类可以定义一个 init_app()类方法它取应用实例作为参数。这里配置特定的初始化可以进行。现在基础配置类实施一个空的init_app()方法。在配置脚本下部不同的配置注册于config字典。一个配置用于开发环境也注册为黙认。
应用包
应用包是所有的应用代码静态文件模板存放的地方。它简单的命名为app虽然它可以给应用特定的名字如果需要。
templates和static 目录是应用包的一部分所以这两个目录放在app里。数据模型和邮件支持函数也放在这个包里每个都有它自已的模块如app/models.py 和app/email.py。
使用应用工厂
在一个文件里创建应用非常方便但是有个大的缺点。因为应用创建在全局范围里没办法动态的应用配置变化脚本运行时应用实例已经创建进行配置变更太迟了。
这对单元测试特别重要因为有时它有必要在不同的配置设置下运行以覆盖更好的测试。这个问题的解决方案是延迟应用的创建通过把它放在工厂 函数里可以从脚本明文的调用。
这不但可以给脚本时间进行配置也可以创建多个应用实例--也时在测试过程中很有用。例Example 7-3显示的工厂函数在app包的构造函数里定义。构造函数导入大部分Flask扩展但是因为没有应用实例初始化他们它的创建没有初始化通过传递空参数到构造函数。create_app()函数是应用工厂取名字作为参数供应用使用。配置设置存贮于一个config.py 定义的类里的配置设置可以直接导入到应用使用from_object()方法这个方法在app.config配置对象里。通过config字典的name选择配置对象。一旦应用被创建和配置扩展就可以被初始化。调用init_app()完成初始化工作。
Example 7-3. app/__init__.py: Application package constructor
from flask import Flask, render_template
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLAlchemy
from config import config
bootstrap Bootstrap()
mail Mail()
moment Moment()
db SQLAlchemy()
def create_app(config_name):
app Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
# attach routes and custom error pages here
return app
工厂函数返回创建的应用实例但是注意用工厂函数创建的应用当前未完全因为它们缺少路由和定制错误处理页。这是下一节的主题。
在Blueprint里实施应用功能
转换到应用工厂会使路由变得复杂。在单一脚本的应用里应用实例存在于全局范围里所以路由可以很易容的定义使用app.route装饰器。但是现在应用在运行时创建app.route只有在create_app()调用之后才有这太迟了。像路由定制错误处理页处理器同样有这个问题因为它们由app.errorhandler装饰器定义。幸运的是Flask 提供了更好的解决方案使用blueprints。blueprint与应用的相似之处是它也可以定义路由。不同之处是与blueprint与关的路由处于支配状态直到blueprint用一个应用注册这个时候路由成为应用的一部分。使用全局范围里定义的blueprint应用的路由可以与单一文件应用的路由的定义方法一样。像应用一样blueprints也可以定义在一个单一的文件或一个包的多个模块里。为了最大的灵活性应用包里的子包将创建以存放blueprint。
Example 7-4展示包的构造函数,它创建blueprint。
Example 7-4. app/main/__init__.py: Blueprint creation
from flask import Blueprint
main Blueprint(main, __name__)
from . import views, errors
Blueprints通过实例化一个Blueprint类对象创建。这个类的构造函数取二个要求的参数blueprint名和blueprint所在的模块或包。
与应用一样, Python的 __name__ 变量是第二个参数的正确值。应用的路由存放在 包的app/main/views.py模块里。 错误处理器放在 app/main/errors.py。导入变些模块使路由和错误处理页与 blueprint关联。重要的是要注意模块在app/__init__.py脚本的底部导入。以免循环依赖因为views.py 和errors.py 需要导入主blueprint。blueprint 在 create_app()应用工厂里用应用注册见Example 7-5.
Example 7-5. app/_init_.py: Blueprint registration
def create_app(config_name):
# ...
from main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
Example 7-6 展示错误处理器。
Example 7-6. app/main/errors.py: Blueprint with error handlers
from flask import render_template
from . import main
main.app_errorhandler(404)
def page_not_found(e):
return render_template(404.html), 404
main.app_errorhandler(500)
def internal_server_error(e):
return render_template(500.html), 500
在blueprint里书写错误处理器的不同之处是如果使用错误处理器装饰函数处理器只会因错误blueprint内的错误而调用。要安装应用范围内的处理器必须使用app_errorhandler。
Example 7-7展示更新于blueprint内的应用路由
Example 7-7. app/main/views.py: Blueprint with application routes
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User
main.route(/, methods[GET, POST])
def index():
form NameForm()
if form.validate_on_submit():
# ...
return redirect(url_for(.index))
return render_template(index.html,
formform, namesession.get(name),
knownsession.get(known, False),
current_timedatetime.utcnow())
在blueprint里书写view在两个主要的不同之处。首先像前面的错误处理器路由装饰器来自blueprint。
第二个不同之处是 url_for() 函数的使用。你可能记得这个函数的第一个参数是路由的endpoint名 在应用的路由里黙认是view名。例如在一个脚本的应用的index()view函数的URL可以用url_for(index)获得。
blueprints的不同是Flask使用名字空间来调用来自blueprint的endpoints以便多个blueprints可以定义view函数使用相同的endpoint名而不冲突。名字空间是blueprint的名blueprint构造器的第一个参数所以index()view函数用endpoint名main.index注册它的URL可以用url_for(main.index)获得。blueprints里url_for()函数也支持更短格式的 endpoints其中blueprint名可以忽略例如url_for(.index)。使用这种标记使用当前请求的blueprint。这意味着相同blueprint里的重定向可以用更短格式而blueprints间的重定向必须使用 namespaced端点名。
要完成应用页的变更 form对象也存贮在app/main/forms.py的blueprint里。
启动脚本
使用在顶层目录里的manage.py文件来启动应用。这个脚本展示于 Example 7-8.
Example 7-8. manage.py: 启动脚本
#!/usr/bin/env python
import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand
app create_app(os.getenv(FLASK_CONFIG) or default)
manager Manager(app)
migrate Migrate(app, db)
def make_shell_context():
return dict(appapp, dbdb, UserUser, RoleRole)
manager.add_command(shell, Shell(make_contextmake_shell_context))
manager.add_command(db, MigrateCommand)
if __name__ __main__:
manager.run()
脚本以创建一个应用开始。使用的配置来自环境变量FLASK_CONFIG 如果它被定义。如果没有定义使用黙认的配置。然后Flask-Script, Flask-Migrate, 和Python shell的自制义上下文初始化。
作为习惯增加一行shebang以便基于Unix的操作系统里脚本可以执行为./manage.py 而不是python manage.py。
Requirements文件
应用必须包括requirements.txt文件记录所有的依赖包带上正确的版本号。 这很重要对于不同机器上产生相同的虚拟环境。例如应用布局于生产环境。这个文件通过如下命令自动产生
(venv) $ pip freeze requirements.txt
当包被安装或更新时刷新这个文件是很好的想法。示例的requirements文件如下
Flask0.10.1
Flask-Bootstrap3.0.3.1
Flask-Mail0.9.0
Flask-Migrate1.1.0
Flask-Moment0.2.0
Flask-SQLAlchemy1.0
Flask-Script0.6.6
Flask-WTF0.9.4
Jinja22.7.1
Mako0.9.1
MarkupSafe0.18
SQLAlchemy0.8.4
WTForms1.0.5
Werkzeug0.9.4
alembic0.6.2
blinker1.3
itsdangerous0.23
当你要构建相同的虚拟环境时你可以创建新的虚拟环境然后运行如下命令:
(venv) $ pip install -r requirements.txt
你读这本书时示例requirements.txt 的版本号可能已经过时了。如果你喜欢你可以使用最新发行包。如果你遇到问题你可以返回requirements文件指定的版本因为这些是已知与应用兼容的。
单元测试
这个应用很小还没有太多的测试但作为示例有两个测试可以按Example 7-9
Example 7-9. tests/test_basics.py: Unit tests
import unittest
from flask import current_app
from app import create_app, db
class BasicsTestCase(unittest.TestCase):
def setUp(self):
self.app create_app(testing)
self.app_context self.app.app_context()
self.app_context.push()
db.create_all()
def tearDown(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_app_exists(self):
self.assertFalse(current_app is None)
def test_app_is_testing(self):
self.assertTrue(current_app.config[TESTING])
这些测试用python标准库的标准的unittest包。 setUp()和tearDown()在测试前后运行任何有test_开始的名称的方法都按测试执行。如果你想要学习更多用python unittest包写单元测试请看官方文档。
setUp()方法试图创建一个与运行应用相似的测试环境。它首先创建一个测试的应用配置和激活上下文。这一步确保测试可以像正常的请求一样访问。然后它创建一个新的数据库必要时测试可以使用它。数据库和应用上下文用tearDown()方法删除。
第一个测试确保应用实全存在。第二个测试确保应用运行于测试本置。要使测试目录成为一个合适的包需要增加tests/__init__.py但这可以是空的文件。因为 unittest包可以扫描所有模块并定位测试。要运行单元测试可以在manage.py脚本增加制定命令。
Example 7-10 展示如何增加test命令。
Example 7-10. manage.py: Unit test launcher command
manager.command
def test():
Run the unit tests.
import unittest
tests unittest.TestLoader().discover(tests)
unittest.TextTestRunner(verbosity2).run(tests)
manager.command装饰器使定制命令变得简单。装饰函数的名称用作命令名函数的 docstring 显示于帮助信息。test()的实施调用来自unittest包的测试运行器。单元测试可以用如下方法执行。
(venv) $ python manage.py test
test_app_exists (test_basics.BasicsTestCase) ... ok
test_app_is_testing (test_basics.BasicsTestCase) ... ok
.----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
数据库设置
重构的应用使用一个不同于单一脚本版本的数据库。数据库URL取自环境变量作为第一选择黙认的SQLite数据库作为备选。环境变量和数据文件对于三种配置是不同的。例如在开发配置里URL来自环境变量DEV_DATABASE_URL如果没有定义则使用名为 data-dev.sqlite的 SQLite 数据库。不管数据库URL的来源都要首先创建新数据库的数据表。当用Flask-Migrate来跟踪迁移时可以创建或更新数据库表到最新的版本使用如下命令
(venv) $ python manage.py db upgrade
不管你信不信你已到达 Part I的结尾了。你已学习了用Flask构建网络应用的必要元素但是你可能还不确定如何用这些元素来形成一个实际的应用。