flask 进阶
基本相当于翻译官方文档(并没有全翻),添加一些自己的理解。
用蓝图使应用程序模块化
蓝图的概念:当在一个应用程序上注册一个蓝图的时候,他们记录了执行的操作。当分发请求和从一个端点到另一个端点生成URL时,Flask用蓝图吧视图函数结合在一起。
使用蓝图的原因
- 把一个应用程序划分成一个蓝图的集合,这个对大型应用程序来说是理想的,一个项目可以实例化一个应用程序对象,初始化几个扩展并注册一个蓝图的集合。
- 在一个应用程序的URL前缀或者子域上注册一个蓝图。在URL前缀/子域上的参数变成蓝图上所有视图函数的普通视图参数(有缺省值)。
- 在一个应用程序上用不同的URL规则注册相同的蓝图。
- 提供蓝图上提供模版过滤器,静态文件、模版和其它实用功能。蓝图不需要实现应用函数或者视图函数。
- 当初始化一个Flask扩展的时候,在应用程序上注册一个蓝图。
class flask.Blueprint(name, import_name, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, root_path=None)
from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
simple_page = Blueprint('simple_page', __name__,
template_folder='templates')
@simple_page.route('/', defaults={'page': 'index'})
@simple_page.route('/<page>')
def show(page):
try:
return render_template('pages/%s.html' % page)
except TemplateNotFound:
abort(404)
访问在蓝图上挂载的URL依旧是route后的URL,与蓝图无关
可以在蓝图导向的URL上添加前缀
app.register_blueprint(simple_page, url_prefix='/pages')
注册蓝图
from flask import Flask
from yourapplication.simple_page import simple_page
app = Flask(__name__)
app.register_blueprint(simple_page)
一个蓝图应该放在一个文件夹中
# 获得蓝图的根路径
>>> simple_page.root_path
'/Users/username/TestProject/yourapplication'
# 打开蓝图的资源文件
with simple_page.open_resource('static/style.css') as f:
code = f.read()
构造蓝图的URL
from flask import url_for, Blueprint
admin = Blueprint('admin', __name__)
@admin.route('/')
def index():
pass
# 参数为蓝图变量+'.'+函数名
url_for('admin.index')
实现API Exceptions
自己实现一个简单的 Exception 类
from flask import jsonify
class InvalidUsage(Exception):
status_code = 400
def __init__(self, message, status_code=None, payload=None):
Exception.__init__(self)
self.message = message
if status_code is not None:
self.status_code = status_code
self.payload = payload
def to_dict(self):
rv = dict(self.payload or ())
rv['message'] = self.message
return rv
注册一个 Error Handler
@app.errorhandler(InvalidUsage)
def handle_invalid_usage(error):
response = jsonify(error.to_dict())
response.status_code = error.status_code
return response
在视图中使用
@app.route('/foo')
def get_foo():
raise InvalidUsage('This view is gone', status_code=410)
如果没有注册处理InvalidUsage的Error Handler,在视图中使用会引起服务器内部错误。从中可以得知,当raise一个错误的时候,其实是调用这个错误的Error Handler来处理这个错误,Flask内置了常见错误的Error Handler,出现错误会把控制权交给这些处理函数,当然自己也可以重写错误处理函数。
使用URL处理器
很多资源的URLyou一样的部分,所以不想总是显式提供。
用URL处理器加上蓝图对此很有用。
url_defaults()
url_defaults函数自动把值注入url_for函数的调用中
也就是说,当调用url_for时,会自动运行用url_defaults装饰的函数,这个函数操作url_for的参数,把值注入其中。
@app.url_defaults
def add_language_code(endpoint, values):
if 'lang_code' in values or not g.lang_code:
return
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
values['lang_code'] = g.lang_code
@app.route('/<lang_code>/')
def index():
pass
# 此时会调用add_language_code函数,endpoint就是传入url_for的第一个参数,就是处理url的函数index, values是包括url_for函数所有后面参数的dict,添加values的值,用于生成url
>>> url_for('index', lang_code='Zh')
# 假设此时g.lang_code已经存在,且值为‘Zh’
>>> url_for('index')
'/Zh'
url_map 的 is_endpoint_expecting函数用于找出往endpoint函数传递该参数是否有意义。在这个例子中,就是用来测试index函数是否需要’lang_code’这个参数。
url_value_preprocessor()
url_value_preprocessor函数在请求匹配到且能够执行基于url值的代码的时候立即执行
@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code', None)
from flask import Flask, g
app = Flask(__name__)
@app.url_defaults
def add_language_code(endpoint, values):
if 'lang_code' in values or not g.lang_code:
return
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
values['lang_code'] = g.lang_code
@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code', None)
@app.route('/<lang_code>/')
def index():
...
@app.route('/<lang_code>/about')
def about():
...
在一次处理同一个用户的TCP连接的请求中,请求的所有页面的lang_code一定都是一样的。这样写的话,只需要第一次显式输入lang_code,在处理第一次有lang_code参数的请求的时候,即通过pull_lang_code把g.lang_code设为用户需要的语言,之后用url_for动态生成的url中就可以自动注入lang_code参数,无需每次添加lang_code参数,非常方便。
应用程序工厂函数
作用:在同一个应用程序进程中有多个同一个应用程序的实例在运行
可以用于测试和拥有一个应用程序的多个实例
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
from yourapplication.views.admin import admin
from yourapplication.views.frontend import frontend
app.register_blueprint(admin)
app.register_blueprint(frontend)
return app
app 是一个实例,只有配置互相不同。
current_app
可用current_app访问现在是哪个配置的实例在处理当前的请求
from flask import current_app, Blueprint, render_template
admin = Blueprint('admin', __name__, url_prefix='/admin')
@admin.route('/')
def index():
return render_template(current_app.config['INDEX_TEMPLATE'])
Factories & Extensions
应该创建扩展对象和app工厂函数,使扩展对象没有初始化绑定在应用函数上。
db = SQLAlchemy() # 在yourapplication.model中
def create_app(config_filename):
app = Flask(__name__)
app.config.from_pyfile(config_filename)
from yourapplication.model import db
db.init_app(app)
延迟请求回调
Flask的一个设计模式是响应对象被创建并在一系列的潜在回调函数中传递,并且可以被修改或者替换。当开始处理请求的时候,可能响应函数还不存在。响应对象可能被一个视图函数或者系统中的其它组成部份创建。
所以如果想修改响应对象但是响应对象还没有创建怎么办?
方法一:把修改响应对象的部分在after-request回调函数中事件 -- 不好
方法二:把修改响应对象的部分绑定在g中,在after-request中再运行
from flask import g
# 将回调函数绑定在g对象上 -- 作为装饰器
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
# 在处理请求之后会被调用,在其中执行修改响应对象的函数
@app.after_request
def call_after_request_callbacks(response):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(response)
return response
用 Setuptools进行部署
Setuptools是一个通常被用来分发Python库和扩展的外部扩展库。
可以提供:
- 对依赖的支持:一个库或者应用可以声明一系列的它依赖的其它库,这些库可以自动被下载。
- 包的登记:setuptools用你下载的python来登记你的包。这使查询一个包给另一个包提供的信息成为可能。这个系统最好的特色在于入口点的支持使一个包可以声明一个”entry point”,另一个包就可以钩入用于扩展其他的包。
- 下载管理:pip可以为你下载其它库。
setup.py
setup代码应该卸载一个叫setup.py的文件(next to your application)中(约定俗成的)
from setuptools import setup
setup(
name='Your Application',
version='1.0',
long_description=__doc__,
packages=['yourapplication'],
include_package_data=True,
zip_safe=False,
install_requires=['Flask']
)
如果想setuptools自动寻找依赖包,可以用find_packages函数。
from setuptools import setup, find_packages
setup(
...
packages=find_packages()
)
include_package_data
include_package_data告诉setuptools去寻找一个MAINFEST.in文件,并且下载所有匹配包数据的项,可以用这个来伴随你的python模块分发静态文件和模版文件。
zip_safe
zip_safe可以用来强迫或者避免zip Archive的创建。你有可能不想你的包被作为zip文件下载,因为有些工具并不支持zip压缩,而且压缩会使debugging变得难很多。
MANIFEST.in
recursive-include yourapplication/templates *
recursive-include yourapplication/static *
同时要把include_package_data设为True才能分发静态文件和模版文件。
声明依赖
依赖作为一个list被声明在install_requires参数中,这个list中的每一项应该从PyPI中下载下来,缺省下载最近的版本,但是可以提供最小和最大的版本值。
install_requires=[
'Flask>=0.2',
'SQLAlchemy>=0.6',
'BrokenPackage>=0.7,<=1.0'
]
如果依赖的库不能从PyPI中找到或者它是一个你不想和别人分享的内部库
dependency_links=['http://example.com/yourfiles']
下载和开发
$ python setup.py install
$ python setup.py develop
用Fabric进行部署
Fabric 是 Python中和Makefiles很像的一个工具,但是它还能够在远程服务器上执行命令。和一个set up Python package和一个好的配置结合,很容易在远程服务器上部署Flask应用。
创建第一个Fabfile
Fabfile是控制Fabric执行的文件,应该命名为fabfile.py并用fab命令执行。所有定义在文件中的函数都作为fab的子命令显示。他们可以在一台或者多台主机上执行。这些注意可以在fabfile或者命令行中定义。
这个例子用来上传当前的源代码到服务器上并把它下载到一个已经存在的虚拟环境中。
from fabric.api import *
# the user to use for the remote commands
env.user = 'appuser'
# the servers where the commands are executed
env.hosts = ['server1.example.com', 'server2.example.com']
def pack():
# build the package
local('python setup.py sdist --formats=gztar', capture=False)
def deploy():
# figure out the package name and version
dist = local('python setup.py --fullname', capture=True).strip()
filename = '%s.tar.gz' % dist
# upload the package to the temporary folder on the server
put('dist/%s' % filename, '/tmp/%s' % filename)
# install the package in the application's virtualenv with pip
run('/var/www/yourapplication/env/bin/pip install /tmp/%s' % filename)
# remove the uploaded package
run('rm -r /tmp/%s' % filename)
# touch the .wsgi file to trigger a reload in mod_wsgi
run('touch /var/www/yourapplication.wsgi')
运行Fabfiles
# 部署当前的版本到远程服务器上
>>> fab pack deploy
未完待续