Flask之大型程序結(jié)構(gòu)
使用包和模塊組織大型程序
項(xiàng)目結(jié)構(gòu):
|-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
四個(gè)頂級(jí)文件夾
- Flask程序一般都保存在名為app的包中
- 和之前一樣,migrations文件夾包含數(shù)據(jù)庫(kù)遷移的腳本
- 單元測(cè)試編寫在tests包中
- 和之前一樣,側(cè)女文件夾包含Python虛擬環(huán)境
并同時(shí)創(chuàng)建一些新文件:
- requirements.txt列出了所有依賴包,便于在其他電腦中重新生成相同的虛擬環(huán)境
- config.py存儲(chǔ)配置
- manage.py用于啟動(dòng)程序以及其他的程序任務(wù)
把hello.py轉(zhuǎn)換成這種結(jié)構(gòu)的過(guò)程
配置選項(xiàng)
程序經(jīng)常需要設(shè)定多個(gè)配置,開(kāi)發(fā)、測(cè)試和生產(chǎn)環(huán)境要使用不同的數(shù)據(jù)庫(kù),這樣才不會(huì)彼此影響。
使用層次結(jié)構(gòu)的配置類。
#程序的配置:基類 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(以及相關(guān)的擴(kuò)展extension)需要進(jìn)行加密
SQLALCHEMY_COMMIT_ON_TEARDOWN=True
#將其設(shè)為True時(shí),每次請(qǐng)求結(jié)束后都會(huì)自動(dòng)提交數(shù)據(jù)庫(kù)中的變動(dòng)。
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_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 個(gè)子類中, SQLALCHEMY_DATABASE_URI 變量都被指定了不同的值。這樣程序就可在不同的配置環(huán)境中運(yùn)行,每個(gè)環(huán)境都使用不同的數(shù)據(jù)庫(kù)。
config={
'development':DevelopmentConfig,
'testing':TestingConfig,
'production':ProduvtionConfig,
'default':DevelopmentConfig
}
程序包
程序包用來(lái)保存程序的所有代碼,模板和靜態(tài)文件
使用程序工廠函數(shù)
為了可以修改配置,可以延遲創(chuàng)建程序?qū)嵗?,把?chuàng)建過(guò)程移到可顯式調(diào)用的工廠函數(shù)中,這樣可以創(chuàng)建多個(gè)程序?qū)嵗?br>
程序的工廠函數(shù)在app包的構(gòu)造文件中定義
#app/__init__.py:程序包的構(gòu)造文件
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()函數(shù)就是程序的工廠函數(shù)
def creat_app(config_name):
app=Flask(__name__)
app.config.from_object(config[config_name])
#配置類在 config.py 文件中定義,其中保存的配置可以使用 Flask app.config 配置對(duì)象提供的 from_object() 方法直接導(dǎo)入程序。
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
#附加路由和自定義的錯(cuò)誤界面
return app
在藍(lán)本中實(shí)現(xiàn)程序功能
在藍(lán)本中定義的路由處于休眠狀態(tài),直到藍(lán)本注冊(cè)到程序上后,路由才真正成為程序的一部分。為了獲得最大的靈活性,程序包中創(chuàng)建了一個(gè)子包,用于保存藍(lán)本。
#app/main/__init__.py:創(chuàng)建藍(lán)本
from flask import Blueprint
main-Blueprint('main',__name__) #通過(guò)實(shí)例化一個(gè) Blueprint 類對(duì)象可以創(chuàng)建藍(lán)本。這個(gè)構(gòu)造函數(shù)有兩個(gè)必須指定的參數(shù):藍(lán)本的名字和藍(lán)本所在的包或模塊。
from . import views,errors
程序的路由保存在包里的 app/main/views.py 模塊中,而錯(cuò)誤處理程序保存在 app/main/errors.py 模塊中。導(dǎo)入這兩個(gè)模塊就能把路由和錯(cuò)誤處理程序與藍(lán)本關(guān)聯(lián)起來(lái)。
#app/__init__.py:注冊(cè)藍(lán)本
def creat_app(config_name):
#...
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
#app/main/errors.oy 藍(lán)本中的錯(cuò)誤處理程序
在藍(lán)本中編寫錯(cuò)誤處理程序稍有不同,如果使用 errorhandler 修飾器,那么只有藍(lán)本中的錯(cuò)誤才能觸發(fā)處理程序。要想注冊(cè)程序全局的錯(cuò)誤處理程序,必須使用 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藍(lán)本中定義的程序路由
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() 函數(shù)還支持一種簡(jiǎn)寫的端點(diǎn)形式,在藍(lán)本中可以省略藍(lán)本名,例如 url_for('.index') 。在這種寫法中,命名空間是當(dāng)前請(qǐng)求所在的藍(lán)本。
啟動(dòng)腳本
頂級(jí)文件夾中的manage.py文件用于啟動(dòng)程序
#manage.py:?jiǎn)?dòng)腳本
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()
需求文件
程序中必須包含一個(gè) requirements.txt 文件,用于記錄所有依賴包及其精確的版本號(hào)。
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
如果你要?jiǎng)?chuàng)建這個(gè)虛擬環(huán)境的完全副本,可以創(chuàng)建一個(gè)新的虛擬環(huán)境,并在其上運(yùn)行以下命令:
(venv) $ pip install -r requirements.txt
單元測(cè)試
tests/test_basics.py:單元測(cè)試
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'])
#如果你想進(jìn)一步了解如何使用 Python 的 unittest 包編寫測(cè)試,請(qǐng)閱讀官方文檔(https://docs.python.org/2/library/unittest.html)。
#manger.py:啟動(dòng)單元測(cè)試的命令
@manager.command
def test():
"""Run the unit tests."""
import unittest
tests = unittest.TestLoader().discover('tests')
unittest.TextTestRunner(verbosity=2).run(tests)
創(chuàng)建數(shù)據(jù)庫(kù)
首選從環(huán)境變量中讀取數(shù)據(jù)庫(kù)的 URL,同時(shí)還提供了一個(gè)默認(rèn)的 SQLite 數(shù)據(jù)庫(kù)做備用。3
種配置環(huán)境中的環(huán)境變量名和 SQLite 數(shù)據(jù)庫(kù)文件名都不一樣。例如,在開(kāi)發(fā)環(huán)境中,數(shù)據(jù)
庫(kù) URL 從環(huán)境變量 DEV_DATABASE_URL 中讀取,如果沒(méi)有定義這個(gè)環(huán)境變量,則使用名為
data-dev.sqlite 的 SQLite 數(shù)據(jù)庫(kù)。
不管從哪里獲取數(shù)據(jù)庫(kù) URL,都要在新數(shù)據(jù)庫(kù)中創(chuàng)建數(shù)據(jù)表。如果使用 Flask-Migrate 跟
蹤遷移,可使用如下命令創(chuàng)建數(shù)據(jù)表或者升級(jí)到最新修訂版本:
(venv) $ python manage.py db upgrade