0x1 项目结构
git checkout 7a
多文件Flask程序基本结构
多文件 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
Flask程序一般保存在app包中
migrations包含数据库迁移脚本
单元测试在tests包中
venv包含Python虚拟环境
requirements.txt列出所有依赖包
config.py 存储配置
manage.py 用于启动程序以及其他程序任务
0x2 配置选项
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 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‘) FLASKY_MAIL_SUBJECT_PREFIX = ‘[Flasky]‘ FLASKY_MAIL_SENDER = ‘Flasky Admin <[email protected]>‘ FLASKY_ADMIN = os.environ.get(‘FLASKY_ADMIN‘) @staticmethod def init_app(app): pass class DevelopmentConfig(Config): DEBUG = True 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包含通用配置,子类定义专用配置
0x3 程序包
3.1 app
程序包保存所有代码、模板、静态文件,这个包通常称为app
数据库模型和电子邮件支持函数保存为app/models.py和app/email.py
3.2 工厂函数
把创建程序实例移到可显式调用的工厂函数中,这样可以创建多个程序实例
app/__init__.py
from flask import Flask from flask_bootstrap import Bootstrap from flask_mail import Mail from flask_moment import Moment from flask_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) from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
create_app()就是程序的工厂函数,接受一个参数,是程序使用的配置名
配置类在config.py中定义,其中保存的配置可以使用Flask app.config配置对象提供的from_object()方法直接导入程序
配置对象则可以通过名字从config字典中选择
在之前创建的扩展对象上调用init_app()可以完成初始化过程
3.3 在蓝本中实现程序功能
蓝本可以定义路由,在蓝本中定义路由处于休眠状态,避免app.route定义路由不及时等问题
app/main/__init__.py:创建蓝本
from flask import Blueprint main = Blueprint(‘main‘, __name__) from . import views, errors
通过实例化一个Blueprint类对象可以创建蓝本,这个构造函数的两个必须指定的参数:
蓝本的名字和蓝本所在的包或模块,大多数情况第二个参数使用__name__变量
程序的路由在 app/main/views.py模块中,错误处理程序在app/main/errors.py模块中
导入这两个模块就能把路由和错误处理程序与蓝本关联起来(在app/main/__init__.py末尾导入)
app/__init__.py:注册蓝本
def create_app(config_name): #... from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
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
要想注册程序全局的错误处理程序,必须使用app_errorhandler
app/main/views.py:蓝本中定义的程序路由
from flask import render_template, session, redirect, url_for, current_app from .. import db from ..models import User from ..email import send_email from . import main from .forms import NameForm @main.route(‘/‘, methods=[‘GET‘, ‘POST‘]) def index(): form = NameForm() if form.validate_on_submit(): user = User.query.filter_by(username=form.name.data).first() if user is None: user = User(username=form.name.data) db.session.add(user) session[‘known‘] = False if current_app.config[‘FLASKY_ADMIN‘]: send_email(current_app.config[‘FLASKY_ADMIN‘], ‘New User‘, ‘mail/new_user‘, user=user) else: session[‘known‘] = True session[‘name‘] = form.name.data return redirect(url_for(‘.index‘)) return render_template(‘index.html‘, form=form, name=session.get(‘name‘), known=session.get(‘known‘, False))
在蓝本中url_for()用法不同,Flask为蓝本的全部端点加一个命名空间,命名空间就是蓝本的名字
所以视图函数index()注册的端点名是main.index,其URL使用url_for(‘main.index‘)获取
url_for()支持简写的端点形式,在蓝本中可以省略蓝本名,但跨蓝本的重定向的端点名必须带有命名空间
表单对象也要移到蓝本中,保存于 app/main/forms.py模块
0x4 启动脚本
顶级文件夹中的manage.py文件用于启动脚本
manage.py:启动脚本
#!/usr/bin/env python import os from app import create_app, db from app.models import User, Role from flask_script import Manager, Shell from flask_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(app=app, db=db, User=User, Role=Role) manager.add_command("shell", Shell(make_context=make_shell_context)) manager.add_command(‘db‘, MigrateCommand) if __name__ == ‘__main__‘: manager.run()
如果已经定义了环境变量FLASK_CONFIG,则从中读取配置名,否则使用默认配置
然后初始化Flask-Script、Flask-Migrate和为Python shell定义的上下文
脚本中加入shebang声明,可以通过./manage.py执行脚本
0x5 需求文件
程序中包含requirements.txt,用于记录所有依赖包及版本号
pip自动生成requirements.txt文件
(venv) $ pip freeze >requirements.txt
如果要创建这个虚拟环境的副本
(venv) $ pip install -r requirements.txt
0x6 单元测试
这个测试使用Python标准库中的unittest包编写
了解更多unittest包编写测试:https://docs.python.org/2/library/unittest.html
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‘])
setUp()方法创建一个测试环境,类似于运行中的程序
首先,使用测试配置创建程序,然后激活上下文,这样可确保能在测试中使用current_app
然后创建一个全新的数据库,以备不时之需,数据库和程序上下文在tearDown()方法中删除
第一个测试确保程序实例存在,第二个测试确保程序在测试配置中运行
若想把tests文件夹作为包使用,需要添加tests/__init__.py
manage.py:启动单元测试命令
@manager.command def test(): """Run the unit tests.""" import unittest tests = unittest.TestLoader().discover(‘tests‘) unittest.TextTestRunner(verbosity=2).run(tests)
单元测试运行:
(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.187s OK
0x7 创建数据库
如果使用Flask-Migrate跟踪迁移,可使用如下命令创建数据表或升级到最新修订版:
(venv) $ python manage.py db upgrade