一、动态配置Flask app
在使用单个文件开发Flask应用程序时,由于Flask对象app是在全局作用域中创建的,在运行这个全局文件脚本时,app就已经创建,所以此时修改app的配置是不会生效的。为了解决这个问题可以把app的创建移到可显式调用的工厂函数中,即延迟创建app实例。
工厂函数:app/__init__.py
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from config import config
db = SQLAlchemy()
csrf = CSRFProtect()
# 创建Flask实例,注册第三方扩展,调用config.init_app初始化
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app) # 注册第三方扩展
csrf.init_app(app)
return app # 返回app实例
此时调用create_app()即可动态传入config_name参数实现动态配置。
二、使用蓝本
使用工厂函数创建app实例可以解决了动态配置的问题,但是程序路由应该如何配置呢?我们在使用单个文件开发Flask程序时,在全局作用域创建了app实例,直接使用app.route就可配置路由,而现在app实例的创建已经移到工厂函数中进行了,只有调用工厂函数create_app后才能使用app实例,然而我们的路由函数在编写程序时已经定义好了,也就是说在编写路由函数时是无法获取到app实例的,也就无法定义路由配置。通用的所有需要在编码时使用app实例的配置此时都无法使用。
为了解决这个问题flask提供了蓝本。
蓝本:app/main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__) # 创建蓝本
from . import views # 在views定义程序的路由
在views中定义的程序路由只有在蓝本注册到app实例上后才有效。
# 创建Flask实例,注册第三方扩展,调用config.init_app初始化
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
csrf.init_app(app)
from .main import main as main_blueprint # 导入蓝本
app.register_blueprint(main_blueprint) # 注册蓝本
return app
完整的(app/__init__.py)
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from config import config
db = SQLAlchemy()
csrf = CSRFProtect()
# 创建Flask实例,注册第三方扩展,调用config.init_app初始化
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
db.init_app(app)
csrf.init_app(app)
#注册蓝本
from .main import main as main_blueprint
from .auth import auth as auth_blueprint
app.register_blueprint(main_blueprint)
app.register_blueprint(auth_blueprint, url_prefix = '/auth')
return app
views.py
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main # 导入蓝本
@main.route('/home', methods = ['GET', 'POST']) # 定义路由
def index():
return render_template('welcome.html')
三、定义程序启动脚本
manage.py
import os
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
from app import create_app
from app import db
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)
manager.add_command('shell', Shell( make_context = make_shell_context() ) )
manager.add_command('db', MigrateCommand)
if __name__ == '__main__':
manager.run()
在定义程序脚本时需要使用Flask-Script和Flask-Migrate包。运行python manage.py即可启动程序。
四、完整的项目结构
|-- myproject
|-- config.py
|-- flask-env.yml
|-- manage.py
|-- app
| |-- models.py
| |-- __init__.py # 创建app实例的工厂函数
| |-- auth # auth蓝本
| | |-- forms.py
| | |-- views.py
| | |-- __init__.py
| | |-- templates
| |-- main # main蓝本
| | |-- errors.py
| | |-- forms.py
| | |-- views.py
| | |-- __init__.py
| |-- static
| | |-- css
| | |-- image
| | |-- js
| |-- templates
|-- migrations
|-- tests
| |-- __init__.py
|-- venv
五、动态配置类
config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
# 通用配置
class Config:
WTF_CSRF_SECRET_KEY = os.environ.get('WTF_CSRF_SECRET_KEY') or 'ao blog csrf key'
SECRET_KEY = os.environ.get('SECRET_KEY') or 'ao blog session key'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 初始化Flask实例,对当前环境的配置初始化
@staticmethod
def init_app(app):
app.config['SECRET_KEY'] = SQLALCHEMY_COMMIT_ON_TEARDOWN
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = SQLALCHEMY_COMMIT_ON_TEARDOWN
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = SQLALCHEMY_COMMIT_ON_TEARDOWN
app.config['WTF_CSRF_SECRET_KEY'] = SQLALCHEMY_COMMIT_ON_TEARDOWN
# 开发环境配置
class DevelopmentConfig(Config):
DEBUG = True
# SQLALCHEMY_TRACK_MODIF = 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('DEV_DATABASE_URL') or
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
config = {
'dev': DevelopmentConfig,
'test': TestingConfig,
'prod': ProductionConfig,
'default': DevelopmentConfig
}