虽然小型web应用程序用单个脚本能够很方便,但这种方法却不能很好地扩展。随着应用变得复杂,在单个大的源文件中处理会变得问题重重。html
与大多数其余web框架不一样,Flask对大型项目没有特定的组织方式;应用程序的结构彻底交给开发人员本身决定。在这一章,提出一个可能的方式来组织管理一个大型应用程序的包和模块。这种结构将用于书中其他的示例中。python
示例7-1展现基本Flask应用程序结构web
示例7-1:基本多文件Flask应用结构sql
|-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
这个结构有四个顶层目录:数据库
Flask应用通常放置在名为app
的目录下。flask
migrations
目录包含数据库迁移脚本,这和以前说的同样。bootstrap
单元测试放置在test
目录下安全
venv
目录包含Python虚拟环境,这和以前说的也是同样的。session
还有一些新的文件:app
requirements.txt
列出一些依赖包,这样就能够很容易的在不一样的计算机上部署一个相同的虚拟环境。
config.py
存储了一些配置设置。
manage.py
用于启动应用程序和其余应用程序任务。
为了帮助你彻底理解这个结构,下面会描述将hello.py
应用改成符合这一结构的整个流程。
应用程序一般须要几个配置设置。最好的例子就是在开发过程当中须要使用不一样的数据库,测试,生产环境,这样他们能够作到互不干扰。
咱们可使用配置类的层次结构来代替hello.py
中的简单类字典结构配置。示例7-2展现了config.py
文件。
示例7-2. config.py:应用程序配置
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 <flasky@example.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()
静态方法。这里特定于配置的初始化是能够执行的。这里Config
基类实现一个空init_app()
方法。
在配置脚本的底部,这些不一样的配置是注册在配置字典中。将其中一个配置(开发配置)注册为默认配置。
应用程序包放置了全部应用程序代码、模板和静态文件。它被简单的称为app
,也能够给定一个特定于应用的名称(若是须要的话)。templates
和static
目录是应用的一部分,所以这两个目录应该放置在app
中。数据库模型和电子邮件支持功能也要置入到这个包中,每一个都以app/models.py
和app/email.py
形式存入本身的模块当中。
在单个文件中建立应用程序的方式很是方便,可是它有一个大缺点。由于应用程序建立在全局范围,没有办法动态的适应应用配置的更改:脚本运行时,应用程序实例已经建立,因此它已经来不及更改配置。对于单元测试这是特别重要的,由于有时须要在不一样的配置下运行应用程序来得到更好的测试覆盖率。
解决这一问题的方法就是将应用程序放入一个工厂函数中来延迟建立,这样就能够从脚本中显式的调用。
这不只给脚本充足的时间来设置配置,也能用于建立多个应用程序实例——一些在测试过程当中很是有用的东西。被定义在app
包的构造函数中的应用程序工厂函数会在示例7-3中展现。
这个构造函数导入大部分当前须要使用的扩展,但由于没有应用程序实例初始化它们,它能够被建立但不初始化经过不传递参数给它们的构造函数。create_app()
即应用程序工厂函数,须要传入用于应用程序的配置名。配置中的设置被保存在config.py
中的一个类中,可使用Flask的app.config
配置对象的from_object()
方法来直接导入。配置对象能够经过对象名从config
字典中选出。一旦应用程序被建立且配置好,扩展就能够被初始化。调用扩展里的init_app()
以前先建立并完成初始化工做。
示例7-3. app/ _init__.py:应用程序包构造函数_
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
工厂函数返回建立的应用程序实例,可是请注意,在当前状态下使用工厂函数建立的应用程序是不完整的,由于它们没有路由和自定义错误页面处理程序。这是下一节的主题。
应用程序工厂的转化工做引出了路由的复杂化。在单脚本应用中,应用程序实例是全局的,因此能够很容易地使用app.route
装饰器定义路由。可是如今应用程序在运行时建立,app.route
装饰器只有在create_app()
调用后才开始存在,这就太迟了。就像路由那样,这些经过app.errorhandler
装饰器定义的自定义错误页面处理程序也存在一样的问题。
幸运的是Flask使用蓝图来提供一个更好的解决方案。一个蓝图就相似于一个能够定义路由的应用程序。不一样的是,和路由相关联的蓝图都在休眠状态,只有当蓝图在应用中被注册后,此时的路由才会成为它的一部分。使用定义在全局做用域下的蓝图,定义应用程序的路由就几乎能够和单脚本应用程序同样简单了。
和应用程序同样,蓝图能够定义在一个文件或一个包中与多个模块一块儿建立更结构化的方式。为了追求最大的灵活性,能够在应用程序包中建立子包来持有蓝图。示例7-4展现了建立蓝图的构造函数。
示例7-4. app/main/ _init__.py:建立蓝图_
from flask import Blueprint main = Blueprint('main', __name__) from . import views, errors
蓝图是经过实例化Blueprint
类对象来建立的。这个类的构造函数接收两个参数:蓝图名和蓝图所在的模块或包的位置。与应用程序同样,在大多数状况下,对于第二个参数值使用Python的__name__
变量是正确的。
应用程序的路由都保存在app/main/views.py
模块内部,而错误处理程序则保存在app/main/errors.py
中。导入这些模块可使路由、错误处理与蓝图相关联。重要的是要注意,在app/init.py
脚本的底部导入模块要避免循环依赖,由于view.py
和errors.py
都须要导入main
蓝图。
蓝图和应用程序同样注册在create_app()
工厂函数中,如示例7-5所示。
示例7-5. app/ _init__.py:蓝图注册_
def create_app(config_name): # ... from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
示例7-6展现了错误处理。
示例7-6. app/main/errors.py:蓝图的错误处理
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
在蓝图中写错误处理的不一样之处是,若是使用了errorhandler
装饰器,则只会调用在蓝图中引发的错误处理。而应用程序范围内的错误处理则必须使用app_errorhandler
。
示例7-7展现了被更新在蓝图中的应用程序路由。
示例7-7. app/main/views.py:带有蓝图的应用程序路由
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', form=form, name=session.get('name'), known=session.get('known', False), current_time=datetime.utcnow())
在蓝图中写视图函数有两大不一样点。第一,正如以前的错误处理同样,路由装饰器来自于蓝图。第二个不一样是url_for()
函数的使用。你可能会回想,该函数的第一个参数为路由节点名,它给基于应用程序的路由指定默认视图函数。例如,单脚本应用程序中的index()
视图函数的URL能够经过url_for('index')
来得到。
不一样的是Flask名称空间适用于来自蓝图的全部节点,这样多个蓝图可使用相同节点定义视图函数而不会产生冲突。名称空间就是蓝图名(Blueprint
构造函数中的第一个参数),因此index()
视图函数注册为main.index
且它的URL能够经过url_for('main.index')
得到。
在蓝图中,url_for()
函数一样支持更短格式的节点,省略蓝图名,例如url_for('.index')
。有了这个,就能够这样使用当前请求的蓝图了。这实际意味着相同蓝图内的重定向可使用更短的形式,若是重定向跨蓝图则必须使用带名称空间的节点名。
完成了应用程序页面更改,表单对象也保存在app/main/forms.py
模块中的蓝图里面。