0x1 項目結構html
git checkout 7apython
多文件Flask程序基本結構git
多文件 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包中shell
migrations包含數據庫遷移腳本數據庫
單元測試在tests包中flask
venv包含Python虛擬環境bootstrap
requirements.txt列出全部依賴包session
config.py 存儲配置app
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 <flasky@example.com>' 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