在编写小型Web程序时我们可以使用单一文件,但是当程序较复杂是,这种结构会导致很多问题。当我们在开发Flask Web程序时,可以使用包和模块组织源码。
1. 项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |- myproject |- app/ |- template/ |- 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
2. 配置选项 使用配置文件,为开发、测试和生产环境进行不同的设置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import osbasedir = 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 GRITY_MAIL_SUBJECT_PREFIX = '[Grity]' GRITY_MAIL_SENDER = 'Grity Admin <grity@example.com>' GRITY_ADMIN = os.environ.get('GRITY_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.sqlte' ) config = { 'development' : DevelopmentConfig, 'testing' : TestingConfig, 'production' : ProductionConfig, 'default' : DevelopmentConfig }
3. 程序包
使用单文件开发的Flask应用,其程序在全局作用域中创建,所以无法动态修改配置文件。运行脚本时,程序实例已经创建了。为了提高测试覆盖度,必须在不同的配置环境中运行程序,所以必须延迟创建程序实例,把程序创建过程移到可显式调用的工厂函数中。使用这种方法还可可以创建多个程序实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from flask import Flaskfrom flask_bootstrap import Bootstrapfrom flask_sqlalchemy import SQLAlchemyfrom config import configbootstrap = Bootstrap() 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) db.init_app(app) return app
Blueprint包的构造文件
1 2 3 4 5 6 from flask import Blueprintmain = Blueprint('main' , __name__) from . import views, errors
把Blueprint注册到程序上
1 2 3 4 5 6 7 def create_app (config_name) : from .mian import main as main_blueprint app.register_blueprint(main_blueprint) return app
Blueprint中的错误处理程序
1 2 3 4 5 6 7 8 9 10 11 from flask import render_templatefrom . 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中的路由定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from flask import render_template, session, redirect, url_forfrom . import mainfrom .forms import NameFormfrom .. import dbfrom ..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 ))
4. 启动脚本 顶级文件夹中的manage.py文件用于启动程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import osfrom app import create_app, dbfrom app.models import User, Rolefrom flask.ext.script import Manager, Shellfrom flask.ext.migrate import Migrate, MigrateCommandapp = 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()
5. 需求文件 程序中需要包含一个requirements.txt文件,用于记录所有依赖包及其版本号。这样就可以在另外一台电脑上重新生成虚拟环境。
1 (venv) $ pip freeze > requirements.txt
如要创建这个虚拟环境的副本,运行如下命令:
1 (venv) $ pip install -r requirements.txt
6. 单元测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import unittestfrom flask import current_appfrom app import create_app, dbclass 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' ])
在manage.py脚本中添加test自定义命令
1 2 3 4 5 @manager.command def test () : import unittest tests = unittest.TestLoader().discover('tests' ) unittest.TextTestRunner(verbosity=2 ).run(tests)
7. 创建数据库 1 2 3 4 5 6 $ python manage.py db init $ python manage.py db migrate -m "initial migrate" $ python manage.py db upgrade