Application Factories:应用工厂

在单个文件中开发 程度很方便,但却有个很大的缺点,以为程序在全局作用域中创建,所以无法动态修改配置。运行脚本时,程序实例已经创建,再修改配置为时已晚。这一点对单元测试尤其重要,因为有时为了提高程序测试覆盖度,必须在不同的配置环境中运行程序。
解决方法就是延迟创建程序实例,把创建过程移到可显示调用的工厂函数

优点:

  • 可以给脚本留出配置程序的时间
  • 能够创建多个程序示例。

工厂创建

app/__init__.py(原来的hello.py)

from flask import Flask, render_template, session, redirect,url_for,flash, Blueprint
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail, Message
from flask.ext.script import Manager, Shell
from flask.ext.sqlalchemy import SQLAlchemy

import os 

basedir = os.path.abspath(os.path.dirname(__file__))
############################################################
bootstrap = Bootstrap()
mail = Mail()
db = SQLAlchemy()
# manager = Manager()
# migrate = Migrate(app,db)
def create_app(config_name):
    app = Flask(__name__)
    ################# configuratio n#####################
    app.config['SECRET_KEY'] = 'hard to guess string'
    app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(basedir,'data.sqlite')
    app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] =True
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] =True
    app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[hoptop]'
    app.config['FLASKY_MAIL_SENDER'] = 'Flasky Admin <xuzhougeng@163.com>'
    app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN')
    app.config['MAIL_SERVER'] ='smtp.163.com'
    app.config['MAIL_PORT'] = 465
    app.config['MAIL_USE_SSL'] = True
    app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
    app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
    ################ initialize extension ##################
    bootstrap.init_app(app)
    mail.init_app(app)
    db.init_app(app)
    ################ register bllueprint #############
    from .main import main as main_blueprint
    app.register_blueprint(main_blueprint)
    
    return app

说明

  • 在导入库方面:和之前的hello.py无差异
  • 在扩展方面: 由于尚未初始化所需的程序实例,所以没有初始化扩展(如bootstrap=Bootstrap()未传入参数。在工厂函数中初始化(bootstrap.init_app(app))
  • create_app()函数就是程序的工厂函数,接受不同配置作为参数(这里偷懒,直接在内部设置了config,实际上应该建立一个config.py,从这里导入配置),返回一个应用(app),相当于hello.py的app = Flask(__name__)
  • 在路由(app.route)方面:在单脚本程序中,程序实例(app)存在于全局作用域中,路由可以直接使用app.route修饰器定义。但现在程序在运行时创建,只有调用create_app()之后才能使用app.route修饰器,显然这时才定义路由就太晚了。这时候就要用到上一篇说到的蓝本

再谈蓝本

上一篇中只知道蓝本可以把一个应用简化分解成几个小部分,虽然说能多次注册看起来挺棒的,但是实际上也就那样,因此对这个概念也就不太理解。

蓝本定义的路由处于休眠状态,直到蓝本注册到程序上后,路由才真正成为程序的一部分。

因此蓝本完美的解决了工厂函数在定义路由方面的问题。
上篇的蓝本在单个文件中定义,这里我们采用结构化的方式在包中的多个模块中创建。

app/main/__init__.py

from flask import Blueprint

main = Blueprint('main',__name__)

from . import views

说明:通过实例化一个Blueprint类对象可以创建蓝本。这个构造函数有个必须指定的参数:蓝本的名字(这里是’main')和蓝本所在的包或模块(__name__)。由于我们采用的是结构化定义蓝本,所以在最后一行中导入
顺便说下python中__name__的作用

  • 如果模块是被导入,name的值为模块名字 ;
  • 如果模块是被直接执行,name的值为’main
    其他参数:指定静态文件夹路径:static_folder='static';指定模板文件路径,默认在app/static:template_folder='templates',默认在app/templates。

为了更加简化views.py,我们把数据库模型和表格模型也独立出来。

  • app/models.py
from flask.ext.sqlalchemy import SQLAlchemy
from . import db

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    uers = db.relationship('User', backref='role')
    
    def __repr__(self):
        return '<Role %r>' %self.name
        
class User(db.Model):
    __table__name = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(64),unique=True, index=True)
    role_id = db.Column(db.Integer,db.ForeignKey('roles.id'))
    
    def __repr__(self):
        return '<User %r>' %self.username
  • app/mail.py

    from flask.ext.mail import Mail, Message

    def send_email(to,subject, template, **kwargs):
    msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX']+subject, sender=app.config['FLASKY_MAIL_SENDER'],recipients=[to])
    msg.body = render_template(template + '.txt', **kwargs)
    msg.html = render_template(template + '.html', **kwargs)
    mail.send(msg)

  • app/main/forms.py

from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import Required

class NameForm(Form): 
    name = StringField('who are you', validators=[Required()])
    submit = SubmitField('Submit')
  • app/main/views.py
from flask import session, redirect, render_template,url_for

from . import main
from .forms import NameForm
from .. import db
from ..models import User

@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 app.config['FLASKY_ADMIN']:
                send_email(app.config['FLASKY_ADMIN'],'New User','mail/new_user',user=user)
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data =''
        return redirect(url_for('.index')) #注意这里是.index 
        #如果在一个蓝图的视图函数或者被渲染的模板中需要链接同一个蓝图中的其他 端点,那么使用相对重定向,只使用一个点使用为前缀
    return render_template('index.html',form=form,name=session.get('name'), known=session.get('known',False))

到这里位置,一个hello.py就被分解成各个单独功能文件,接下来我们需要改进我们的启动脚本用于启动程序
/manage.py

import os
from app import create_app, db
from app.models import User, Role
from flask.ext.script import Manager, Shell
from flask.ext.migrate import Migrate, MigrateCommand

app = create_app()
manager = Manager(app)
migrate = Migrate(app,db)

def make_shell_context():
    return dict(app=app,db=db,User=User,Role=Role)

app.debug = True
manager.add_command("shell",Shell(make_context=make_shell_context))
manager.add_command('db',MigrateCommand)

if __name__ == '__main__':
    manager.run()

这样整体上就完成了hello.py的分解,接下来就是除虫的过程,上面的代码中肯定存在不少的bug,当然你看的时候估计都被我改完了。

少年 python manage.py runserver 来检验你的成果吧


除虫中出现的问题:

  • 部分文件中存在库导入补全
  • 部分文件中函数名打错了
  • app/main/views.py中app.config。本来打算用main.config代替,然而main是视图函数除非程序运行不然是休眠状态,没有config。既然要偷懒,那么这里就直接吧发送邮件功能删了吧。
上一篇:Xamarin.Android使用ZBar库扫描条码实现


下一篇:一起谈.NET技术,Xml日志记录文件最优方案(附源代码)