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包中
- 和之前一样,侧女文件夹包含Python虚拟环境
并同时创建一些新文件:
- requirements.txt列出了所有依赖包,便于在其他电脑中重新生成相同的虚拟环境
- config.py存储配置
- manage.py用于启动程序以及其他的程序任务
把hello.py转换成这种结构的过程
配置选项
程序经常需要设定多个配置,开发、测试和生产环境要使用不同的数据库,这样才不会彼此影响。
使用层次结构的配置类。
#程序的配置:基类 Config 中包含通用配置,子类分别定义专用的配置
import os
basedir=os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY=os.environ.get('SECRET_KET') or 'hard ro guess string'
#Flask(以及相关的扩展extension)需要进行加密
SQLALCHEMY_COMMIT_ON_TEARDOWN=True
#将其设为True时,每次请求结束后都会自动提交数据库中的变动。
FLASKY_MAIL_SUBJECT_PREFIX=['Flasky']
FLASKY_MAIL_SENDER='Flasky Admin '
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_URL=os.environ.get('DEV_DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING=True
SQLALCHEMY_DATABASE_URL=os.environ.get('TEST_DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
class ProductionConfig(Congfig):
SQLCHEMY_DATABASE_URL=os.environ.get('DATABASE_URL') or \'sqlite:///' + os.path.join(basedir, 'data.sqlite')
#在 3 个子类中, SQLALCHEMY_DATABASE_URI 变量都被指定了不同的值。这样程序就可在不同的配置环境中运行,每个环境都使用不同的数据库。
config={
'development':DevelopmentConfig,
'testing':TestingConfig,
'production':ProduvtionConfig,
'default':DevelopmentConfig
}
程序包
程序包用来保存程序的所有代码,模板和静态文件
使用程序工厂函数
为了可以修改配置,可以延迟创建程序实例,把创建过程移到可显式调用的工厂函数中,这样可以创建多个程序实例。
程序的工厂函数在app包的构造文件中定义
#app/__init__.py:程序包的构造文件
from flask import Flask,render_template
from flask.ext.boorstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLALchemy
from onfig import config
bootstrap=Bootstrap()
mail=Mail()
moment=Moment()
db=SQLALchemy()
#creat_app()函数就是程序的工厂函数
def creat_app(config_name):
app=Flask(__name__)
app.config.from_object(config[config_name])
#配置类在 config.py 文件中定义,其中保存的配置可以使用 Flask app.config 配置对象提供的 from_object() 方法直接导入程序。
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
#附加路由和自定义的错误界面
return app
在蓝本中实现程序功能
在蓝本中定义的路由处于休眠状态,直到蓝本注册到程序上后,路由才真正成为程序的一部分。为了获得最大的灵活性,程序包中创建了一个子包,用于保存蓝本。
#app/main/__init__.py:创建蓝本
from flask import Blueprint
main-Blueprint('main',__name__) #通过实例化一个 Blueprint 类对象可以创建蓝本。这个构造函数有两个必须指定的参数:蓝本的名字和蓝本所在的包或模块。
from . import views,errors
程序的路由保存在包里的 app/main/views.py 模块中,而错误处理程序保存在 app/main/errors.py 模块中。导入这两个模块就能把路由和错误处理程序与蓝本关联起来。
#app/__init__.py:注册蓝本
def creat_app(config_name):
#...
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
#app/main/errors.oy 蓝本中的错误处理程序
在蓝本中编写错误处理程序稍有不同,如果使用 errorhandler 修饰器,那么只有蓝本中的错误才能触发处理程序。要想注册程序全局的错误处理程序,必须使用 app_errorhandler 。
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/main/views.py蓝本中定义的程序路由
from datetime import datetime
from flask import render_tempalte,session,redirect,url_for
from . import main
from .froms 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_tempalte('index.html',form=form,name=session.get('name'),known=session.get('known',Flase),current_time=datetime.utcnow())
#url_for() 函数还支持一种简写的端点形式,在蓝本中可以省略蓝本名,例如 url_for('.index') 。在这种写法中,命名空间是当前请求所在的蓝本。
启动脚本
顶级文件夹中的manage.py文件用于启动程序
#manage.py:启动脚本
import os
from app import creat_app,db
from app.models import User,Role
from flask.ext.script import Manager.Shell
from flask.ext.migrate import Migrate,MigrateCommand
app=creat_app(os.getenv('FLASK_CONFIG') or 'default')
manager=Manger(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()
需求文件
程序中必须包含一个 requirements.txt 文件,用于记录所有依赖包及其精确的版本号。
Flask==0.10.1
Flask-Bootstrap==3.0.3.1
Flask-Mail==0.9.0
Flask-Migrate==1.1.0
Flask-Moment==0.2.0
Flask-SQLAlchemy==1.0
Flask-Script==0.6.6
Flask-WTF==0.9.4
Jinja2==2.7.1
Mako==0.9.1
MarkupSafe==0.18
SQLAlchemy==0.8.4
WTForms==1.0.5
Werkzeug==0.9.4
alembic==0.6.2
blinker==1.3
itsdangerous==0.23
如果你要创建这个虚拟环境的完全副本,可以创建一个新的虚拟环境,并在其上运行以下命令:
(venv) $ pip install -r requirements.txt
单元测试
tests/test_basics.py:单元测试
from 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 teatDown(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 包编写测试,请阅读官方文档(https://docs.python.org/2/library/unittest.html)。
#manger.py:启动单元测试的命令
@manager.command
def test():
"""Run the unit tests."""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
创建数据库
首选从环境变量中读取数据库的 URL,同时还提供了一个默认的 SQLite 数据库做备用。3
种配置环境中的环境变量名和 SQLite 数据库文件名都不一样。例如,在开发环境中,数据
库 URL 从环境变量 DEV_DATABASE_URL 中读取,如果没有定义这个环境变量,则使用名为
data-dev.sqlite 的 SQLite 数据库。
不管从哪里获取数据库 URL,都要在新数据库中创建数据表。如果使用 Flask-Migrate 跟
踪迁移,可使用如下命令创建数据表或者升级到最新修订版本:
(venv) $ python manage.py db upgrade