什么是程序工厂函数?请先看__init__.py的代码:
from flask import Flask,render_template
from flask.ext.bootstrap import Bootstrap
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.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()函数就是工厂函数。为什么要使用工厂函数?其实在上一文已经提到过,主要是因为在可以实现app的动态配置,通过传给create_app()不同的参数,给app不同的配置。下面我们逐行分析下这个__init__.py文件:
1、8~11行分别创建了bootstrap等四个对象。
对比之前对象的创建方式:
mail=Mail(app)
bootstrap=Bootstrap(app)
moment=Moment(app)
db=SQLAlchemy(app)
此次创建并没有传入app的参数,而是分别采用Bootstrap等无参构造方式,然后再create_app())中利用init_app成员函数进行a与pp对象的关联:
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
2、接下来看倒数第2行和第3行,这里我们引入了一个蓝本的类对象main_blueprint
我们先看下app/main/__init__.py中对main的定义:
from flask import Blueprint
main=Blueprint('main',__name__)
from . import views,errors
发现main是一个叫做Blueprint的类对象,在工厂函数中利用 app.register_blueprint()关联了这个类对象,接下来查看flask中对register_blueprint的定义:
@setupmethod
def register_blueprint(self, blueprint, **options):
"""Register a blueprint on the application. For information about
blueprints head over to :ref:`blueprints`.
The blueprint name is passed in as the first argument.
Options are passed as additional keyword arguments and forwarded to
`blueprints` in an "options" dictionary.
:param subdomain: set a subdomain for the blueprint
:param url_prefix: set the prefix for all URLs defined on the blueprint.
``(url_prefix='/<lang code>')``
:param url_defaults: a dictionary with URL defaults that is added to
each and every URL defined with this blueprint
:param static_folder: add a static folder to urls in this blueprint
:param static_url_path: add a static url path to urls in this blueprint
:param template_folder: set an alternate template folder
:param root_path: set an alternate root path for this blueprint
.. versionadded:: 0.7
"""
first_registration = False
if blueprint.name in self.blueprints:
assert self.blueprints[blueprint.name] is blueprint, \
'A blueprint\'s name collision occurred between %r and ' \
'%r. Both share the same name "%s". Blueprints that ' \
'are created on the fly need unique names.' % \
(blueprint, self.blueprints[blueprint.name], blueprint.name)
else:
self.blueprints[blueprint.name] = blueprint
self._blueprint_order.append(blueprint)
first_registration = True
blueprint.register(self, options, first_registration)
发现register_blueprint()函数的作用就是把蓝本对象main注册到了其一个list中blueprints,我们可以做这样的猜想,当用户在输入URL中,程序内部会从app类中的蓝本list中找到这个蓝本对象main,然后再从main中找到对应的templates或者static资源路径,为了验证这个猜想,我们再看下blueprint的构造函数:
def __init__(self, name, import_name, static_folder=None,
static_url_path=None, template_folder=None,
url_prefix=None, subdomain=None, url_defaults=None,
root_path=None):
_PackageBoundObject.__init__(self, import_name, template_folder,
root_path=root_path)
self.name = name
self.url_prefix = url_prefix
self.subdomain = subdomain
self.static_folder = static_folder
self.static_url_path = static_url_path
self.deferred_functions = []
if url_defaults is None:
url_defaults = {}
self.url_values_defaults = url_defaults
我们使用Blueprint('main',__name__),也就是没有传入static_folder等参数,请留意deferred_functions 这个成员变量,这是一个list,将用于存储flask的url和视图函数的映射关系。我们看下在app/main/viem.py中用来添加路由和视图函数的方法:
from datetime import datetime
from flask import render_template,session,redirect,url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User
@main.route('/',methods=['GET','POST'])
def index():
name=None
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['konwn'] = 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('main.index'))
return render_template('index.html',form=form,name=session.get('name'),
known=session.get('known',False))
它调用蓝本的route函数添加路由与视图函数的映射,下面分别是route等函数的原型,它们存在调用关系:route -> add_url_rule() -> record()。仔细看下调用record的代码,传给它的参数是由lambda定义的一个匿名函数,返回参数的add_url_rule方法,而这个参数将来就会是flask程序对象。至此我们跟踪完了添加路由和视图函数的整个过程。
ef route(self, rule, **options):
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
:func:`url_for` function is prefixed with the name of the blueprint.
"""
def decorator(f):
endpoint = options.pop("endpoint", f.__name__)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
"""Like :meth:`Flask.add_url_rule` but for a blueprint. The endpoint for
the :func:`url_for` function is prefixed with the name of the blueprint.
"""
if endpoint:
assert '.' not in endpoint, "Blueprint endpoints should not contain dots"
self.record(lambda s:s.add_url_rule(rule, endpoint, view_func, **options))
def record(self, func):
"""Registers a function that is called when the blueprint is
registered on the application. This function is called with the
state as argument as returned by the :meth:`make_setup_state`
method.
"""
if self._got_registered_once and self.warn_on_modifications:
from warnings import warn
warn(Warning('The blueprint was already registered once '
'but is getting modified now. These changes '
'will not show up.'))
self.deferred_functions.append(func)
Github位置:
https://github.com/HymanLiuTS/flaskTs
克隆本项目:
git clone git@github.com:HymanLiuTS/flaskTs.git
获取本文源代码:
git checkout FL26