欢迎来到 Flask 的世界

欢迎来到 Flask 的世界

Flask: web development, one drop at a time

欢迎阅读 Flask 的文档。本文档分成几个部分,我推荐您先读 《 安装 》,然后读《 快速上手 》。《 教程 》 比快速上手文档更详细一点,该文档介绍了如何创建一个完整(尽管很小)的 Flask 应用。如果你想深入研究 Flask ,那么需要阅读《 API 》。 《 Flask 方案 》中介绍了一些常用的解决方案。

Flask 依赖两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 套件。这两个库的 使用不在本文档的范围内,欲知详情请移步:

用户指南

这部分文档是比较松散的,首先介绍了 Flask 的一些背景材料,接着专注于一步一步地 说明如何使用 Flask 进行 Web 开发。

前言

在使用 Flask 前请阅读本文。希望本文可以回答您有关 Flask 的用途和目的,以及是 否应当使用 Flask 等问题。

“微”是什么意思?

“微”并不代表整个应用只能塞在一个 Python 文件内,当然塞在单一文件内也是可以的。 “微”也不代表 Flask 功能不强。微框架中的“微”字表示 Flask 的目标是保持核心既简 单而又可扩展。 Flask 不会替你做出许多决定,比如选用何种数据库。类似的决定,如 使用何种模板引擎,是非常容易改变的。 Flask 可以变成你任何想要的东西,一切恰到 好处,由你做主。

缺省情况下, Flask 不包含数据库抽象层、表单验证或者其他已有的库可以处理的东西。 然而, Flask 通过扩展为你的应用添加这些功能,就如同这些功能是 Flask 原生的一样。 大量的扩展用以支持数据库整合、表单验证、上传处理和各种开放验证等等。Flask 可能是 “微小”的,但它已经为满足您的各种生产需要做出了充足的准备。

配置和惯例

刚起步的时候 Flask 有许多带有合理缺省值的配置值和惯例。按照惯例,模板和静态文件 存放在应用的 Python 源代码树的子目录中,名称分别为 templates 和 static 。惯 例是可以改变的,但是你大可不必改变,尤其是刚起步的时候。

Flask 可持续发展

一旦你开始使用 Flask ,你会发现有各种各样的扩展可供使用。 Flask 核心开发组会 审查扩展,并保证通过检验的扩展可以在最新版本的 Flask 中可用。

随着你的代码库日益壮大,你可以自由地决定设计目标。 Flask 会一直提供一个非常 简约而优秀的胶合层,就像 Python 语言一样。你可以自由地使用 SQLAlchemy 执行高级 模式,或者使用其他数据库工具,亦可引入非关系数据模型,甚至还可以利用用于 Python 网络接口 WSGI 的非框架工具。

Flask 包含许多可以自定义其行为的钩子。考虑到你的定制需求, Flask 的类专为继承 而打造。 如果对这一点感兴趣,请阅读 大型应用 一节。如果对 Flask 的设计原则感 兴趣,请移步 Flask 的设计思路 。

接下来请阅读 安装 、 快速上手 或者 针对高级程序员的前言 。

针对高级程序员的前言

Flask 中的本地线程对象

Flask 的设计原则之一是简单的任务不应当使用很多代码,应当可以简单地完成,但同时 又不应当把程序员限制得太死。因此,一些 Flask 的设计思路可能会让某些人觉得吃惊, 或者不可思议。例如, Flask 内部使用本地线程对象,这样就可以不用为了线程安全的 缘故在同一个请求中在函数之间传递对象。这种实现方法是非常便利的,但是当用于依赖 注入或者当尝试重用使用了与请求挂钩的值的代码时,需要一个合法的环境。 Flask 项目 对于本地线程是直言不讳的,没有一点隐藏的意思,并且在使用本地线程时在代码中进行 了标注和说明。

做网络开发时要谨慎

做网络应用开发时,安全要永记在心。

如果你开发了一个网络应用,那么可能会让用户注册并把他们的数据保存在服务器上。 用户把数据托付给了你。哪怕你的应用只是给自己用的,你也会希望数据完好无损。

不幸的是,网络应用的安全性是千疮百孔的,可以攻击的方法太多了。 Flask 可以防御 现代 Web 应用最常见的安全攻击:跨站代码攻击( XSS )。 Flask 和 下层的 Jinja2 模板引擎会保护你免受这种攻击,除非故意把不安全的 HTML 代码放进来。但是安全攻击 的方法依然还有很多。

这里警示你:在 Web 开发过程中要时刻注意安全问题。一些安全问题远比想象的要复杂 得多。我们有时会低估程序的弱点,直到被一个聪明人利用这个弱点来攻击我们的程序。 不要以为你的应用不重要,还不足以别人来攻击。没准是自动化机器人用垃圾邮件或恶意 软件链接等东西来填满你宝贵的数据库。

Flask 与其他框架相同,你在开发时必须小心谨慎。

Python 3 的情况

目前, Python 社区正处在改进库的过程中,以便于加强对 Python 语言的新迭代的 支持。虽然现在情况已经有很大改善,但是还是存在一些问题使用户难以下决心现在就 转向 Python 3 。部分原因是 Python 语言中的变动长时间未经审核,还有部分原因是 我们还没有想好底层 API 针对 Python 3 中 unicode 处理方式的变化应该如何改动。

我们强烈建议你在开发过程中使用 Python 2.7 ,同时打开 Python 3 警告。如果你计划 在近期升级到 Python 3 ,那么强烈推荐阅读 如何编写向前兼容的 Python 代码 。

如果你一定要使用 Python 3 ,那么请先阅读 python3-support 。

接下来,请阅读 安装 或 快速上手 。

安装

Flask 依赖两个外部库: Werkzeug 和 Jinja2 。Werkzeug 是一个 WSGI 套件。 WSGI 是 Web 应用与 多种服务器之间的标准 Python 接口,即用于开发,也用于部署。 Jinja2 是用于渲染 模板的。

那么如何快速在你的计算机上装好所有东西?本节会介绍多种方法,但是最强力的方法是 使用 virtualenv 。因此,我们先说 virtualenv 。

无论使用哪种方法安装,你都会需要 Python 2.6 或更高版本。因此请确保安装了最新的 Python 2.x 版本。在 Python 3 下使用 Flask 请参阅 Python 3 支持 。

virtualenv

如果可以使用 shell ,那么可能 Virtualenv 是你在开发环境和生产环境都想使用的 东西。

virtualenv 有什么用?如果你象我一样热爱 Python ,那么除了基于 Flask 的项目外 还会有其他项目用到 Python 。当项目越来越多时就会面对使用不同版本的 Python 的 问题,或者至少会遇到使用不同版本的 Python 库的问题。摆在你面前的是:库常常不能 向后兼容,更不幸的是任何成熟的应用都不是零依赖。如果两个项目依赖出现冲突, 怎么办?

Virtualenv 就是救星!它的基本原理是为每个项目安装一套 Python ,多套 Python 并存。但它不是真正地安装多套独立的 Python 拷贝,而是使用了一种巧妙的方法让不同 的项目处于各自独立的环境中。让我们来看看 virtualenv 是如何运行的!

如果你使用 Mac OS X 或 Linux ,那么可以使用下面两条命令中任意一条:

$ sudo easy_install virtualenv

或更高级的:

$ sudo pip install virtualenv

上述命令中的任意一条就可以安装好 virtualenv 。也可以使用软件包管理器,在 Ubuntu 系统中可以试试:

$ sudo apt-get install python-virtualenv

如果你使用 Windows 且无法使用 easy_install ,那么你必须先安装它,安装方法详见 《 在 Windows 系统中使用 pip 和 distribute 》。安装好以后运行上述命令,但是要去掉sudo 前缀。

安装完 virtualenv ,打开一个 shell ,创建自己的环境。我通常创建一个包含 venv 文件夹的项目文件夹:

$ mkdir myproject
$ cd myproject
$ virtualenv venv
New python executable in env/bin/python
Installing setuptools............done.

现在,每次需要使用项目时,必须先激活相应的环境。在 OS X 和 Linux 系统中运行:

$ . venv/bin/activate

Windows 用户请运行下面的命令:

$ venv\scripts\activate

殊途同归,你现在进入你的 virtualenv (注意查看你的 shell 提示符已经改变了)。

现在可以开始在你的 virtualenv 中安装 Flask 了:

$ pip install Flask

几秒钟后就安装好了。

系统全局安装

虽然这样做是可行的,虽然我不推荐。只需要以 root 权限运行 pip 就可以了:

$ sudo pip install Flask

( Windows 系统中请在管理员 shell 中运行,去掉 sudo )。

生活在边缘

如果你想要使用最新版的 Flask ,那么有两种方法:要么使用 pip 安装开发版本, 要么使用 git 检索。无论哪种方法,都推荐使用 virtualenv 。

在一个新的 virtualenv 中获得 git 检索,并在开发模式下运行:

$ git clone http://github.com/mitsuhiko/flask.git
Initialized empty Git repository in ~/dev/flask/.git/
$ cd flask
$ virtualenv venv --distribute
New python executable in venv/bin/python
Installing distribute............done.
$ . venv/bin/activate
$ python setup.py develop
...
Finished processing dependencies for Flask

上述操作会安装相关依赖库并在 virtualenv 中激活 git 头作为当前版本。然后只要 使用git pull origin 命令就可以安装最新版本的 Flask 了。

如果不使用 git ,那么可以这样获得开发版本:

$ mkdir flask
$ cd flask
$ virtualenv venv --distribute
$ . venv/bin/activate
New python executable in venv/bin/python
Installing distribute............done.
$ pip install Flask==dev
...
Finished processing dependencies for Flask==dev

在 Windows 系统中使用 pip 和 distribute

在 Windows 系统中,安装 easy_install 稍微有点麻烦,但还是比较简单的。最简单的 方法是下载并运行 ez_setup.py 文件。最简单的运行文件的方法是打开下载文件所在 文件夹,双击这个文件。

接下来,通过把 Python 代码所在文件夹添加到 PATH 环境变量的方法把 easy_install 命令和其他 Python 代码添加到命令搜索目录。操作方法:用鼠标右键 点击桌面上或者开始菜单中的“我的电脑”图标,在弹出的菜单中点击“属性”。然后 点击“高级系统设置”(如果是 Windows XP ,则点击“高级”分页)。接着点击“环境 变量”按钮,双击“系统变量”一节中的“ Path ”变量。这样就可以添加 Python 代码 所在的文件夹了。 注意,与已经存在的值之间要用分号分隔。假设你在缺省路径安装了 Python 2.7 ,那么就应该添加如下内容:

;C:\Python27\Scripts

至此安装完成。要检验安装是否正确可以打开命令提示符,并运行 easy_install 命令。如果你使用 Windows Vista 或 Windows 7 ,并打开了权限控制,会提示你需要 管理员权限。

至此,你安装好了 easy_install ,接下来就可以用它来安装 pip 了:

> easy_install pip

快速上手

等久了吧?本文会给你好好介绍如何上手 Flask 。这里假定你已经安装好了 Flask , 否则请先阅读《 安装 》。

一个最小的应用

一个最小的 Flask 应用如下:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

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

把它保存为 hello.py 或其他类似名称并用你的 Python 解释器运行这个文件。请不要 使用 flask.py 作为应用名称,这会与 Flask 本身发生冲突。

$ python hello.py
 * Running on http://127.0.0.1:5000/

现在,在浏览器中打开 http://127.0.0.1:5000/ ,就 可以看到问候页面了。

那么,这些代码是什么意思呢?

  1. 首先我们导入了 Flask 类。这个类的实例将会成为我们的 WSGI 应用。
  2. 接着我们创建了这个类的实例。第一个参数是应用模块或者包的名称。如果你使用一个 单一模块(就像本例),那么应当使用 __name__ ,因为名称会根据这个模块是按 应用方式使用还是作为一个模块导入而发生变化(可能是 '__main__' ,也可能是 实际导入的名称)。这个参数是必需的,这样 Flask 就可以知道在哪里找到模板和 静态文件等东西。更多内容详见 Flask 文档。
  3. 然后我们使用 route() 装饰器来告诉 Flask 触发函数的 URL 。
  4. 函数名称可用于生成相关联的 URL ,并返回需要在用户浏览器中显示的信息。
  5. 最后,使用 run() 函数来运行本地服务器和我们的应用。 if __name__ =='__main__': 确保服务器只会在使用 Python 解释器运行代码的 情况下运行,而不会在作为模块导入时运行。

按 control-C 可以停止服务器。

外部可见的服务器。

运行服务器后,会发现只有你自己的电脑可以使用服务,而网络中的其他电脑却不行。 缺省设置就是这样的,因为在调试模式下该应用的用户可以执行你电脑中的任意 Python 代码。

如果你关闭了 调试 或信任你网络中的用户,那么可以让服务器被公开访问。只要像 这样改变 run() 方法的调用:

app.run(host='0.0.0.0')

这行代码告诉你的操作系统监听一个公开的 IP 。

调试模式

虽然 run() 方法可以方便地启动一个本地开发服务器,但是每次 修改应用之后都需要手动重启服务器。这样不是很方便, Flask 可以做得更好。如果你 打开调试模式,那么服务器会在修改应用之后自动重启,并且当应用出错时还会提供一个 有用的调试器。

打开调试模式有两种方法,一种是在应用对象上设置标志:

app.debug = True
app.run()

另一种是作为参数传递给 run 方法:

app.run(debug=True)

两种方法的效果相同。

注意

虽然交互调试器不能在分布环境下工作(这使得它基本不可能用于生产环境),但是 它允许执行任意代码,这样会成为一个重大安全隐患。因此, 绝对不能在生产环境 中使用调试器 。

运行的调试器的截图:

screenshot of debugger in action

想使用其他调试器?请参阅 使用调试器 。

路由

现代 web 应用都使用漂亮的 URL ,有助于人们记忆,对于使用网速较慢的移动设备尤其 有利。如果用户可以不通过点击首页而直达所需要的页面,那么这个网页会更得到用户的 青睐,提高回头率。

如前文所述, route() 装饰器用于把一个函数绑定到一个 URL 。 下面是一些基本的例子:

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello World'

但是能做的不仅仅是这些!你可以动态变化 URL 的某些部分,还可以为一个函数指定多个 规则。

变量规则

通过把 URL 的一部分标记为 <variable_name> 就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 <converter:variable_name> ,可以 选择性的加上一个转换器,为变量指定规则。请看下面的例子:

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

现有的转换器有:

int 接受整数
float 接受浮点数
path 和缺省情况相同,但也接受斜杠

唯一的 URL / 重定向行为

Flask 的 URL 规则都是基于 Werkzeug 的路由模块的。其背后的理念是保证漂亮的 外观和唯一的 URL 。这个理念来自于 Apache 和更早期的服务器。

假设有如下两条规则:

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

它们看上去很相近,不同之处在于 URL 定义 中尾部的斜杠。第一个例子中 projects 的 URL 是中规中举的,尾部有一个斜杠,看起来就如同一个文件夹。访问 一个没有斜杠结尾的 URL 时 Flask 会自动进行重定向,帮你在尾部加上一个斜杠。

但是在第二个例子中, URL 没有尾部斜杠,因此其行为表现与一个文件类似。如果 访问这个 URL 时添加了尾部斜杠就会得到一个 404 错误。

为什么这样做?因为这样可以在省略末尾斜杠时仍能继续相关的 URL 。这种重定向 行为与 Apache 和其他服务器一致。同时, URL 仍保持唯一,帮助搜索引擎不重复 索引同一页面。

URL 构建

如果可以匹配 URL ,那么 Flask 也可以生成 URL 吗?当然可以。 url_for() 函数就是用于构建指定函数的 URL 的。它把函数名称作为 第一个参数,其余参数对应 URL 中的变量。未知变量将添加到 URL 中作为查询参数。 例如:

>>> from flask import Flask, url_for
>>> app = Flask(__name__)
>>> @app.route('/')
... def index(): pass
...
>>> @app.route('/login')
... def login(): pass
...
>>> @app.route('/user/<username>')
... def profile(username): pass
...
>>> with app.test_request_context():
...  print url_for('index')
...  print url_for('login')
...  print url_for('login', next='/')
...  print url_for('profile', username='John Doe')
...
/
/login
/login?next=/
/user/John%20Doe

(例子中还使用下文要讲到的 test_request_context() 方法。这个 方法的作用是告诉 Flask 我们正在处理一个请求,而实际上也许我们正处在交互 Python shell 之中,并没有真正的请求。详见下面的 本地环境 )。

为什么不在把 URL 写死在模板中,反而要动态构建?有三个很好的理由:

  1. 反向解析通常比硬编码 URL 更直观。同时,更重要的是你可以只在一个地方改变 URL ,而不用到处乱找。
  2. URL 创建会为你处理特殊字符的转义和 Unicode 数据,不用你操心。
  3. 如果你的应用是放在 URL 根路径之外的地方(如在 /myapplication 中,不在 /中), url_for() 会为你妥善处理。
HTTP 方法

HTTP ( web 应用使用的协议)) 协议中有访问 URL 的不同方法。缺省情况下,一个路由 只回应 GET 请求,但是可以通过 methods 参数使用不同方法。例如:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

如果当前使用的是 GET 方法,会自动添加 HEAD ,你不必亲自操刀。同时还会确保HEAD 请求按照 HTTP RFC (说明 HTTP 协议的文档)的要求来处理,因此你可以 完全忽略这部分 HTTP 规范。与 Flask 0.6 一样, OPTIONS 自动为你处理好。

完全不懂 HTTP 方法?没关系,这里给你速成培训一下:

HTTP 方法(通常也被称为“动作”)告诉服务器一个页面请求要  什么。以下是常见 的方法:

GET
浏览器告诉服务器只要 得到 页面上的信息并发送这些信息。这可能是最常见的 方法。
HEAD
浏览器告诉服务器想要得到信息,但是只要得到 信息头 就行了,页面内容不要。 一个应用应该像接受到一个 GET 请求一样运行,但是不传递实际的内容。在 Flask 中,你根本不必理会这个,下层的 Werkzeug 库会为你处理好。
POST
浏览器告诉服务器想要向 URL 发表 一些新的信息,服务器必须确保数据被保存好 且只保存了一次。 HTML 表单实际上就是使用这个访求向服务器传送数据的。
PUT
与 POST 方法类似,不同的是服务器可能触发多次储存过程而把旧的值覆盖掉。你 可能会问这样做有什么用?这样做是有原因的。假设在传输过程中连接丢失的情况 下,一个处于浏览器和服务器之间的系统可以在不中断的情况下安全地接收第二次 请求。在这种情况下,使用 POST 方法就无法做到了,因为它只被触发一次。
DELETE
删除给定位置的信息。
OPTIONS
为客户端提供一个查询 URL 支持哪些方法的捷径。从 Flask 0.6 开始,自动为你 实现了这个方法。

有趣的是在 HTML4 和 XHTML1 中,表单只能使用 GET 和 POST 方法。但是 JavaScript 和未来的 HTML 标准中可以使用其他的方法。此外, HTTP 近来已经变得相当 流行,浏览器不再只是唯一使用 HTTP 的客户端。比如许多版本控制系统也使用 HTTP 。

静态文件

动态的 web 应用也需要静态文件,一般是 CSS 和 JavaScript 文件。理想情况下你的 服务器已经配置好了为你的提供静态文件的服务。在开发过程中, Flask 也能做好这个 工作。只要在你的包或模块旁边创建一个名为 static 的文件夹就行了。静态文件位于 应用的 /static 中。

使用选定的 'static' 端点就可以生成相应的 URL 。:

url_for('static', filename='style.css')

这个静态文件在文件系统中的位置应该是 static/style.css 。

渲染模板

在 Python 内部生成 HTML 不好玩,且相当笨拙。因为你必须自己负责 HTML 转义,以 确保应用的安全。因此, Flask 自动为你配置的 Jinja2 模板引擎。

使用 render_template() 方法可以渲染模板,你只要提供模板名称和需要 作为参数传递给模板的变量就行了。下面是一个简单的模板渲染例子:

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

Flask 会在 templates 文件夹内寻找模板。因此,如果你的应用是一个模块,那么模板 文件夹应该在模块旁边;如果是一个包,那么就应该在包里面:

情形 1: 一个模块:

/application.py
/templates
    /hello.html

情形 2: 一个包:

/application
    /__init__.py
    /templates
        /hello.html

你可以充分使用 Jinja2 模板引擎的威力。更多内容,详见官方 Jinja2 模板文档 。

模板举例:

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello World!</h1>
{% endif %}

在模板内部你也可以访问 request 、session 和 g [1] 对象,以及get_flashed_messages() 函数。

模板在继承使用的情况下尤其有用,其工作原理 模板继承 方案 文档。简单的说,模板继承可以使每个页面的特定元素(如页头,导航,页尾)保持 一致。

自动转义默认开启。因此,如果 name 包含 HTML ,那么会被自动转义。如果你可以 信任某个变量,且知道它是安全的 HTML (例如变量来自一个把 wiki 标记转换为 HTML 的模块),那么可以使用 Markup 类把它标记为安全的。否则请在模板 中使用 |safe 过滤器。更多例子参见 Jinja 2 文档。

下面简单介绍一下 Markup 类的工作方式:

>>> from flask import Markup
>>> Markup('<strong>Hello %s!</strong>') % '<blink>hacker</blink>'
Markup(u'<strong>Hello &lt;blink&gt;hacker&lt;/blink&gt;!</strong>')
>>> Markup.escape('<blink>hacker</blink>')
Markup(u'&lt;blink&gt;hacker&lt;/blink&gt;')
>>> Markup('<em>Marked up</em> &raquo; HTML').striptags()
u'Marked up \xbb HTML'

Changed in version 0.5: 自动转义不再为所有模板开启,只为扩展名为 .html 、 .htm 、.xml 和 .xhtml 开启。从字符串载入的模板将关闭自动转义。

[1] 不理解什么是 g 对象?它是某个可以根据需要储存信息的 东西。更多信息参见 g 对象的文档和 在 Flask 中使用 SQLite 3 文档。

操作请求数据

对于 web 应用来说对客户端向服务器发送的数据作出响应很重要。在 Flask 中由全局 对象 request 来提供请求信息。如果你有一些 Python 基础,那么可能 会奇怪:既然这个对象是全局的,怎么还能保持线程安全?答案是本地环境:

本地环境

内部信息

如果你想了解其工作原理和如何测试,请阅读本节,否则可以跳过本节。

某些对象在 Flask 中是全局对象,但是不是通常意义下的全局对象。这些对象实际上是 特定环境下本地对象的代理。真拗口!但还是很容易理解的。

设想现在处于处理线程的环境中。一个请求进来了,服务器决定生成一个新线程(或者 叫其他什么名称的东西,这个下层的东西能够处理包括线程在内的并发系统)。当 Flask 开始其内部请求处理时会把当前线程作为活动环境,并把当前应用和 WSGI 环境 绑定到这个环境(线程)。它以一种聪明的方式使得一个应用可以在不中断的情况下 调用另一个应用。

这对你有什么用?基本上你可以完全不必理会。这个只有在做单元测试时才有用。在测试 时会遇到由于没有请求对象而导致依赖于请求的代码会突然崩溃的情况。对策是自己创建 一个请求对象并绑定到环境。最简单的单元测试解决方案是使用test_request_context() 环境管理器。通过使用 with 语句可以 绑定一个测试请求,以便于交互。例如:

from flask import request

with app.test_request_context('/hello', method='POST'):
    # now you can do something with the request until the
    # end of the with block, such as basic assertions:
    assert request.path == '/hello'
    assert request.method == 'POST'

另一种方式是把整个 WSGI 环境传递给 request_context() 方法:

from flask import request

with app.request_context(environ):
    assert request.method == 'POST'
请求对象

请求对象在 API 一节中有详细说明这里不细谈(参见 request )。 这里简略地谈一下最常见的操作。首先,你必须从 flask 模块导入请求对象:

from flask import request

通过使用 method 属性可以操作当前请求方法,通过使用 form 属性处理表单数据。以下是使用两个属性的例子:

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # 如果请求访求是 GET 或验证未通过就会执行下面的代码
    return render_template('login.html', error=error)

当 form 属性中不存在这个键时会发生什么?会引发一个 KeyError 。如果你不 像捕捉一个标准错误一样捕捉 KeyError ,那么会显示一个 HTTP 400 Bad Request 错误页面。因此,多数情况下你不必处理这个问题。

要操作 URL (如 ?key=value )中提交的参数可以使用 args 属性:

searchword = request.args.get('key', '')

用户可能会改变 URL 导致出现一个 400 请求出错页面,这样降低了用户友好度。因此, 我们推荐使用 get 或通过捕捉 KeyError 来访问 URL 参数。

完整的请求对象方法和属性参见 request 文档。

文件上传

用 Flask 处理文件上传很容易,只要确保不要忘记在你的 HTML 表单中设置enctype="multipart/form-data" 属性就可以了。否则浏览器将不会传送你的文件。

已上传的文件被储存在内存或文件系统的临时位置。你可以通过请求对象 files 属性来访问上传的文件。每个上传的文件都储存在这个 字典型属性中。这个属性基本和标准 Python file 对象一样,另外多出一个 用于把上传文件保存到服务器的文件系统中的save() 方法。下例展示其如何运作:

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

如果想要知道文件上传之前其在客户端系统中的名称,可以使用 filename 属性。但是请牢记这个值是 可以伪造的,永远不要信任这个值。如果想要把客户端的文件名作为服务器上的文件名, 可以通过 Werkzeug 提供的 secure_filename() 函数:

from flask import request
from werkzeug import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))
    ...

更好的例子参见 上传文件 方案。

Cookies

要访问 cookies ,可以使用 cookies 属性。可以使用请求对象 的 set_cookie 方法来设置 cookies 。请求对象的 cookies 属性是一个包含了客户端传输的所有 cookies 的字典。 在 Flask 中,如果能够使用 会话 ,那么就不要直接使用 cookies ,因为 会话比较安全一些。

读取 cookies:

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # 使用 cookies.get(key) 来代替 cookies[key] ,
    # 以避免当 cookie 不存在时引发 KeyError 。

储存 cookies:

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

注意, cookies 设置在响应对象上。通常只是从视图函数返回字符串, Flask 会把它们 转换为响应对象。如果你想显式地转换,那么可以使用 make_response() 函数,然后再修改它。

使用 延迟的请求回调 方案可以在没有响应对象的情况下设置一个 cookie 。

同时可以参见 关于响应 。

重定向和错误

使用 redirect() 函数可以重定向。使用 abort() 可以更早 退出请求,并返回错误代码:

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

上例实际上是没有意义的,它让一个用户从索引页重定向到一个无法访问的页面(401 表示禁止访问)。但是上例可以说明重定向和出错跳出是如何工作的。

缺省情况下每种出错代码都会对应显示一个黑白的出错页面。使用 errorhandler() 装饰器可以定制出错页面:

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

注意 render_template() 后面的 404 ,这表示页面对就的出错代码是 404 ,即页面不存在。缺省情况下 200 表示一切正常。

关于响应

视图函数的返回值会自动转换为一个响应对象。如果返回值是一个字符串,那么会被转换 为一个包含作为响应体的字符串、一个 200 OK 出错代码 和一个 text/html MIME 类型的响应对象。以下是转换的规则:

  1. 如果视图要返回的是一个响应对象,那么就直接返回它。
  2. 如果要返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的 响应对象。
  3. 如果要返回的是一个元组,那么元组中的项目可以提供额外的信息。元组中必须至少 包含一个项目,且项目应当由 (response, status, headers) 组成。 status 的值会重载状态代码, headers 是一个由额外头部值组成的列表或字典。
  4. 如果以上都不是,那么 Flask 会假定返回值是一个有效的 WSGI 应用并把它转换为 一个响应对象。

如果想要在视图内部掌控响应对象的结果,那么可以使用 make_response() 函数。

设想有如下视图:

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

可以使用 make_response() 包裹返回表达式,获得响应对象,并对该对象 进行修改,然后再返回:

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

会话

除了请求对象之外还有一种称为 session 的对象,允许你在不同请求 之间储存信息。这个对象相当于用密钥签名加密的 cookie ,即用户可以查看你的 cookie ,但是如果没有密钥就无法修改它。

使用会话之前你必须设置一个密钥。举例说明:

from flask import Flask, session, redirect, url_for, escape, request

app = Flask(__name__)

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form action="" method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # 如果会话中有用户名就删除它。
    session.pop('username', None)
    return redirect(url_for('index'))

# 设置密钥,复杂一点:
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

这里用到的 escape() 是用来转义的。如果不使用模板引擎就可以像上例 一样使用这个函数来转义。

如何生成一个好的密钥

生成随机数的关键在于一个好的随机种子,因此一个好的密钥应当有足够的随机性。 你的操作系统可以使用一个随机生成器来生成一个好的随机种子:

>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'

只要复制这个随机种子到你的代码中就行了。

基于 cookie 的会话的说明: Flask 会把会话对象中的值储存在 cookie 中。在打开 cookie 的情况下,如果你访问会话对象中没有的值,那么会得到模糊的错误信息:请检查 页面 cookie 的大小是否与网络浏览器所支持的大小一致。

消息闪现

一个好的应用和用户接口都有良好的反馈,否则到后来用户就会讨厌这个应用。 Flask 通过闪现系统来提供了一个易用的反馈方式。闪现系统的基本工作原理是在请求结束时 记录一个消息,提供且只提供给下一个请求使用。通常通过一个布局模板来展现闪现的 消息。

flash() 用于闪现一个消息。在模板中,使用 get_flashed_messages() 来操作消息。完整的例子参见 消息闪现 。

日志

New in version 0.3.

有时候可能会遇到数据出错需要纠正的情况。例如因为用户篡改了数据或客户端代码出错 而导致一个客户端代码向服务器发送了明显错误的 HTTP 请求。多数时候在类似情况下 返回 400 Bad Request 就没事了,但也有不会返回的时候,而代码还得继续运行 下去。

这时候就需要使用日志来记录这些不正常的东西了。自从 Flask 0.3 后就已经为你配置好 了一个日志工具。

以下是一些日志调用示例:

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

logger 是一个标准的 Python Logger 类, 更多信息详见官方的 logging 文档 。

集成 WSGI 中间件

如果想要在应用中添加一个 WSGI 中间件,那么可以包装内部的 WSGI 应用。假设为了 解决 lighttpd 的错误,你要使用一个来自 Werkzeug 包的中间件,那么可以这样做:

from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)

部署到一个网络服务器

准备好发布你的新 Flask 应用了吗?作为本文的一个圆满结尾,你可以立即把应用部署到 一个主机上。下面介绍的是如何把小项目部署到免费主机上。

其他可以部署 Flask 应用的地方:

如果拥有自己的独立主机,参见《 部署方式 》。

教程

想要用 Python 和 Flask 开发应用吗?让我们来边看例子边学习。本教程中我们将会创建 一个微博应用。这个应用只支持单一用户,只能创建文本条目,也没有不能订阅和评论, 但是已经具备一个初学者需要掌握的功能。这个应用只需要使用 Flask 和 SQLite , SQLite 是 Python 自带的。

如果你想要事先下载完整的源代码或者用于比较,请查看 示例源代码 。

Flaskr 介绍

我们把教程中的博客应用称为 flaskr ,当然你也可以随便取一个没有 web-2.0 气息的 名字 ;) 它的基本功能如下:

  1. 让用户可以根据配置文件中的信息登录和注销。只支持一个用户。
  2. 用户登录以后可以添加新的博客条目。条目由文本标题和支持 HTML 代码的内容组成。 因为我们信任用户,所以不对内容中的 HTML 进行净化处理。
  3. 页面以倒序(新的在上面)显示所有条目。并且用户登录以后可以在这个页面添加新 的条目。

我们直接在应用中使用 SQLite3 ,因为在这种规模的应用中 SQlite3 已经够用了。如果 是大型应用,那么就有必要使用能够好的处理数据库连接的 SQLAlchemy 了,它可以 同时对应多种数据库,并做其他更多的事情。如果你的数据更适合使用 NoSQL 数据库, 那么也可以考虑使用某种流行的 NoSQL 数据库。

这是教程应用完工后的截图:

screenshot of the final application

下面请阅读 步骤 0 :创建文件夹 。

步骤 0 :创建文件夹

在开始之前需要为应用创建下列文件夹:

/flaskr
    /static
    /templates

flaskr 文件夹不是一个 Python 包,只是一个用来存放我们文件的地方。我们将把以后 要用到的数据库模式和主模块放在这个文件夹中。 static 文件夹中的文件是用于供 应用用户通过 HTTP 访问的文件,主要是 CSS 和 javascript 文件。 Flask 将会在 templates 文件夹中搜索 Jinja2 模板,所有在教程中的模板都放在 templates 文件夹中。

下面请阅读 步骤 1 :数据库模式 。

步骤 1 :数据库模式

首先我们要创建数据库模式。本应用只需要使用一张表,并且由于我们使用 SQLite , 所以这一步非常简单。把以下内容保存为 schema.sql 文件并放在我们上一步创建的 flaskr文件夹中就行了:

drop table if exists entries;
create table entries (
  id integer primary key autoincrement,
  title text not null,
  text text not null
);

这个模式只有一张名为 entries 的表,表中的字段为 id 、 title 和 text 。 id 是主键,是自增整数型字段,另外两个字段是非空的字符串型字段。

下面请阅读 步骤 2 :应用构建代码 。

步骤 2 :应用构建代码

现在我们已经准备好了数据库模式了,下面来创建应用模块。我们把模块命名为flaskr.py ,并放在 flaskr 文件夹中。为了方便初学者学习,我们把库的导入与 相关配置放在了一起。对于小型应用来说,可以把配置直接放在模块中。但是更加清晰的 方案是把配置放在一个独立的 .ini 或 .py 文件中,并在模块中导入配置的值。

在 flaskr.py 文件中:

# all the imports
import sqlite3
from flask import Flask, request, session, g, redirect, url_for, \
     abort, render_template, flash

# configuration
DATABASE = '/tmp/flaskr.db'
DEBUG = True
SECRET_KEY = 'development key'
USERNAME = 'admin'
PASSWORD = 'default'

接着创建真正的应用,并用同一文件中的配置来初始化,在 flaskr.py 文件中:

# create our little application :)
app = Flask(__name__)
app.config.from_object(__name__)

from_object() 会查看给定的对象(如果该对象是一个字符串就会 直接导入它),搜索对象中所有变量名均为大字字母的变量。在我们的应用中,已经将配 置写在前面了。你可以把这些配置放到一个独立的文件中。

通常,从一个配置文件中导入配置是比较好的做法,我们使用 from_envvar() 来完成这个工作,把上面的 from_object() 一行替换为:

app.config.from_envvar('FLASKR_SETTINGS', silent=True)

这样做就可以设置一个 FLASKR_SETTINGS 的环境变量来指定一个配置文件,并 根据该文件来重载缺省的配置。 silent 开关的作用是告诉 Flask 如果没有这个环境变量 不要报错。

secret_key (密钥)用于保持客户端会话安全,请谨慎地选择密钥,并尽可能的使它 复杂而且不容易被猜到。 DEBUG 标志用于开关交互调试器。因为调试模式允许用户执行 服务器上的代码,所以 永远不要在生产环境中打开调试模式 !

我们还添加了一个方便连接指定数据库的方法。这个方法可以用于在请求时打开连接,也 可以用于 Python 交互终端或代码中。以后会派上用场。

def connect_db():
    return sqlite3.connect(app.config['DATABASE'])

最后,在文件末尾添加以单机方式启动服务器的代码:

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

到此为止,我们可以顺利运行应用了。输入以下命令开始运行:

python flaskr.py

你会看到服务器已经运行的信息,其中包含应用访问地址。

因为我们还没创建视图,所以当你在浏览器中访问这个地址时,会得到一个 404 页面未 找到错误。很快我们就会谈到视图,但我们先要弄好数据库。

外部可见的服务器

想让你的服务器被公开访问?详见 外部可见的服务器 。

下面请阅读 步骤 3 :创建数据库 。

步骤 3 :创建数据库

如前所述 Flaskr 是一个数据库驱动的应用,更准确地说是一个关系型数据库驱动的 应用。关系型数据库需要一个数据库模式来定义如何储存信息,因此必须在第一次运行 服务器前创建数据库模式。

使用 sqlite3 命令通过管道导入 schema.sql 创建模式:

sqlite3 /tmp/flaskr.db < schema.sql

上述方法的不足之处是需要额外的 sqlite3 命令,但这个命令不是每个系统都有的。而且还必须提供数据库的路径,容易出错。因此更好的方法是在应用中添加一个数据库初始化 函数。

添加的方法是:首先从 contextlib 库中导入 contextlib.closing() 函数,即在flaskr.py 文件的导入部分添加如下内容:

from contextlib import closing

接下来,可以创建一个用来初始化数据库的 init_db 函数,其中我们使用了先前创建的connect_db 函数。把这个初始化函数放在 flaskr.py 文件中的`connect_db` 函数 下面:

def init_db():
    with closing(connect_db()) as db:
        with app.open_resource('schema.sql', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

closing() 帮助函数允许我们在 with 代码块保持数据库连接 打开。应用对象的open_resource() 方法支持也支持这个功能, 可以在 with 代码块中直接使用。这个函数打开一个位于来源位置(你的 flaskr 文件夹)的文件并允许你读取文件的内容。这里我们用于在数据库连接上执行 代码。

当我们连接到数据库时,我们得到一个提供指针的连接对象(本例中的 db )。这个 指针有一个方法可以执行完整的代码。最后我们提供要做的修改。 SQLite 3 和其他 事务型数据库只有在显式提交时才会真正提交。

现在可以创建数据库了。打开 Python shell ,导入,调用函数:

>>> from flaskr import init_db
>>> init_db()

故障处理

如果出现表无法找到的问题,请检查是否写错了函数名称(应该是 init_db ), 是否写错了表名(例如单数复数错误)。

下面请阅读 步骤 4 :请求数据库连接 。

步骤 4 :请求数据库连接

现在我们已经学会如何打开并在代码中使用数据库连接,但是如何优雅地在请求时使用它 呢?我们会在每一个函数中用到数据库连接,因此有必要在请求之前初始化连接,并在 请求之后关闭连接。

Flask 中可以使用 before_request() 、 after_request() 和teardown_request() 装饰器达到这个目的:

@app.before_request
def before_request():
    g.db = connect_db()

@app.teardown_request
def teardown_request(exception):
    db = getattr(g, 'db', None)
    if db is not None:
        db.close()

使用 before_request() 装饰的函数会在请求之前调用,且不传递 参数。使用after_request() 装饰的函数会在请求之后调用,且 传递发送给客户端响应对象。它们必须传递响应对象,所以在出错的情况下就不会执行。 因此我们就要用到teardown_request() 装饰器了。这个装饰器下 的函数在响应对象构建后被调用。它们不允许修改请求,并且它们的返回值被忽略。如果 请求过程中出错,那么这个错误会传递给每个函数;否则传递 None 。

我们把数据库连接保存在 Flask 提供的特殊的 g 对象中。这个对象与 每一个请求是一一对应的,并且只在函数内部有效。不要在其它对象中储存类似信息, 因为在多线程环境下无效。这个特殊的 g 对象会在后台神奇的工作,保 证系统正常运行。

若想更好地处理这种资源,请参阅 在 Flask 中使用 SQLite 3 。

下面请阅读 步骤 5 :视图函数.

Hint

我该把这些代码放在哪里?

如果你按教程一步一步读下来,那么可能会疑惑应该把这个步骤和以后的代码放在哪 里?比较有条理的做法是把这些模块级别的函数放在一起,并把新的 before_request和 teardown_request 函数放在前文的 init_db 函数 下面(即按照教程的顺序放置)。

如果你已经晕头转向了,那么你可以参考一下 示例源代码 。在 Flask 中,你可以 把应用的所有代码都放在同一个 Python 模块中。但是你没有必要这样做,尤其是当你 的应用变大了 的时候,更不应当这样。

步骤 5 :视图函数

现在数据库连接弄好了,接着开始写视图函数。我们共需要四个视图函数:

显示条目

这个视图显示所有数据库中的条目。它绑定应用的根地址,并从数据库中读取 title 和 text 字段。 id 最大的记录(最新的条目)在最上面。从指针返回的记录集是一个包含 select 语句查询结果的元组。对于教程应用这样的小应用,做到这样就已经够好了。但是 你可能想要把结果转换为字典,具体做法参见 简化查询 中的例子。

这个视图会把条目作为字典传递给 show_entries.html 模板,并返回渲染结果:

@app.route('/')
def show_entries():
    cur = g.db.execute('select title, text from entries order by id desc')
    entries = [dict(title=row[0], text=row[1]) for row in cur.fetchall()]
    return render_template('show_entries.html', entries=entries)
添加一个新条目

这个视图可以让一个登录后的用户添加一个新条目。本视图只响应 POST 请求,真正的 表单显示在 show_entries 页面中。如果一切顺利,我们会 flash() 一个消息给下一个请求并重定向回到 show_entries 页面:

@app.route('/add', methods=['POST'])
def add_entry():
    if not session.get('logged_in'):
        abort(401)
    g.db.execute('insert into entries (title, text) values (?, ?)',
                 [request.form['title'], request.form['text']])
    g.db.commit()
    flash('New entry was successfully posted')
    return redirect(url_for('show_entries'))

注意,我们在本视图中检查了用户是否已经登录(即检查会话中是否有 logged_in 键,且 对应的值是否为 True )。

安全性建议

请像示例代码一样确保在构建 SQL 语句时使用问号。否则当你使用字符串构建 SQL 时 容易遭到 SQL 注入攻击。更多内容参见 在 Flask 中使用 SQLite 3 。

登录和注销

这些函数用于用户登录和注销。登录视图根据配置中的用户名和密码验证用户并在会话中 设置 logged_in 键值。如果用户通过验证,键值设为 True ,那么用户会被重定向到show_entries 页面。另外闪现一个信息,告诉用户已登录成功。如果出现错误,模板会 提示错误信息,并让用户重新登录:

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != app.config['USERNAME']:
            error = 'Invalid username'
        elif request.form['password'] != app.config['PASSWORD']:
            error = 'Invalid password'
        else:
            session['logged_in'] = True
            flash('You were logged in')
            return redirect(url_for('show_entries'))
    return render_template('login.html', error=error)

登出视图则正好相反,把键值从会话中删除。在这里我们使用了一个小技巧:如果你使用 字典的 pop() 方法并且传递了第二个参数(键的缺省值),那么当字典中有 这个键时就会删除这个键,否则什么也不做。这样做的好处是我们不用检查用户是否已经 登录了。

@app.route('/logout')
def logout():
    session.pop('logged_in', None)
    flash('You were logged out')
    return redirect(url_for('show_entries'))

下面请阅读 步骤 6 :模板 。

步骤 6 :模板

现在开始写模板。如果我们现在访问 URL ,那么会得到一个 Flask 无法找到模板文件的 异常。 Flask 使用 Jinja2 模板语法并默认开启自动转义。也就是说除非用 Markup 标记一个值或在模板中使用 |safe 过滤器,否则 Jinja2 会把如 < 或 > 之类的特殊字符转义为与其 XML 等价字符。

我们还使用了模板继承以保存所有页面的布局统一。

请把以下模板放在 templates 文件夹中:

layout.html

这个模板包含 HTML 骨架、头部和一个登录链接(如果用户已登录则变为一个注销链接 )。如果有闪现信息,那么还会显示闪现信息。 {% block body %} 块会被子模板中 同名( body )的块替换。

session 字典在模板中也可以使用。你可以使用它来检验用户是否已经 登录。注意,在 Jinja 中可以访问对象或字典的不存在的属性和成员。如例子中的 'logged_in' 键不存在时代码仍然能正常运行:

<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
  <h1>Flaskr</h1>
  <div class=metanav>
  {% if not session.logged_in %}
    <a href="{{ url_for('login') }}">log in</a>
  {% else %}
    <a href="{{ url_for('logout') }}">log out</a>
  {% endif %}
  </div>
  {% for message in get_flashed_messages() %}
    <div class=flash>{{ message }}</div>
  {% endfor %}
  {% block body %}{% endblock %}
</div>
show_entries.html

这个模板扩展了上述的 layout.html 模板,用于显示信息。注意, for 遍历了我们 通过render_template() 函数传递的所有信息。模板还告诉表单使用 POST 作为 HTTP 方法向 add_entry 函数提交数据:

{% extends "layout.html" %}
{% block body %}
  {% if session.logged_in %}
    <form action="{{ url_for('add_entry') }}" method=post class=add-entry>
      <dl>
        <dt>Title:
        <dd><input type=text size=30 name=title>
        <dt>Text:
        <dd><textarea name=text rows=5 cols=40></textarea>
        <dd><input type=submit value=Share>
      </dl>
    </form>
  {% endif %}
  <ul class=entries>
  {% for entry in entries %}
    <li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
  {% else %}
    <li><em>Unbelievable.  No entries here so far</em>
  {% endfor %}
  </ul>
{% endblock %}
login.html

最后是简单显示用户登录表单的登录模板:

{% extends "layout.html" %}
{% block body %}
  <h2>Login</h2>
  {% if error %}<p class=error><strong>Error:</strong> {{ error }}{% endif %}
  <form action="{{ url_for('login') }}" method=post>
    <dl>
      <dt>Username:
      <dd><input type=text name=username>
      <dt>Password:
      <dd><input type=password name=password>
      <dd><input type=submit value=Login>
    </dl>
  </form>
{% endblock %}

下面请阅读 步骤 7 :添加样式 。

步骤 7 :添加样式

现在万事俱备,只剩给应用添加一些样式了。只要把以下内容保存为 static 文件夹中 的style.css 文件就行了:

body            { font-family: sans-serif; background: #eee; }
a, h1, h2       { color: #377ba8; }
h1, h2          { font-family: 'Georgia', serif; margin: 0; }
h1              { border-bottom: 2px solid #eee; }
h2              { font-size: 1.2em; }

.page           { margin: 2em auto; width: 35em; border: 5px solid #ccc;
                  padding: 0.8em; background: white; }
.entries        { list-style: none; margin: 0; padding: 0; }
.entries li     { margin: 0.8em 1.2em; }
.entries li h2  { margin-left: -1em; }
.add-entry      { font-size: 0.9em; border-bottom: 1px solid #ccc; }
.add-entry dl   { font-weight: bold; }
.metanav        { text-align: right; font-size: 0.8em; padding: 0.3em;
                  margin-bottom: 1em; background: #fafafa; }
.flash          { background: #cee5F5; padding: 0.5em;
                  border: 1px solid #aacbe2; }
.error          { background: #f0d6d6; padding: 0.5em; }

下面请阅读 额外赠送:测试应用 。

额外赠送:测试应用

现在你已经完成了整个应用,一切都运行正常。为了方便以后进行完善修改,添加自动 测试不失为一个好主意。本教程中的应用已成为 测试 Flask 应用 文档中演示如何进行 单元测试的例子,可以去看看测试 Flask 应用是多么容易。

模板

Flask 使用 Jinja2 作为默认模板引擎。你完全可以使用其它模板引擎。但是不管你使用 哪种模板引擎,都必须安装 Jinja2 。因为使用 Jinja2 可以让 Flask 使用更多依赖于 这个模板引擎的扩展。

本文只是简单介绍如何在 Flask 中使用 Jinja2 。如果要详细了解这个模板引擎的语法, 请查阅 Jinja2 模板官方文档 。

Jinja 设置

在 Flask 中, Jinja2 默认配置如下:

  • 在扩展名为 .html 、 .htm 、 .xml 和 .xhtml 的模板中开启自动 转义。
  • 在模板中可以使用 {% autoescape %} 来手动设置是否转义。
  • Flask 在 Jinja2 环境中加入一些全局函数和辅助对象,以增强模板的功能。

标准环境

缺省情况下,以下全局变量可以在 Jinja2 模板中使用:

config

当前配置对象 ( flask.config )

New in version 0.6.

Changed in version 0.10: 此版本开始,这个变量总是可用,甚至是在被导入的模板中。

request

当前请求对象 ( flask.request )。在没有活动请求环境情况下渲染模块 时,这个变量不可用。

session

当前会话对象 ( flask.session )。在没有活动请求环境情况下渲染模块 时,这个变量不可用。

g

请求绑定的全局变量 ( flask.g )。在没有活动请求环境情况下渲染模块 时,这个变量不可用。

url_for()

flask.url_for() 函数。

get_flashed_messages()

flask.get_flashed_messages() 函数。

Jinja 环境行为

这些添加到环境中的变量不是全局变量。与真正的全局变量不同的是这些变量在已导入 的模板的环境中是不可见的。这样做是基于性能的原因,同时也考虑让代码更有条理。

那么对你来说又有什么意义呢?假设你需要导入一个宏,这个宏需要访问请求对象, 那么你有两个选择:

  1. 显式地把请求或都该请求有用的属性作为参数传递给宏。
  2. 导入 “with context” 宏。

导入方式如下:

{% from '_helpers.html' import my_macro with context %}

标准过滤器

在 Flask 中的模板中添加了以下 Jinja2 本身没有的过滤器:

tojson()

这个函数可以把对象转换为 JSON 格式。如果你要动态生成 JavaScript ,那么这个 函数非常有用。

注意,在 script 标记内部不能转义,因此在 Flask 0.10 之前的版本中,如果要在script 标记内部使用这个函数必须用 |safe 关闭转义:

<script type=text/javascript>
    doSomethingWith({{ user.username|tojson|safe }});
</script>

控制自动转义

自动转义是指自动对特殊字符进行转义。特殊字符是指 HTML ( 或 XML 和 XHTML )中的 & 、 > 、 < 、 " 和 ' 。因为这些特殊字符代表了特殊的意思, 所以如果要在文本中使用它们就必须把它们替换为“实体”。如果不转义,那么用户就 无法使用这些字符,而且还会带来安全问题。(参见 跨站脚本攻击(XSS) )

有时候,如需要直接把 HTML 植入页面的时候,可能会需要在模板中关闭自动转义功能。 这个可以直接植入的 HTML 一般来自安全的来源,例如一个把标记语言转换为 HTML 的 转换器。

有三种方法可以控制自动转义:

  • 在 Python 代码中,可以在把 HTML 字符串传递给模板之前,用 Markup 对象封装。一般情况下推荐使用这个方法。
  • 在模板中,使用 |safe 过滤器显式把一个字符串标记为安全的 HTML (例如: {{myvariable|safe }} )。
  • 临时关闭整个系统的自动转义。

在模板中关闭自动转义系统可以使用 {% autoescape %} 块:

{% autoescape false %}
    <p>autoescaping is disabled here
    <p>{{ will_not_be_escaped }}
{% endautoescape %}

在这样做的时候,要非常小心块中的变量的安全性。

注册过滤器

有两种方法可以在 Jinja2 中注册你自己的过滤器。要么手动把它们放入应用的jinja_env 中,要么使用 template_filter() 装饰器。

下面两个例子功能相同,都是倒序一个对象:

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

def reverse_filter(s):
    return s[::-1]
app.jinja_env.filters['reverse'] = reverse_filter

装饰器的参数是可选的,如果不给出就使用函数名作为过滤器名。一旦注册完成后,你就 可以在模板中像 Jinja2 的内建过滤器一样使用过滤器了。例如,假设在环境中你有一个 名为 mylist 的 Pyhton 列表:

{% for x in mylist | reverse %}
{% endfor %}

环境处理器

环境处理器的作用是把新的变量自动引入模板环境中。环境处理器在模板被渲染前运行, 因此可以把新的变量自动引入模板环境中。它是一个函数,返回值是一个字典。在应用的 所有模板中,这个字典将与模板环境合并:

@app.context_processor
def inject_user():
    return dict(user=g.user)

上例中的环境处理器创建了一个值为 g.user 的 user 变量,并把这个变量加入了 模板环境中。这个例子只是用于说明工作原理,不是非常有用,因为在模板中, g 总是 存在的。

传递值不仅仅局限于变量,还可以传递函数( Python 提供传递函数的功能):

@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'€'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

上例中的环境处理器把 format_price 函数传递给了所有模板:

{{ format_price(0.33) }}

你还可以把 format_price 创建为一个模板过滤器(参见 注册过滤器 ),这里只是演示如何在一个环境处理器中传递函数。

测试 Flask 应用

未经测试的小猫,肯定不是一只好猫。

这句话的出处不详(译者注:这句是译者献给小猫的),也不一定完全正确,但是基本上 是正确的。未经测试的应用难于改进现有的代码,因此其开发者会越改进越抓狂。反之, 经过自动测试的代码可以安全的改进,并且如果可以测试过程中立即发现错误。

Flask 提供的测试渠道是公开 Werkzeug 的 Client ,为你 处理本地环境。你可以结合这个渠道使用你喜欢的测试工具。本文使用的测试工具是随着 Python 一起安装好的unittest 包。

应用

首先,我们需要一个用来测试的应用。我们将使用 教程 中的应用。如果你还 没有这个应用,可以下载 示例代码 。

测试骨架

为了测试应用,我们添加了一个新的模块 (flaskr_tests.py) 并创建了如下测试骨架:

import os
import flaskr
import unittest
import tempfile

class FlaskrTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
        flaskr.app.config['TESTING'] = True
        self.app = flaskr.app.test_client()
        flaskr.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(flaskr.app.config['DATABASE'])

if __name__ == '__main__':
    unittest.main()

setUp() 方法中会创建一个新的测试客户端并初始化一个新的 数据库。在每个独立的测试函数运行前都会调用这个方法。 tearDown() 方法的功能是在测试结束后关闭文件,并在文件 系统中删除数据库文件。另外在设置中 TESTING 标志开启的,这意味着在请求时关闭 错误捕捉,以便于在执行测试请求时得到更好的错误报告。

测试客户端会给我们提供一个简单的应用接口。我们可以通过这个接口向应用发送测试 请求。客户端还可以追踪 cookies 。

因为 SQLite3 是基于文件系统的,所以我们可以方便地使用临时文件模块来创建一个临时 数据库并初始化它。 mkstemp() 函数返回两个东西:一个低级别的文件 句柄和一个随机文件名。这个文件名后面将作为我们的数据库名称。我们必须把句柄保存 到 db_fd中,以便于以后用 os.close() 函数来关闭文件。

如果现在进行测试,那么会输出以下内容:

$ python flaskr_tests.py

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

虽然没有运行任何实际测试,但是已经可以知道我们的 flaskr 应用没有语法错误。 否则在导入时会引发异常并中断运行。

第一个测试

现在开始测试应用的功能。当我们访问应用的根 URL ( / )时应该显示 “ No entries here so far ”。我们新增了一个新的测试方法来测试这个功能:

class FlaskrTestCase(unittest.TestCase):

    def setUp(self):
        self.db_fd, flaskr.app.config['DATABASE'] = tempfile.mkstemp()
        self.app = flaskr.app.test_client()
        flaskr.init_db()

    def tearDown(self):
        os.close(self.db_fd)
        os.unlink(flaskr.app.config['DATABASE'])

    def test_empty_db(self):
        rv = self.app.get('/')
        assert 'No entries here so far' in rv.data

注意,我们的调试函数都是以 test 开头的。这样 unittest 就会自动识别这些 是用于测试的函数并运行它们。

通过使用 self.app.get ,可以向应用的指定 URL 发送 HTTP GET 请求,其返回的是 一个 ~flask.Flask.response_class 对象。我们可以使用 data 属性来检查应用的返回值(字符串 类型)。在本例中,我们检查输出是否包含 'No entries here so far' 。

再次运行测试,会看到通过了一个测试:

$ python flaskr_tests.py
.
----------------------------------------------------------------------
Ran 1 test in 0.034s

OK

登录和注销

我们应用的主要功能必须登录以后才能使用,因此必须测试应用的登录和注销。测试的 方法是使用规定的数据(用户名和密码)向应用发出登录和注销的请求。因为登录和注销 后会重定向到别的页面,因此必须告诉客户端使用 follow_redirects 追踪重定向。

在 FlaskrTestCase 类中添加以下两个方法:

def login(self, username, password):
    return self.app.post('/login', data=dict(
        username=username,
        password=password
    ), follow_redirects=True)

def logout(self):
    return self.app.get('/logout', follow_redirects=True)

现在可以方便地测试登录成功、登录失败和注销功能了。下面为新增的测试代码:

def test_login_logout(self):
    rv = self.login('admin', 'default')
    assert 'You were logged in' in rv.data
    rv = self.logout()
    assert 'You were logged out' in rv.data
    rv = self.login('adminx', 'default')
    assert 'Invalid username' in rv.data
    rv = self.login('admin', 'defaultx')
    assert 'Invalid password' in rv.data

测试增加条目功能

我们还要测试增加条目功能。添加以下测试代码:

def test_messages(self):
    self.login('admin', 'default')
    rv = self.app.post('/add', data=dict(
        title='<Hello>',
        text='<strong>HTML</strong> allowed here'
    ), follow_redirects=True)
    assert 'No entries here so far' not in rv.data
    assert '&lt;Hello&gt;' in rv.data
    assert '<strong>HTML</strong> allowed here' in rv.data

这里我们检查了博客内容中允许使用 HTML ,但标题不可以。应用设计思路就是这样的。

运行测试,现在通过了三个测试:

$ python flaskr_tests.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.332s

OK

关于更复杂的 HTTP 头部和状态码测试参见 MiniTwit 示例 。这个示例的源代码中 包含了更大的测试套件。

其他测试技巧

除了使用上述测试客户端外,还可以在 with 语句中使用 test_request_context()方法来临时激活一个请求环境。在这个 环境中可以像以视图函数中一样操作 requestg 和 session 对象。示例:

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    assert flask.request.path == '/'
    assert flask.request.args['name'] == 'Peter'

其他与环境绑定的对象也可以这样使用。

如果你必须使用不同的配置来测试应用,而且没有什么好的测试方法,那么可以考虑使用 应用工厂(参见 应用工厂 )。

注意,在测试请求环境中 before_request() 函数和 after_request() 函数不会被自动调用。但是当调试请求环境离开 with 块时会执行 teardown_request() 函数。如果需要 before_request() 函数和正常情况下一样被调用,那么你必须调用preprocess_request()

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    app.preprocess_request()
    ...

在这函数中可以打开数据库连接或者根据应用需要打开其他类似东西。

如果想调用 after_request() 函数,那么必须调用 process_response() ,并把响应对象传递给它:

app = flask.Flask(__name__)

with app.test_request_context('/?name=Peter'):
    resp = Response('...')
    resp = app.process_response(resp)
    ...

这个例子中的情况基本没有用处,因为在这种情况下可以直接开始使用测试客户端。

伪造资源和环境

New in version 0.10.

通常情况下,我们会把用户认证信息和数据库连接储存到应用环境或者 flask.g 对象中,并在第一次使用前准备好,然后在断开时删除。假设应用中 得到当前用户的代码如下:

def get_user():
    user = getattr(g, 'user', None)
    if user is None:
        user = fetch_current_user_from_database()
        g.user = user
    return user

在测试时可以很很方便地重载用户而不用改动代码。可以先象下面这样钩接flask.appcontext_pushed 信号:

from contextlib import contextmanager
from flask import appcontext_pushed

@contextmanager
def user_set(app, user):
    def handler(sender, **kwargs):
        g.user = user
    with appcontext_pushed.connected_to(handler, app):
        yield

然后使用:

from flask import json, jsonify

@app.route('/users/me')
def users_me():
    return jsonify(username=g.user.username)

with user_set(app, my_user):
    with app.test_client() as c:
        resp = c.get('/users/me')
        data = json.loads(resp.data)
        self.assert_equal(data['username'], my_user.username)

保持环境

New in version 0.4.

有时候这种情形是有用的:触发一个常规请求,但是保持环境以便于做一点额外 的事情。 在 Flask 0.4 之后可以在 with 语句中使用 test_client() 来 实现:

app = flask.Flask(__name__)

with app.test_client() as c:
    rv = c.get('/?tequila=42')
    assert request.args['tequila'] == '42'

如果你在没有 with 的情况下使用 test_client() ,那么 assert 会出错失败。因为无法在请求之外访问 request 。

访问和修改会话

New in version 0.8.

有时候在测试客户端中访问和修改会话是非常有用的。通常有两方法。如果你想测试会话中 的键和值是否正确,你可以使用 flask.session:

with app.test_client() as c:
    rv = c.get('/')
    assert flask.session['foo'] == 42

但是这个方法无法修改会话或在请求发出前访问会话。自 Flask 0.8 开始,我们提供了 “会话处理”,用打开测试环境中会话和修改会话,最后保存会话。处理后的会话独立于 后端实际使用的会话:

with app.test_client() as c:
    with c.session_transaction() as sess:
        sess['a_key'] = 'a value'

    # 运行到这里时,会话已被保存

注意在这种情况下必须使用 sess 对象来代替 flask.session 代理。 sess 对象本身可以提供相同的接口。

掌握应用错误

New in version 0.3.

应用出错,服务器出错。或早或晚,你会遇到产品出错。即使你的代码是百分百正确, 还是会时常看见出错。为什么?因为其他相关东西会出错。以下是一些在代码完全正确的 条件下服务器出错的情况:

  • 客户端已经中断了请求,但应用还在读取数据。
  • 数据库已经过载,无法处理查询。
  • 文件系统没有空间。
  • 硬盘完蛋了。
  • 后台服务过载。
  • 使用的库出现程序错误。
  • 服务器与另一个系统的网络连接出错。

以上只是你会遇到的问题的一小部分。那么如果处理这些问题呢?如果你的应用运行在 生产环境下,那么缺省情况下 Flask 会显示一个简单的出错页面,并把出错情况记录到logger 。

但要做得还不只这些,下面介绍一些更好的出错处理方法。

报错邮件

如果应用在生产环境(在你的服务器中一般使用生产环境)下运行,那么缺省情况下不会 看到任何日志信息。为什么?因为 Flask 是一个零配置的框架。既然没有配置,那么日志 放在哪里呢?显然, Flask 不能来随便找一个地放给用户存放日志,因为如果用户在这个 位置没有创建文件的权限就糟了。同时,对于大多数小应用来说,没人会去看日志。

事实上,我现在可以负责任地说除非调试一个用户向你报告的错误,你是不会去看应用的 日志文件的。你真下需要的是出错的时候马上给你发封电子邮件,及时提醒你,以便于 进行处理。

Flask 使用 Python 内置的日志系统,它可以发送你需要的错误报告电子邮件。以下是 如何配置 Flask 日志记录器发送错误报告电子邮件的例子:

ADMINS = ['yourname@example.com']
if not app.debug:
    import logging
    from logging.handlers import SMTPHandler
    mail_handler = SMTPHandler('127.0.0.1',
                               'server-error@example.com',
                               ADMINS, 'YourApplication Failed')
    mail_handler.setLevel(logging.ERROR)
    app.logger.addHandler(mail_handler)

这个例子是什么意思?我们创建了一个新的 SMTPHandler 类。这个类会使用邮件服务器 127.0.0.1 向 server-error@example.com 的 ADMINS 发送主题为 “YourApplication Failed” 的 电子邮件。如果你的邮件服务器 需要认证,这是可行的,详见 SMTPHandler的文档。

我们还定义了只报送错误及错误以上级别的信息。因为我们不想得到警告或其他没用的 日志,比如请求处理日志。

在你的产品中使用它们前,请查看一下 控制日志格式 ,以了解错误报告邮件的更多 信息,磨刀不误砍柴功。

日志文件

报错邮件有了,可能还需要记录警告信息。这是一个好习惯,有利于除错。请注意,在 核心系统中 Flask 本身不会发出任何警告。因此,在有问题时发出警告只能自力更生了。

虽然有许多日志记录系统,但不是每个系统都能做好基本日志记录的。以下可能是最值得 关注的:

  • FileHandler - 把日志信息记录到文件系统中的一个文件。
  • RotatingFileHandler - 把日志信息记录到文件系统中 的一个文件,当信息达到一定数量后反转。
  • NTEventLogHandler - 把日志信息记录到 Windows 的 事件日志中。如果你的应用部署在 Windows 下,就用这个吧。
  • SysLogHandler - 把日志记录到一个 UNIX 系统日志。

一旦你选定了日志记录器之后,使用方法类似上一节所讲的 SMTP 处理器,只是记录的 级别应当低一点(我推荐 WARNING 级别):

if not app.debug:
    import logging
    from themodule import TheHandlerYouWant
    file_handler = TheHandlerYouWant(...)
    file_handler.setLevel(logging.WARNING)
    app.logger.addHandler(file_handler)

控制日志格式

缺省情况下一个处理器只会把信息字符串写入一个文件或把信息作为电子邮件发送给你。 但是一个日志应当记录更多的信息,因些应该认真地配置日志记录器。一个好的日志不光 记录为什么会出错,更重要的是记录错在哪里。

格式化器使用一个格式化字符串作为实例化时的构造参数,这个字符串中的格式变量会在 日志记录时自动转化。

举例:

电子邮件
from logging import Formatter
mail_handler.setFormatter(Formatter('''
Message type:       %(levelname)s
Location:           %(pathname)s:%(lineno)d
Module:             %(module)s
Function:           %(funcName)s
Time:               %(asctime)s

Message:

%(message)s
'''))
日志文件
from logging import Formatter
file_handler.setFormatter(Formatter(
    '%(asctime)s %(levelname)s: %(message)s '
    '[in %(pathname)s:%(lineno)d]'
))
复杂日志格式

以下是格式化字符串中一种重要的格式变量。注意,这并不包括全部格式变量,更多变更 参见 logging 包的官方文档。

格式变量说明
%(levelname)s 文字形式的日志等级 ( 'DEBUG' 、 'INFO' 、 'WARNING' 、'ERROR' 和 'CRITICAL' )。
%(pathname)s 调用日志的源文件的完整路径(如果可用)。
%(filename)s 调用日志的源文件文件名。
%(module)s 调用日志的模块名。
%(funcName)s 调用日志的函数名。
%(lineno)d 调用日志的代码的行号(如果可用)。
%(asctime)s 调用日志的时间,缺省格式为 "2003-07-08 16:49:45,896"(逗号后面的数字为 毫秒)。通过重载 formatTime() 方法可以改变 格式。
%(message)s 日志记录的消息,同 msg args 。

如果要进一步定制格式,可以使用格式化器的子类。格式化器有三个有趣的方法:

format():
处理实际的格式化。它接收一个 LogRecord 对象,返回格式化后 的字符串。
formatTime():
它用于 asctime 格式变量。重载它可以改变时间格式。
formatException()
它用于异常格式化。接收一个 exc_info 元组并且必须返回一个 字符串。缺省情况下它够用了,不必重载。

更多信息参见官方文档。

其他库

至此,我们只配置了应用本身的日志记录器。其他库可能同样需要记录日志。例如, SQLAlchemy 在其核心中大量使用日志。在 logging 包中有一个方法可以一次性 地配置所有日志记录器,但我不推荐这么做。因为当你在同一个 Python 解释器中同时 运行两个独立的应用时就无法使用不同的日志设置了。

相反,我建议使用 getLogger() 函数来鉴别是哪个日志记录器,并获取 相应的处理器:

from logging import getLogger
loggers = [app.logger, getLogger('sqlalchemy'),
           getLogger('otherlibrary')]
for logger in loggers:
    logger.addHandler(mail_handler)
    logger.addHandler(file_handler)

排除应用错误

掌握应用错误 一文所讲的是如何为生产应用设置日志和出错通知。本文要 讲的是部署中配置调试的要点和如何使用全功能的 Python 调试器深挖错误。

有疑问时,请手动运行

在生产环境中,配置应用时出错?如果你可以通过 shell 来访问主机,那么请首先在部署 环境中验证是否可以通过 shell 手动运行你的应用。请确保验证时使用的帐户与配置的 相同,这样可以排除用户权限引发的错误。你可以在你的生产服务器上,使用 Flask 内建 的开发服务器,并且设置 debug=True ,这样有助于找到配置问题。但是,请 只能在可控的情况下临时这样做 ,绝不能在生产时使用 debug=True 。

使用调试器

为了更深入的挖掘错误,追踪代码的执行, Flask 提供一个开箱即用的调试器(参见 调试模式 )。如果你需要使用其他 Python 调试器,请注意调试器之间的干扰 问题。在使用你自己的调试器前要做一些参数调整:

  • debug - 是否开启调试模式并捕捉异常
  • use_debugger - 是否使用 Flask 内建的调试器
  • use_reloader - 出现异常后是否重载或者派生进程

debug 必须设置为 True (即必须捕获异常),另两个随便。

如果你正在使用 Aptana 或 Eclipse 排错,那么 use_debugger 和 use_reloader 都必须设置为 False 。

一个有用的配置模式如下(当然要根据你的应用调整缩进):

FLASK:
    DEBUG: True
    DEBUG_WITH_APTANA: True

然后,在应用入口( main.py ),修改如下:

if __name__ == "__main__":
    # 为了让 aptana 可以接收到错误,设置 use_debugger=False
    app = create_app(config="config.yaml")

    if app.debug: use_debugger = True
    try:
        # 如果使用其他调试器,应当关闭 Flask 的调试器。
        use_debugger = not(app.config.get('DEBUG_WITH_APTANA'))
    except:
        pass
    app.run(use_debugger=use_debugger, debug=app.debug,
            use_reloader=use_debugger, host='0.0.0.0')

配置管理

New in version 0.3.

应用总是需要一定的配置的。根据应用环境不同,会需要不同的配置。比如开关调试 模式、设置密钥以及其他依赖于环境的东西。

Flask 的设计思路是在应用开始时载入配置。你可以在代码中直接硬编码写入配置,对于 许多小应用来说这不一定是一件坏事,但是还有更好的方法。

不管你使用何种方式载入配置,都可以使用 Flask 的 config 属性来操作配置的值。 Flask 本身就使用这个对象来保存 一些配置,扩展也可以使用这个对象保存配置。同时这也是你保存配置的地方。

配置入门

config 实质上是一个字典的子类,可以像字典一样操作:

app = Flask(__name__)
app.config['DEBUG'] = True

某些配置值还转移到了 Flask 对象中,可以直接通过 Flask 来操作:

app.debug = True

一次更新多个配置值可以使用 dict.update() 方法:

app.config.update(
    DEBUG=True,
    SECRET_KEY='...'
)

内置配置变量

以下配置变量由 Flask 内部使用:

DEBUG 开关调试模式
TESTING 开关测试模式
PROPAGATE_EXCEPTIONS 显式开关异常的传播。当 TESTING 或DEBUG 为真时,总是开启的。
PRESERVE_CONTEXT_ON_EXCEPTION 缺省情况下,如果应用在调试模式下运行, 那么请求环境在发生异常时不会被弹出,以 方便调试器内省数据。可以通过这个配置来 禁止这样做。还可以使用这个配置强制不执行 调试,这样可能有助于调试生产应用(风险 大)。
SECRET_KEY 密钥
SESSION_COOKIE_NAME 会话 cookie 的名称
SESSION_COOKIE_DOMAIN 会话 cookie 的域。如果没有配置,那么SERVER_NAME 的所有子域都可以使用 这个 cookie 。
SESSION_COOKIE_PATH 会话 cookie 的路径。如果没有配置,那么 所有 APPLICATION_ROOT 都可以使用 cookie 。如果没有设置 APPLICATION_ROOT ,那么 '/' 可以 使用 cookie 。
SESSION_COOKIE_HTTPONLY 设置 cookie 的 httponly 标志,缺省为 True 。
SESSION_COOKIE_SECURE 设置 cookie 的安全标志,缺省为 False 。
PERMANENT_SESSION_LIFETIME 常驻会话的存活期,其值是一个datetime.timedelta 对象。 自 Flask 0.8 开始,其值可以是一个整数, 表示秒数。
USE_X_SENDFILE 开关 x-sendfile
LOGGER_NAME 日志记录器的名称
SERVER_NAME 服务器的名称和端口号,用于支持子域(如:'myapp.dev:5000' )。注意设置为 “ localhost ”没有用,因为 localhost 不 支持子域。设置了 SERVER_NAME 后,在 缺省情况下会启用使用应用环境而不使用请求 环境的 URL 生成。
APPLICATION_ROOT 如果应用不占用整个域或子域,那么可以用 这个配置来设定应用的路径。这个配置还用作 会话 cookie 的路径。如果使用了整个域, 那么这个配置的值应当为 None 。
MAX_CONTENT_LENGTH 这个配置的值单位为字节,如果设置了,那么 Flask 会拒绝超过设定长度的请求,返回一个 413 状态码。
SEND_FILE_MAX_AGE_DEFAULT send_static_file() ( 缺省静态文件处理器)和 send_file() 使用的缺省缓存 最大存活期控制,以秒为单位。把get_send_file_max_age() 分别挂勾到Flask 或 Blueprint 上,可以重载每个 文件的值。缺省值为 43200 ( 12 小时)。
TRAP_HTTP_EXCEPTIONS 如果设置为 True ,那么 Flask 将不 执行 HTTP 异常的错误处理,而是把它像其它 异常同样对待并把它压入异常堆栈。当你在 必须查找出一个 HTTP 异常来自哪里的情况下 这个 配置比较有用。
TRAP_BAD_REQUEST_ERRORS Werkzeug 用于处理请求特殊数据的内部数据 结构会引发坏请求异常。同样,许多操作为了 一致性会使用一个坏请求隐藏操作失败。在 这种情况下,这个配置可以在调试时辨别到底 为什么会失败。如果这个配置设为 True ,那么就只能得到一个普通的反馈。
PREFERRED_URL_SCHEME 在没有可用的模式的情况下, URL 生成所 使用的 URL 模式。缺省值为 http 。
JSON_AS_ASCII 缺省情况下 Flask 把对象序列化为 ascii-encoded JSON 。如果这个参数值为 False ,那么 Flask 就不会把对象编码 为 ASCII ,只会原样输出返回 unicode 字符 串。 jsonfiy 会自动把对象编码 utf-8 字符用于传输。
JSON_SORT_KEYS 缺省情况下 Flask 会按键值排序 JSON 对象, 这是为了确保字典的哈希种子的唯一性,返回 值会保持一致,不会破坏外部 HTTP 缓存。 改变这个参数的值就可以重载缺省的行为, 重载后可能会提高缓存的性能,但是不推荐 这样做。
JSONIFY_PRETTYPRINT_REGULAR 如果这个参数设置为 True (缺省值), 并且如果 jsonify 响应不是被一个 XMLHttpRequest 对象请求的(由 X-Requested-With 头部控制),那么 就会被完美打印。

关于 SERVER_NAME 的更多说明

SERVER_NAME 配置用于支持子域。如果要使用子域,那么就需要这个配置。因为 Flask 在不知道真正服务器名称的情况下无法得知子域。这个配置也用于会话 cookie 。

请记住,不仅 Flask 是在使用子域时有这样的问题,你的浏览器同样如此。大多数 现代浏览器不会允许在没有点的服务器名称上设置跨子域 cookie 。因此,如果你的 服务器名称是 'localhost' ,那么你将不能为 'localhost' 和所有子域设置 cookie 。在这种情况下请选择一个其他服务器名称,如 'myapplication.local' 。并且把名称加上要使用的子域写入主机配置中或者设置 一个本地 bind 。

New in version 0.4: LOGGER_NAME

New in version 0.5: SERVER_NAME

New in version 0.6: MAX_CONTENT_LENGTH

New in version 0.7: PROPAGATE_EXCEPTIONSPRESERVE_CONTEXT_ON_EXCEPTION

New in version 0.8: TRAP_BAD_REQUEST_ERRORSTRAP_HTTP_EXCEPTIONS,APPLICATION_ROOTSESSION_COOKIE_DOMAINSESSION_COOKIE_PATH,SESSION_COOKIE_HTTPONLYSESSION_COOKIE_SECURE

New in version 0.9: PREFERRED_URL_SCHEME

New in version 0.10: JSON_AS_ASCIIJSON_SORT_KEYS,JSONIFY_PRETTYPRINT_REGULAR

使用配置文件

如果把配置放在一个单独的文件中会更有用。理想情况下配置文件应当放在应用包的 外面。这样可以在修改配置文件时不影响应用的打包与分发( 使用 Distribute 部署 )。

因此,常见用法如下:

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

首先从 yourapplication.default_settings 模块载入配置,然后根据YOURAPPLICATION_SETTINGS 环境变量所指向的文件的内容重载配置的值。在 启动服务器前,在 Linux 或 OS X 操作系统中,这个环境变量可以在终端中使用 export 命令来设置:

$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
$ python run-app.py
 * Running on http://127.0.0.1:5000/
 * Restarting with reloader...

在 Windows 系统中使用内置的 set 来代替:

>set YOURAPPLICATION_SETTINGS=\path\to\settings.cfg

配置文件本身实质是 Python 文件。只有全部是大写字母的变量才会被配置对象所使用。 因此请确保使用大写字母。

一个配置文件的例子:

# 配置示例
DEBUG = False
SECRET_KEY = '?\xbf,\xb4\x8d\xa3"<\x9c\xb0@\x0f5\xab,w\xee\x8d$0\x13\x8b83'

请确保尽早载入配置,以便于扩展在启动时可以访问相关配置。除了从文件载入配置外, 配置对象还有其他方法可以载入配置,详见 Config 对象的文档。

配置的最佳实践

前述的方法的缺点是测试有一点点麻烦。通常解决这个问题没有标准答案,但有些好的 好的建议:

  1. 在一个函数中创建你的应用并注册“蓝图”。这样就可以使用不同配置创建多个 实例,极大方便单元测试。你可以按需载入配置。
  2. 不要编写在导入时就访问配置的代码。如果你限制自己只能通过请求访问代码,那么 你可以以后按需配置对象。

开发/生产

大多数应用需要一个以上的配置。最起码需要一个配置用于生产服务器,另一个配置用于 开发。应对这种情况的最简单的方法总是载入一个缺省配置,并把这个缺省配置作为版本 控制的一部分。然后,把需要重载的配置,如前文所述,放在一个独立的文件中:

app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')

然后你只要增加一个独立的 config.py 文件并导出YOURAPPLICATION_SETTINGS=/path/to/config.py 就可了。当然还有其他方法可选, 例如可以使用导入或子类。

在 Django 应用中,通常的做法是在文件的开关增加 fromyourapplication.default_settings import * 进行显式地导入,然后手工重载 配置。你还可以通过检查一个 YOURAPPLICATION_MODE 之类的环境变量(变量值设置 为production 或 development 等等)来导入不同的配置文件。

一个有趣的方案是使用类和类的继承来配置:

class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite://:memory:'

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DEBUG = True

class TestingConfig(Config):
    TESTING = True

如果要使用这样的方案,那么必须使用 from_object():

app.config.from_object('configmodule.ProductionConfig')

配置的方法多种多样,由你定度。以下是一些建议:

  • 在版本控制中保存一个缺省配置。要么在应用中使用这些缺省配置,要么先导入缺省 配置然后用你自己的配置文件来重载缺省配置。
  • 使用一个环境变量来切换不同的配置。这样就可以在 Python 解释器外进行切换,而 根本不用改动代码,使开发和部署更方便,更快捷。如果你经常在不同的项目间 切换,那么你甚至可以创建代码来激活 virtualenv 并导出开发配置。
  • 在生产应用中使用 fabric 之类的工具,向服务器分别传送代码和配置。更多细节 参见 使用 Fabric 部署 方案。

实例文件夹

New in version 0.8.

Flask 0.8 引入了实例文件夹。 Flask 花了很长时间才能够直接使用应用文件夹的路径( 通过 Flask.root_path )。这也是许多开发者载入应用文件夹外的配置的方法。 不幸的是这种方法只能用于应用不是一个包的情况下,即根路径指向包的内容的情况。

Flask 0.8 引入了一个新的属性: Flask.instance_path 。它指向一个新名词: “实例文件夹”。实例文件夹应当处于版本控制中并进行特殊部署。这个文件夹特别适合 存放需要在应用运行中改变的东西或者配置文件。

可以要么在创建 Flask 应用时显式地提供实例文件夹的路径,要么让 Flask 自动探测 实例文件夹。显式定义使用 instance_path 参数:

app = Flask(__name__, instance_path='/path/to/instance/folder')

请记住,这里提供的路径 必须 是绝对路径。

如果 instance_path 参数没有提供,那么会使用以下缺省位置:

  • 未安装的模块:

    /myapp.py
    /instance
    
  • 未安装的包:

    /myapp
        /__init__.py
    /instance
    
  • 已安装的模块或包:

    $PREFIX/lib/python2.X/site-packages/myapp
    $PREFIX/var/myapp-instance
    

    $PREFIX 是你的 Python 安装的前缀。可能是 /usr 或你的 virtualenv 的 路径。可以通过打印 sys.prefix 的值来查看当前的前缀的值。

既然可以通过使用配置对象来根据关联文件名从文件中载入配置,那么就可以通过改变与 实例路径相关联的文件名来按需要载入不同配置。在配置文件中的关联路径的行为可以在 “关联到应用的根路径”(缺省的)和 “关联到实例文件夹”之间变换,具体通过应用 构建函数中的 instance_relative_config 来实现:

app = Flask(__name__, instance_relative_config=True)

以下是一个完整的配置 Flask 的例子,从一个模块预先载入配置,然后从配置文件夹中的 一个配置文件(如果这个文件存在的话)载入要重载的配置:

app = Flask(__name__, instance_relative_config=True)
app.config.from_object('yourapplication.default_settings')
app.config.from_pyfile('application.cfg', silent=True)

通过 Flask.instance_path 可以找到实例文件夹的路径。 Flask 还提供一个打开实例文件夹中的文件的快捷方法: Flask.open_instance_resource() 。

举例说明:

filename = os.path.join(app.instance_path, 'application.cfg')
with open(filename) as f:
    config = f.read()

# 或者通过使用 open_instance_resource:
with app.open_instance_resource('application.cfg') as f:
    config = f.read()

信号

New in version 0.6.

Flask 自 0.6 版本开始在内部支持信号。信号功能由优秀的 blinker 库提供支持, 如果没有安装该库就无法使用信号功能,但不影响其他功能。

什么是信号?当核心框架的其他地方或另一个 Flask 扩展中发生动作时,信号通过发送 通知来帮助你解耦应用。简言之,信号允许某个发送者通知接收者有事情发生了。

Flask 自身有许多信号,其他扩展可能还会带来更多信号。请记住,信号使用目的是通知 接收者,不应该鼓励接收者修改数据。你会注意到信号的功能与一些内建的装饰器类似( 如 request_started 与 before_request() 非常 相似),但是它们的工作原理不同。例如核心的 before_request() 处理器以一定的顺序执行,并且可以提前退出请求,返回一个响应。相反,所有的信号 处理器是乱序执行的,并且不修改任何数据。

信号的最大优势是可以安全快速的订阅。比如,在单元测试中这些临时订阅十分有用。 假设你想知道请求需要渲染哪个模块,信号可以给你答案。

订阅信号

使用信号的 connect() 方法可以订阅该信号。该方法的 第一个参数是当信号发出时所调用的函数。第二个参数是可选参数,定义一个发送者。 使用 disconnect() 方法可以退订信号。

所有核心 Flask 信号的发送者是应用本身。因此当订阅信号时请指定发送者,除非你真的 想要收听应用的所有信号。当你正在开发一个扩展时,尤其要注意这点。

下面是一个环境管理器的辅助工具,可用于在单元测试中辨别哪个模板被渲染了,哪些 变量被传递给了模板:

from flask import template_rendered
from contextlib import contextmanager

@contextmanager
def captured_templates(app):
    recorded = []
    def record(sender, template, context, **extra):
        recorded.append((template, context))
    template_rendered.connect(record, app)
    try:
        yield recorded
    finally:
        template_rendered.disconnect(record, app)

上例可以在测试客户端中轻松使用:

with captured_templates(app) as templates:
    rv = app.test_client().get('/')
    assert rv.status_code == 200
    assert len(templates) == 1
    template, context = templates[0]
    assert template.name == 'index.html'
    assert len(context['items']) == 10

为了使 Flask 在向信号中添加新的参数时不发生错误,请确保使用一个额外的 **extra参数。

在 with 代码块中,所有由 app 渲染的模板会被记录在 templates 变量中。每当有 模板被渲染,模板对象及环境就会追加到变量中。

另外还有一个方便的辅助方法( connected_to() )。它 允许临时把一个使用环境对象的函数订阅到一个信号。因为环境对象的返回值不能被 指定,所以必须把列表作为参数:

from flask import template_rendered

def captured_templates(app, recorded, **extra):
    def record(sender, template, context):
        recorded.append((template, context))
    return template_rendered.connected_to(record, app)

上例可以这样使用:

templates = []
with captured_templates(app, templates, **extra):
    ...
    template, context = templates[0]

Blinker API 变化

Blinker version 1.1 版本中增加了 connected_to() 方法。

创建信号

如果相要在你自己的应用中使用信号,那么可以直接使用 blinker 库。最常见的,也是最 推荐的方法是在自定义的 Namespace 中命名信号:

from blinker import Namespace
my_signals = Namespace()

接着可以像这样创建新的信号:

model_saved = my_signals.signal('model-saved')

信号的名称应当是唯一的,并且应当简明以便于调试。可以通过 name 属性获得信号的名称。

扩展开发者注意

如果你正在编写一个 Flask 扩展,并且想要妥善处理 blinker 安装缺失的情况,那么 可以使用 flask.signals.Namespace 类。

发送信号

如果想要发送信号,可以使用 send() 方法。它的第一个 参数是一个发送者,其他参数要发送给订阅者的东西,其他参数是可选的:

class Model(object):
    ...

    def save(self):
        model_saved.send(self)

请谨慎选择发送者。如果是一个发送信号的类,请把 self 作为发送者。如果发送信号 的是一个随机的函数,那么可以把 current_app._get_current_object() 作为 发送者。

传递代理作为发送者

不要把 current_app 作为发送者传递给信号。请使用current_app._get_current_object() 。因为 current_app 是 一个代理,不是实际的应用对象。

信号与 Flask 的请求环境

信号在接收时,完全支持 请求环境 。在 request_started 和 request_finished 本地环境变量 始终可用。因此你可以依赖 flask.g 及其他本地环境变量。 请注意在 发送信号 中所述的限制和 request_tearing_down 信号。

信号订阅装饰器

Blinker 1.1 版本中你还可以通过使用新的 connect_via() 装饰器轻松订阅信号:

from flask import template_rendered

@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
    print 'Template %s is rendered with %s' % (template.name, context)

核心信号

Flask 中有以下信号:

flask.template_rendered

这个信号发送于一个模板被渲染成功后。信号传递的 template 是模板的实例,context 是环境对象是一个字典。

订阅示例:

def log_template_renders(sender, template, context, **extra):
    sender.logger.debug('Rendering template "%s" with context %s',
                        template.name or 'string template',
                        context)

from flask import template_rendered
template_rendered.connect(log_template_renders, app)
flask.request_started

这个信号发送于请求开始之前,且请求环境设置完成之后。因为请求环境已经绑定, 所以订阅者可以用标准的全局代理,如 request 来操作请求。

订阅示例:

def log_request(sender, **extra):
    sender.logger.debug('Request context is set up')

from flask import request_started
request_started.connect(log_request, app)
flask.request_finished

这个信号发送于向客户端发送响应之前。信号传递的 response 为将要发送的响应。

订阅示例:

def log_response(sender, response, **extra):
    sender.logger.debug('Request context is about to close down.  '
                        'Response: %s', response)

from flask import request_finished
request_finished.connect(log_response, app)
flask.got_request_exception

这个信号发送于请求进行中发生异常的时候。它的发送 早于 标准异常处理介于。 在调试模式下,虽然没有异常处理,但发生异常时也发送这个信号。信号传递的exception 是异常对象。

订阅示例:

def log_exception(sender, exception, **extra):
    sender.logger.debug('Got exception during processing: %s', exception)

from flask import got_request_exception
got_request_exception.connect(log_exception, app)
flask.request_tearing_down

这个信号发送于请求崩溃的时候,不管是否引发异常。目前,侦听此信号的函数在一般 崩溃处理器后调用,但是没有什么东西可用。

订阅示例:

def close_db_connection(sender, **extra):
    session.close()

from flask import appcontext_tearing_down
request_tearing_down.connect(close_db_connection, app)

在 Flask 版本 0.9 中,这还会传递一个 exc 关键字参数,如果这个参数存在的话。 这个参数是引发崩溃的异常的引用。

flask.appcontext_tearing_down

当应用环境崩溃时发送这个信号。这个信号总是会发送,甚至是因为一个异常引发的 崩溃。侦听这个信号的函数会在常规崩溃处理器后被调用,但是你无法回馈这个信号。

订阅示例:

def close_db_connection(sender, **extra):
    session.close()

from flask import request_tearing_down
appcontext_tearing_down.connect(close_db_connection, app)

这还会传递一个 exc 关键字参数,如果这个参数存在的话。这个参数是引发崩溃的 异常的引用。

flask.appcontext_pushed

当一个应用的环境被压入时,应用会发送这个信号。这个信号通常用于在单元测试中 临时钩接信息。例如可以用于改变 g 对象中现存的资源。

用法示例:

from contextlib import contextmanager
from flask import appcontext_pushed

@contextmanager
def user_set(app, user):
    def handler(sender, **kwargs):
        g.user = user
    with appcontext_pushed.connected_to(handler, app):
        yield

在测试代码中这样写:

def test_user_me(self):
    with user_set(app, 'john'):
        c = app.test_client()
        resp = c.get('/users/me')
        assert resp.data == 'username=john'

New in version 0.10.

appcontext_popped

当一个应用的环境被弹出时,应用会发送这个信号。这个信号通常写成appcontext_tearing_down 信号。

New in version 0.10.

flask.message_flashed

当应用闪现一个消息时会发出这个信号。message`参数是消息内容, `category 参数是消息类别。

订阅示例:

recorded = []
def record(sender, message, category, **extra):
    recorded.append((message, category))

from flask import message_flashed
message_flashed.connect(record, app)

New in version 0.10.

可插拨视图

New in version 0.7.

Flask 0.7 版本引入了可插拨视图。可插拨视图基于使用类来代替函数,其灵感来自于 Django 的通用视图。可插拨视图的主要用途是用可定制的、可插拨的视图来替代部分 实现。

基本原理

假设有一个函数用于从数据库中载入一个对象列表并在模板中渲染:

@app.route('/users/')
def show_users(page):
    users = User.query.all()
    return render_template('users.html', users=users)

上例简单而灵活。但是如果要把这个视图变成一个可以用于其他模型和模板的通用视图, 那么这个视图还是不够灵活。因此,我们就需要引入可插拨的、基于类的视图。第一步, 可以把它转换为一个基础视图:

from flask.views import View

class ShowUsers(View):

    def dispatch_request(self):
        users = User.query.all()
        return render_template('users.html', objects=users)

app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))

就如你所看到的,必须做的是创建一个 flask.views.View 的子类,并且执行dispatch_request() 。然后必须通过使用 as_view() 方法把类转换为实际视图函数。传递给函数的 字符串是最终视图的名称。但是这本身没有什么帮助,所以让我们来小小地重构一下:

from flask.views import View

class ListView(View):

    def get_template_name(self):
        raise NotImplementedError()

    def render_template(self, context):
        return render_template(self.get_template_name(), **context)

    def dispatch_request(self):
        context = {'objects': self.get_objects()}
        return self.render_template(context)

class UserView(ListView):

    def get_template_name(self):
        return 'users.html'

    def get_objects(self):
        return User.query.all()

这样做对于示例中的小应用没有什么用途,但是可以足够清楚的解释基本原理。当你有 一个基础视图类时,问题就来了:类的 self 指向什么?解决之道是:每当请求发出时 就创建一个类的新实例,并且根据来自 URL 规则的参数调用 dispatch_request() 方法。类本身根据参数实例化后传递给 as_view() 函数。例如可以这样写一个类:

class RenderTemplateView(View):
    def __init__(self, template_name):
        self.template_name = template_name
    def dispatch_request(self):
        return render_template(self.template_name)

然后可以这样注册:

app.add_url_rule('/about', view_func=RenderTemplateView.as_view(
    'about_page', template_name='about.html'))

方法提示

可插拨视图可以像普通函数一样加入应用。加入的方式有两种,一种是使用 route() ,另一种是使用更好的 add_url_rule() 。在加入的视图中应该提供所使用的 HTTP 方法的 名称。提供名称的方法是使用 methods 属性:

class MyView(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'POST':
            ...
        ...

app.add_url_rule('/myview', view_func=MyView.as_view('myview'))

基于方法调度

对于 REST 式的 API 来说,为每种 HTTP 方法提供相对应的不同函数显得尤为有用。使用 flask.views.MethodView 可以轻易做到这点。在这个类中,每个 HTTP 方法 都映射到一个同名函数(函数名称为小写字母):

from flask.views import MethodView

class UserAPI(MethodView):

    def get(self):
        users = User.query.all()
        ...

    def post(self):
        user = User.from_form_data(request.form)
        ...

app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))

使用这种方式,不必提供 methods 属性,它会自动使用相应 的类方法。

装饰视图

视图函数会被添加到路由系统中,而视图类则不会。因此视图类不需要装饰,只能以手工 使用 as_view() 来装饰返回值:

def user_required(f):
    """Checks whether user is logged in or raises error 401."""
    def decorator(*args, **kwargs):
        if not g.user:
            abort(401)
        return f(*args, **kwargs)
    return decorator

view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)

自 Flask 0.8 版本开始,新加了一种选择:在视图类中定义装饰的列表:

class UserAPI(MethodView):
    decorators = [user_required]

请牢记:因为从调用者的角度来看,类的 self 被隐藏了,所以不能在类的方法上单独 使用装饰器。

用于 API 的方法视图

网络 API 经常直接对应 HTTP 变量,因此很有必要实现基于 MethodView 的 API 。即多数时候, API 需要把不同的 URL 规则应用到同一个方法视图。例如,假设你需要这样使用一个 user 对象:

URL 方法 说明
/users/ GET 给出一个包含所有用户的列表
/users/ POST 创建一个新用户
/users/<id> GET 显示一个用户
/users/<id> PUT 更新一个用户
/users/<id> DELETE 删除一个用户

那么如何使用 MethodView 来实现呢?方法是使用多个规则对应 到同一个视图。

假设视图是这样的:

class UserAPI(MethodView):

    def get(self, user_id):
        if user_id is None:
            # 返回一个包含所有用户的列表
            pass
        else:
            # 显示一个用户
            pass

    def post(self):
        # 创建一个新用户
        pass

    def delete(self, user_id):
        # 删除一个用户
        pass

    def put(self, user_id):
        # update a single user
        pass

那么如何把这个视图挂接到路由系统呢?方法是增加两个规则并为每个规则显式声明 方法:

user_view = UserAPI.as_view('user_api')
app.add_url_rule('/users/', defaults={'user_id': None},
                 view_func=user_view, methods=['GET',])
app.add_url_rule('/users/', view_func=user_view, methods=['POST',])
app.add_url_rule('/users/<int:user_id>', view_func=user_view,
                 methods=['GET', 'PUT', 'DELETE'])

如果你有许多类似的 API ,那么可以代码如下:

def register_api(view, endpoint, url, pk='id', pk_type='int'):
    view_func = view.as_view(endpoint)
    app.add_url_rule(url, defaults={pk: None},
                     view_func=view_func, methods=['GET',])
    app.add_url_rule(url, view_func=view_func, methods=['POST',])
    app.add_url_rule('%s<%s:%s>' % (url, pk_type, pk), view_func=view_func,
                     methods=['GET', 'PUT', 'DELETE'])

register_api(UserAPI, 'user_api', '/users/', pk='user_id')

应用环境

New in version 0.9.

Flask 的设计思路之一是代码有两种不同的运行“状态”。一种是“安装”状态,从 Flask 对象被实例化后开始,到第一个请求到来之前。在这种状态下,以下规则 成立:

  • 可以安全地修改应用对象。
  • 还没有请求需要处理。
  • 没有应用对象的引用,因此无法修改应用对象。

相反,在请求处理时,以下规则成立:

  • 当请求活动时,本地环境对象 ( flask.request 或其他对象)指向当前 请求。
  • 这些本地环境对象可以任意修改。

除了上述两种状态之外,还有介于两者之间的第三种状态。这种状态下,你正在处理应用, 但是没有活动的请求。就类似于,你坐在 Python 交互终端前,与应用或一个命令行程序 互动。

应用环境是 current_app 本地环境的源动力。

应用环境的作用

应用环境存在的主要原因是,以前过多的功能依赖于请求环境,缺乏更好的处理方案。 Flask 的一个重要设计原则是可以在同一个 Pyhton 进程中运行多个应用。

那么,代码如何找到“正确”的应用呢?以前我们推荐显式地传递应用,但是有可能会 引发库与库之间的干扰。

问题的解决方法是使用 current_app 代理。 current_app 是绑定到当前请求的应用的引用。但是没有请求的情况下 使用请求环境是一件奢侈的事情,于是就引入了应用环境。

创建一个应用环境

创建应用环境有两种方法。一种是隐式的:当一个请求环境被创建时,如果有需要就会 相应创建一个应用环境。因此,你可以忽略应用环境的存在,除非你要使用它。

另一种是显式的,使用 app_context() 方法:

from flask import Flask, current_app

app = Flask(__name__)
with app.app_context():
    # 在本代码块中, current_app 指向应用。
    print current_app.name

在 SERVER_NAME 被设置的情况下,应用环境还被 url_for() 函数 使用。这样可以让你在没有请求的情况下生成 URL 。

环境的作用域

应用环境按需创建和消灭,它从来不在线程之间移动,也不被不同请求共享。因此,它是 一个存放数据库连接信息和其他信息的好地方。内部的栈对象被称为flask._app_ctx_stack 。扩展可以在栈的最顶端自由储存信息,前提是使用唯一 的名称,而 flask.g 对象是留给用户使用的。

更多信息参见 Flask 扩展开发 。

环境的用法

环境的典型用途是缓存一个请求需要预备或使用的资源,例如一个数据库连接。因为环境 是一个 Flask 的应用和扩展共享的地方,所以储存的资源必须使用独一无二的名称。

最常见的用法是把资源管理分为以下两个部分:

  1. 在环境中缓存一个隐式的资源。
  2. 资源释放后的环境解散。

通常会有一个形如 get_X() 函数,这个函数的用途是当资源 X 存在时就返回 这个资源,否则就创建这个资源。还会有一个 teardown_X() 函数用作解散句柄。

这是一个连接数据库的例子:

import sqlite3
from flask import g

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = connect_to_database()
    return db

@app.teardown_appcontext
def teardown_db(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

第一次调用 get_db() 时,连接将会被建立。建立的过程中隐式地使用了一个LocalProxy 类:

from werkzeug.local import LocalProxy
db = LocalProxy(get_db)

这样,用户就可以通过 get_db() 来直接访问 db 了。

请求环境

本文讲述 Flask 0.7 版本的运行方式,与旧版本的运行方式基本相同,但也有一些细微的 差别。

建议你在阅读本文之前,先阅读 应用环境 。

深入本地环境

假设有一个工具函数,这个函数返回用户重定向的 URL (包括 URL 的 next 参数、 或 HTTP 推荐 和索引页面):

from flask import request, url_for

def redirect_url():
    return request.args.get('next') or \
           request.referrer or \
           url_for('index')

如上例所示,这个函数访问了请求对象。如果你在一个普通的 Python 解释器中运行这个 函数,那么会看到如下异常:

>>> redirect_url()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'request'

这是因为现在我们没有一个可以访问的请求。所以我们只能创建一个请求并绑定到当前 环境中。 test_request_context 方法可以创建一个 RequestContext :

>>> ctx = app.test_request_context('/?next=http://example.com/')

这个环境有两种使用方法:一种是使用 with 语句;另一种是调用 push() 和 pop() 方法:

>>> ctx.push()

现在可以使用请求对象了:

>>> redirect_url()
u'http://example.com/'

直到你调用 pop :

>>> ctx.pop()

可以把请求环境理解为一个堆栈,可以多次压入和弹出,可以方便地执行一个像内部 重定向之类的东西。

关于在 Python 解释器中使用请求环境的更多内容参见 在 Shell 中使用 Flask 。

环境的工作原理

如果深入 Flask WSGI 应用内部,那么会找到类似如下代码:

def wsgi_app(self, environ):
    with self.request_context(environ):
        try:
            response = self.full_dispatch_request()
        except Exception, e:
            response = self.make_response(self.handle_exception(e))
        return response(environ, start_response)

request_context() 方法返回一个新的 RequestContext 对象,并且使用 with 语句把这个对象绑定 到环境。在 with 语句块中,在同一个线程中调用的所有东西可以访问全局请求 (flask.request 或其他)。

请求环境的工作方式就像一个堆栈,栈顶是当前活动请求。 push() 把环境压入堆栈中,而 pop() 把环境弹出。弹出的同时,会执行应用的 teardown_request() 函数。

另一件要注意的事情是:请求环境会在压入时自动创建一个 应用环境 。在此之前,应用没有应用环境。

回调和错误处理

如果在请求处理的过程中发生错误,那么 Flask 会如何处理呢?自 Flask 0.7 版本之后, 处理方式有所改变。这是为了更方便地反映到底发生了什么情况。新的处理方式非常简单:

  1. 在每个请求之前,会执行所有 before_request() 函数。如果 其中一个函数返回一个响应,那么其他函数将不再调用。但是在任何情况下,这个 返回值将会替代视图的返回值。
  2. 如果 before_request() 函数均没有响应,那么就会进行正常的 请求处理,匹配相应的视图,返回响应。
  3. 接着,视图的返回值会转换为一个实际的响应对象并交给 after_request() 函数处理。在处理过程中,这个对象可能会被 替换或修改。
  4. 请求处理的最后一环是执行 teardown_request() 函数。这类 函数在任何情况下都会被执行,甚至是在发生未处理异常或请求预处理器没有执行( 例如在测试环境下,有时不想执行)的情况下。

那么如果出错了会怎么样?在生产模式下,如果一个异常未被主要捕获处理,那么会调用 500 内部服务器处理器。在开发模式下,引发的异常不再被进一步处理,会提交给 WSGI 服务器。因此,需要使用交互调试器来查看调试信息。

Flask 0.7 版本的重大变化是内部服务器错误不再由请求后回调函数来处理,并且请求后 回调函数也不保证一定被执行。这样使得内部调试代码更整洁、更易懂和更容易定制。

同时还引入了新的卸载函数,这个函数在请求结束时一定会执行。

卸载回调函数

卸载回调函数的特殊之处在于其调用的时机是不固定的。严格地说,调用时机取决于 其绑定的 RequestContext 对象的生命周期。当请求环境弹出时就 会调用teardown_request() 函数。

请求环境的生命周期是会变化的,当请求环境位于测试客户端中的 with 语句中或者在 命令行下使用请求环境时,其生命周期会被延长。因此知道生命周期是否被延长是很重要 的:

with app.test_client() as client:
    resp = client.get('/foo')
    # 到这里还没有调用卸载函数。即使这时响应已经结束,并且已经
    # 获得响应对象,还是不会调用卸载函数。

# 只有到这里才会调用卸载函数。另外,如果另一个请求在客户端中被
# 激发,也会调用卸载函数。

在使用命令行时,可以清楚地看到运行方式:

>>> app = Flask(__name__)
>>> @app.teardown_request
... def teardown_request(exception=None):
...     print 'this runs after request'
...
>>> ctx = app.test_request_context()
>>> ctx.push()
>>> ctx.pop()
this runs after request
>>>

记牢记:卸载函数在任何情况下都会被执行,甚至是在请求预处理回调函数没有执行, 但是发生异常的情况下。有的测试系统可能会临时创建一个请求环境,但是不执行 预处理器。请正确使用卸载处理器,确保它们不会执行失败。

关于代理

部分 Flask 提供的对象是其他对象的代理。使用代理的原因是代理对象共享于不同的 线程,它们在后台根据需要把实际的对象分配给不同的线程。

多数情况下,你不需要关心这个。但是也有例外,在下列情况有下,知道对象是一个代理 对象是有好处的:

  • 想要执行真正的实例检查的情况。因为代理对象不会假冒被代理对象的对象类型, 因此,必须检查被代理的实际对象(参见下面的 _get_current_object )。
  • 对象引用非常重要的情况(例如发送 信号 )。

如果想要访问被代理的对象,可以使用 _get_current_object() 方法:

app = current_app._get_current_object()
my_signal.send(app)

出错时的环境保存

不管是否出错,在请求结束时,请求环境会被弹出,并且所有相关联的数据会被销毁。 但是在开发过程中,可能需要在出现异常时保留相关信息。在 Flask 0.6 版本及更早的 版本中,在发生异常时,请求环境不会被弹出,以便于交互调试器提供重要信息。

自 Flask 0.7 版本开始,可以通过设置 PRESERVE_CONTEXT_ON_EXCEPTION 配置变量 来更好地控制环境的保存。缺省情况下,这个配置变更与 DEBUG 变更关联。如果在 调试模式下,那么环境会被保留,而在生产模式下则不保留。

不要在生产环境下强制激活 PRESERVE_CONTEXT_ON_EXCEPTION ,因为这会在出现异常 时导致应用内存溢出。但是在调试模式下使用这个变更是十分有用的,你可以获得在生产 模式下出错时的环境。

使用蓝图的模块化应用

New in version 0.7.

为了在一个或多个应用中,使应用模块化并且支持常用方案, Flask 引入了 蓝图 概念。蓝图可以极大地简化大型应用并为扩展提供集中的注册入口。 Blueprint 对象与Flask 应用对象的工作方式类似,但不是一个真正 的应用。它更像一个用于构建和扩展应用的 蓝图 。

为什么使用蓝图?

Flask 中蓝图有以下用途:

  • 把一个应用分解为一套蓝图。这是针对大型应用的理想方案:一个项目可以实例化一个 应用,初始化多个扩展,并注册许多蓝图。
  • 在一个应用的 URL 前缀和(或)子域上注册一个蓝图。 URL 前缀和(或)子域的参数 成为蓝图中所有视图的通用视图参数(缺省情况下)。
  • 使用不同的 URL 规则在应用中多次注册蓝图。
  • 通过蓝图提供模板过滤器、静态文件、模板和其他工具。蓝图不必执行应用或视图 函数。
  • 当初始化一个 Flask 扩展时,为以上任意一种用途注册一个蓝图。

Flask 中的蓝图不是一个可插拨的应用,因为它不是一个真正的应用,而是一套可以注册 在应用中的操作,并且可以注册多次。那么为什么不使用多个应用对象呢?可以使用多个 应用对象(参见 应用调度 ),但是这样会导致每个应用都使用自己独立的 配置,且只能在 WSGI 层中管理应用。

而如果使用蓝图,那么应用会在 Flask 层中进行管理,共享配置,通过注册按需改变应用 对象。蓝图的缺点是一旦应用被创建后,只有销毁整个应用对象才能注销蓝图。

蓝图的概念

蓝图的基本概念是:在蓝图被注册到应用之后,所要执行的操作的集合。当分配请求时, Flask 会把蓝图和视图函数关联起来,并生成两个端点之前的 URL 。

第一个蓝图

以下是一个最基本的蓝图示例。在这里,我们将使用蓝图来简单地渲染静态模板:

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)

当你使用 @simple_page.route 装饰器绑定一个函数时,蓝图会记录下所登记的 show 函数。当以后在应用中注册蓝图时,这个函数会被注册到应用中。另外,它会把 构建Blueprint 时所使用的名称(在本例为 simple_page )作为函数端点 的前缀。

注册蓝图

可以这样注册蓝图:

from flask import Flask
from yourapplication.simple_page import simple_page

app = Flask(__name__)
app.register_blueprint(simple_page)

以下是注册蓝图后形成的规则:

[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
 <Rule '/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
 <Rule '/' (HEAD, OPTIONS, GET) -> simple_page.show>]

第一条很明显,是来自于应用本身的用于静态文件的。后面两条是用于蓝图simple_page 的 show 函数的。你可以看到,它们的前缀都是蓝图的名称,并且 使用一个点( . )来分隔。

蓝图还可以挂接到不同的位置:

app.register_blueprint(simple_page, url_prefix='/pages')

这样就会形成如下规则:

[<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
 <Rule '/pages/<page>' (HEAD, OPTIONS, GET) -> simple_page.show>,
 <Rule '/pages/' (HEAD, OPTIONS, GET) -> simple_page.show>]

总之,你可以多次注册蓝图,但是不一定每个蓝图都能正确响应。是否能够多次注册实际 上取决于你的蓝图是如何编写的,是否能根据不同的位置做出正确的响应。

蓝图资源

蓝图还可以用于提供资源。有时候,我们仅仅是为了使用一些资源而使用蓝图。

蓝图资源文件夹

和普通应用一样,蓝图一般都放在一个文件夹中。虽然多个蓝图可以共存于同一个文件夹 中,但是最好不要这样做。

文件夹由 Blueprint 的第二个参数指定,通常为 __name__ 。这个参数指定 与蓝图相关的逻辑 Python 模块或包。如果这个参数指向的是实际的 Python 包(文件 系统中的一个文件夹),那么它就是资源文件夹。如果是一个模块,那么这个模块包含的 包就是资源文件夹。可以通过 Blueprint.root_path 属性来查看蓝图的资源 文件夹:

>>> simple_page.root_path
'/Users/username/TestProject/yourapplication'

可以使用 open_resource() 函数快速打开这个文件夹中的资源:

with simple_page.open_resource('static/style.css') as f:
    code = f.read()
静态文件

蓝图的第三个参数是 static_folder 。这个参数用以指定蓝图的静态文件所在的 文件夹,它可以是一个绝对路径也可以是相对路径。:

admin = Blueprint('admin', __name__, static_folder='static')

缺省情况下,路径最右端的部分是在 URL 中暴露的部分。上例中的文件夹为 static ,那么 URL 应该是蓝图加上 /static 。蓝图注册为 /admin , 那么静态文件夹就是/admin/static 。

端点的名称是 blueprint_name.static ,因此你可以使用和应用中的文件夹一样的方法 来生成其 URL:

url_for('admin.static', filename='style.css')
模板

如果你想使用蓝图来暴露模板,那么可以使用 Blueprint 的 template_folder 参数:

admin = Blueprint('admin', __name__, template_folder='templates')

和静态文件一样,指向蓝图资源文件夹的路径可以是绝对的也可以是相对的。蓝图中的 模板文件夹会被添加到模板搜索路径中,但其优先级低于实际应用的模板文件夹。这样在 实际应用中可以方便地重载蓝图提供的模板。

假设你的蓝图便于 yourapplication/admin 中,要渲染的模板是 'admin/index.html', template_folder 参数值为 templates ,那么真正的 模板文件为:yourapplication/admin/templates/admin/index.html 。

更详细一点说:如果你有一个名为 admin 的蓝图,该蓝图指定的模版文件是 index.html,那么最好按照如下结构存放模版文件:

yourpackage/
    blueprints/
        admin/
            templates/
                admin/
                    index.html
            __init__.py

这样,当你需要渲染模板的时候就可以使用 admin/index.html 来找到模板。 如果没有载入正确的模板,那么应该启用 EXPLAIN_TEMPLATE_LOADING 配置变量。 启用这个变量以后,每次调用 render_template 时, Flask 会打印出定位模板的 步骤,方便调试。

创建 URL

如果要创建页面链接,可以和通常一样使用 url_for() 函数,只是要把蓝图名称作为端点的前缀,并且用一个点( . )来 分隔:

url_for('admin.index')

另外,如果在一个蓝图的视图函数或者被渲染的模板中需要链接同一个蓝图中的其他 端点,那么使用相对重定向,只使用一个点使用为前缀:

url_for('.index')

如果当前请求被分配到 admin 蓝图端点时,上例会链接到 admin.index 。

错误处理器

蓝图和 Flask 应用对象一样支持错误处理器装饰器,因此为蓝图自定义错误 处理页面非常方便。

下面是一个处理“ 404 页面无法找到 ”的例子:

@simple_page.errorhandler(404)
def page_not_found(e):
    return render_template('pages/404.html')

更多信息参见 errorpages 。

Flask 扩展

Flask 扩展以各种方式扩展了 Flask 的功能,比如增强对数据库的支持等等。

查找扩展

Flask 扩展都列在 Flask 扩展注册 中,并且可以使用 easy_install 或 pip 下载。如果你把一个扩展作为依赖添加到你的 requirements.rst 或 setup.py 文件,那么它们可以使用一个简单的命令安装或随着应用一起安装。

使用扩展

扩展一般都有说明如何使用的文档,这些文档应该和扩展一起发行。扩展如何运行没有 统一的要求,但是一般在常见位置导入扩展。假设一个扩展称为 Flask-Foo 或 Foo-Flask ,那么总是可以导入 flask.ext.foo:

from flask.ext import foo

Flask 0.8 以前的版本

如果你正在使用 Flask 0.7 版本或更早版本, flask.ext 包是不存在的。你 必须根据扩展的发行方式导入 flaskext.foo 或 flask_foo 。如果你要开发一个 支持 Flask 0.7 版本或更早版本的应用,那么你应当还是从 flask.ext 包中 导入。我们提供了一个兼容模块用以兼容老版本的 Flask ,你可以从 github 下载: flaskext_compat.py

使用方法如下:

import flaskext_compat
flaskext_compat.activate()

from flask.ext import foo

一旦 flaskext_compat 模块被激活, flask.ext 就会存在,就可以从这个 包导入扩展。

在 Shell 中使用 Flask

New in version 0.3.

喜欢 Python 的原因之一是交互式的 shell ,它可以让你实时运行 Python 命令,并且 立即得到结果。 Flask 本身不带交互 shell ,因为它不需要特定的前期设置,只要在 shell 中导入你的应用就可以开始使用了。

有些辅助工具可以让你在 shell 中更舒服。在交互终端中最大的问题是你不会像浏览器 一样触发一个请求,这就意味着无法使用 g 和 request 等对象。那么如何在 shell 中测试依赖这些对象的代码呢?

这里有一些有用的辅助函数。请记住,这些辅助函数不仅仅只能用于 shell ,还可以用于 单元测试和其他需要假冒请求环境的情况下。

在读下去之前最好你已经读过 请求环境 一节。

创建一个请求环境

在 shell 中创建一个正确的请求环境的最简便的方法是使用 test_request_context 方法。这个方法会创建一个 RequestContext :

>>> ctx = app.test_request_context()

通常你会使用 with 语句来激活请求对象,但是在 shell 中,可以简便地手动使用 push()和 pop() 方法:

>>> ctx.push()

从这里开始,直到调用 pop 之前,你可以使用请求对象:

>>> ctx.pop()

发送请求前/后动作

仅仅创建一个请求环境还是不够的,需要在请求前运行的代码还是没有运行。比如,在 请求前可以会需要转接数据库,或者把用户信息储存在 g 对象中。

使用 preprocess_request() 可以方便地模拟请求前/后动作:

>>> ctx = app.test_request_context()
>>> ctx.push()
>>> app.preprocess_request()

请记住, preprocess_request() 函数可以会返回一个响应对象。 如果返回的话请忽略它。

如果要关闭一个请求,那么你需要在请求后函数(由 process_response() 触发)作用于响应对象前关闭:

>>> app.process_response(app.response_class())
<Response 0 bytes [200 OK]>
>>> ctx.pop()

teardown_request() 函数会在环境弹出后自动执行。我们可以使用 这些函数来销毁请求环境所需要使用的资源(如数据库连接)。

在 Shell 中玩得更爽

如果你喜欢在 shell 中的感觉,那么你可以创建一个导入有关东西的模块,在模块中还 可以定义一些辅助方法,如初始化数据库或者删除表等等。假设这个模块名为 shelltools ,那么在开始时你可以:

>>> from shelltools import *

Flask 方案

有一些东西是大多数网络应用都会用到的。比如许多应用都会使用关系型数据库和用户 验证,在请求之前连接数据库并得到当前登录用户的信息,在请求之后关闭数据库连接。

更多用户贡献的代码片断和方案参见 Flask 代码片断归档 。

大型应用

对于大型应用来说使用包代替模块是一个好主意。使用包非常简单。假设有一个小应用如 下:

/yourapplication
    /yourapplication.py
    /static
        /style.css
    /templates
        layout.html
        index.html
        login.html
        ...
简单的包

要把上例中的小应用装换为大型应用只要在现有应用中创建一个名为 yourapplication 的新文件夹,并把所有东西都移动到这个文件夹内。然后把 yourapplication.py 更名 为__init__.py 。(请首先删除所有 .pyc 文件,否则基本上会出问题)

修改完后应该如下例:

/yourapplication
    /yourapplication
        /__init__.py
        /static
            /style.css
        /templates
            layout.html
            index.html
            login.html
            ...

但是现在如何运行应用呢?原本的 python yourapplication/__init__.py 无法运行 了。因为 Python 不希望包内的模块成为启动文件。但是这不是一个大问题,只要在yourapplication 文件夹旁添加一个 runserver.py 文件就可以了,其内容如下:

from yourapplication import app
app.run(debug=True)

我们从中学到了什么?现在我们来重构一下应用以适应多模块。只要记住以下几点:

  1. Flask 应用对象必须位于 __init__.py 文件中。这样每个模块就可以安全地导入 了,且 __name__ 变量会解析到正确的包。
  2. 所有视图函数(在顶端有 route() 的)必须在 __init__.py 文件中被导入。不是导入对象本身,而是导入视图模块。请 在应用对象创建之后 导入视图对象。

__init__.py 示例:

from flask import Flask
app = Flask(__name__)

import yourapplication.views

views.py 内容如下:

from yourapplication import app

@app.route('/')
def index():
    return 'Hello World!'

最终全部内容如下:

/yourapplication
    /runserver.py
    /yourapplication
        /__init__.py
        /views.py
        /static
            /style.css
        /templates
            layout.html
            index.html
            login.html
            ...

回环导入

回环导入是指两个模块互相导入,本例中我们添加的 views.py 就与 __init__.py 相互依赖。每个 Python 程序员都讨厌回环导入。一般情况下回环导入是个坏主意,但 在这里一点问题都没有。原因是我们没有真正使用 __init__.py 中的视图,只是 保证模块被导入,并且我们在文件底部才这样做。

但是这种方式还是有些问题,因为没有办法使用装饰器。要找到解决问题的灵感请参阅大型应用 一节。

使用蓝图

对于大型应用推荐把应用分隔为小块,每个小块使用蓝图辅助执行。关于这个主题的介绍 请参阅 使用蓝图的模块化应用 一节 。

应用工厂

如果你已经在应用中使用了包和蓝图( 使用蓝图的模块化应用 ),那么还有许多方法可以更 进一步地改进你的应用。常用的方案是导入蓝图后创建应用对象,但是如果在一个函数中 创建对象,那么就可以创建多个实例。

那么这样做有什么用呢?

  1. 用于测试。可以针对不同的情况使用不同的配置来测试应用。
  2. 用于多实例,如果你需要运行同一个应用的不同版本的话。当然你可以在服务器上 使用不同配置运行多个相同应用,但是如果使用应用工厂,那么你可以只使用一个 应用进程而得到多个应用实例,这样更容易操控。

那么如何做呢?

基础工厂

方法是在一个函数中设置应用,具体如下:

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

这个方法的缺点是在导入时无法在蓝图中使用应用对象。但是你可以在一个请求中使用它。 如何通过配置来访问应用?使用 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'])

这里我们在配置中查找模板的名称。

扩展对象初始化时不会绑定到一个应用,应用可以使用 db.init_app 来设置扩展。 扩展对象中不会储存特定应用的状态,因此一个扩展可以被多个应用使用。关于扩展设计 的更多信息请参阅 Flask 扩展开发 。

当使用 Flask-SQLAlchemy 时,你的 model.py 可能是这样的:

from flask.ext.sqlalchemy import SQLAlchemy
# no app object passed! Instead we use use db.init_app in the factory.
db = SQLAlchemy()

# create some models
使用应用

因此,要使用这样的应用就必须先创建它。下面是一个运行应用的示例 run.py 文件:

from yourapplication import create_app
app = create_app('/path/to/config.cfg')
app.run()
改进工厂

上面的工厂函数还不是足够好,可以改进的地方主要有以下几点:

  1. 为了单元测试,要想办法传入配置,这样就不必在文件系统中创建配置文件。
  2. 当设置应用时从蓝图调用一个函数,这样就可以有机会修改属性(如挂接请求前/后 处理器等)。
  3. 如果有必要的话,当创建一个应用时增加一个 WSGI 中间件。

应用调度

应用调度是在 WSGI 层面组合多个 WSGI 应用的过程。可以组合多个 Flask 应用,也可以 组合 Flask 应用和其他 WSGI 应用。通过这种组合,如果有必要的话,甚至可以在同一个 解释器中一边运行 Django ,一边运行 Flask 。这种组合的好处取决于应用内部是如何 工作的。

应用调度与 模块化 的最大不同在于应用调度中的每个 应用是完全独立的,它们以各自的配置运行,并在 WSGI 层面被调度。

说明

下面所有的技术说明和举例都归结于一个可以运行于任何 WSGI 服务器的 application对象。对于生产环境,参见 部署方式 。对于开发环境, Werkzeug 提供了一个内建开发服务器,它使用 werkzeug.serving.run_simple() 来运行:

from werkzeug.serving import run_simple
run_simple('localhost', 5000, application, use_reloader=True)

注意 run_simple 不能用于生产环境,生产 环境服务器参见 成熟的 WSGI 服务器 。

为了使用交互调试器,应用和简单服务器都应当处于调试模式。下面是一个简单的 “ hello world ”示例,使用了调试模式和 run_simple:

from flask import Flask
from werkzeug.serving import run_simple

app = Flask(__name__)
app.debug = True

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    run_simple('localhost', 5000, app,
               use_reloader=True, use_debugger=True, use_evalex=True)
组合应用

如果你想在同一个 Python 解释器中运行多个独立的应用,那么你可以使用werkzeug.wsgi.DispatcherMiddleware 。其原理是:每个独立的 Flask 应用都 是一个合法的 WSGI 应用,它们通过调度中间件组合为一个基于前缀调度的大应用。

假设你的主应用运行于 / ,后台接口位于 /backend:

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})
根据子域调度

有时候你可能需要使用不同的配置来运行同一个应用的多个实例。可以把应用创建过程 放在一个函数中,这样调用这个函数就可以创建一个应用的实例,具体实现参见 应用工厂 方案。

最常见的做法是每个子域创建一个应用,配置服务器来调度所有子域的应用请求,使用 子域来创建用户自定义的实例。一旦你的服务器可以监听所有子域,那么就可以使用一个 很简单的 WSGI 应用来动态创建应用了。

WSGI 层是完美的抽象层,因此可以写一个你自己的 WSGI 应用来监视请求,并把请求分配 给你的 Flask 应用。如果被分配的应用还没有创建,那么就会动态创建应用并被登记 下来:

from threading import Lock

class SubdomainDispatcher(object):

    def __init__(self, domain, create_app):
        self.domain = domain
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, host):
        host = host.split(':')[0]
        assert host.endswith(self.domain), 'Configuration error'
        subdomain = host[:-len(self.domain)].rstrip('.')
        with self.lock:
            app = self.instances.get(subdomain)
            if app is None:
                app = self.create_app(subdomain)
                self.instances[subdomain] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(environ['HTTP_HOST'])
        return app(environ, start_response)

调度器示例:

from myapplication import create_app, get_user_for_subdomain
from werkzeug.exceptions import NotFound

def make_app(subdomain):
    user = get_user_for_subdomain(subdomain)
    if user is None:
        # 如果子域没有对应的用户,那么还是得返回一个 WSGI 应用
        # 用于处理请求。这里我们把 NotFound() 异常作为应用返回,
        # 它会被渲染为一个缺省的 404 页面。然后,可能还需要把
        # 用户重定向到主页。
        return NotFound()

    # 否则为特定用户创建应用
    return create_app(user)

application = SubdomainDispatcher('example.com', make_app)
根据路径调度

根据 URL 的路径调度非常简单。上面,我们通过查找 Host 头来判断子域,现在 只要查找请求路径的第一个斜杠之前的路径就可以了:

from threading import Lock
from werkzeug.wsgi import pop_path_info, peek_path_info

class PathDispatcher(object):

    def __init__(self, default_app, create_app):
        self.default_app = default_app
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, prefix):
        with self.lock:
            app = self.instances.get(prefix)
            if app is None:
                app = self.create_app(prefix)
                if app is not None:
                    self.instances[prefix] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(peek_path_info(environ))
        if app is not None:
            pop_path_info(environ)
        else:
            app = self.default_app
        return app(environ, start_response)

与根据子域调度相比最大的不同是:根据路径调度时,如果创建函数返回 None ,那么 就会回落到另一个应用:

from myapplication import create_app, default_app, get_user_for_prefix

def make_app(prefix):
    user = get_user_for_prefix(prefix)
    if user is not None:
        return create_app(user)

application = PathDispatcher(default_app, make_app)

实现 API 异常处理

在 Flask 上经常会执行 RESTful API 。开发者首先会遇到的问题之一是用于 API 的内建 异常处理不给力,回馈的内容不是很有用。

对于非法使用 API ,比使用 abort 更好的解决方式是实现你自己的异常处理类型, 并安装相应句柄,输出符合用户格式要求的出错信息。

简单的异常类

基本的思路是引入一个新的异常,回馈一个合适的可读性高的信息、一个状态码和一些 可选的负载,给错误提供更多的环境内容。

以下是一个简单的示例:

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

这样一个视图就可以抛出带有出错信息的异常了。另外,还可以通过 payload 参数以 字典的形式提供一些额外的负载。

注册一个错误处理句柄

现在,视图可以抛出异常,但是会立即引发一个内部服务错误。这是因为没有为这个错误 处理类注册句柄。句柄增加很容易,例如:

@app.errorhandler(InvalidAPIUsage)
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)

URL 处理器

New in version 0.7.

Flask 0.7 引入了 URL 处理器,其作用是为你处理大量包含相同部分的 URL 。假设你有 许多 URL 都包含语言代码,但是又不想在每个函数中都重复处理这个语言代码,那么就可 可以使用 URL 处理器。

在与蓝图配合使用时, URL 处理器格外有用。下面我们分别演示在应用中和蓝图中使用 URL 处理器。

国际化应用的 URL

假设有应用如下:

from flask import Flask, g

app = Flask(__name__)

@app.route('/<lang_code>/')
def index(lang_code):
    g.lang_code = lang_code
    ...

@app.route('/<lang_code>/about')
def about(lang_code):
    g.lang_code = lang_code
    ...

上例中出现了大量的重复:必须在每一个函数中把语言代码赋值给 g 对象。当然,如果使用一个装饰器可以简化这个工作。但是,当你需要生成由一个函数 指向另一个函数的 URL 时,还是得显式地提供语言代码,相当麻烦。

我们使用 url_defaults() 函数来简化这个问题。这个函数可以自动 把值注入到url_for() 。以下代码检查在 URL 字典中是否存在语言代码, 端点是否需要一个名为'lang_code' 的值:

@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

URL 映射的 is_endpoint_expecting() 方法可用于检查 端点是否需要提供一个语言代码。

上例的逆向函数是 url_value_preprocessor() 。这些函数在请求 匹配后立即根据 URL 的值执行代码。它们可以从 URL 字典中取出值,并把取出的值放在 其他地方:

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

这样就不必在每个函数中把 lang_code 赋值给 g 了。你还可以作 进一步改进:写一个装饰器把语言代码作为 URL 的前缀。但是更好的解决方式是使用 蓝图。一旦 'lang_code'从值的字典中弹出,它就不再传送给视图函数了。精简后的 代码如下:

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():
    ...
国际化的蓝图 URL

因为蓝图可以自动给所有 URL 加上一个统一的前缀,所以应用到每个函数就非常方便了。 更进一步,因为蓝图 URL 预处理器不需要检查 URL 是否真的需要要一个'lang_code' 参数,所以可以去除 url_defaults() 函数中的 逻辑判断:

from flask import Blueprint, g

bp = Blueprint('frontend', __name__, url_prefix='/<lang_code>')

@bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code')

@bp.route('/')
def index():
    ...

@bp.route('/about')
def about():
    ...

使用 Distribute 部署

distribute 的前身是 setuptools ,它是一个扩展库,通常用于分发 Python 库和 扩展。它的英文名称的就是“分发”的意思。它扩展了 Python 自带的一个基础模块安装 系统 distutils ,支持多种更复杂的结构,方便了大型应用的分发部署。它的主要特色:

  • 支持依赖 :一个库或者应用可以声明其所依赖的其他库的列表。依赖库将被自动 安装。
  • 包注册 :可以在安装过程中注册包,这样就可以通过一个包查询其他包的信息。 这套系统最有名的功能是“切入点”,即一个包可以定义一个入口,以便于其他包挂接, 用以扩展包。
  • 安装管理 : distribute 中的 easy_install 可以为你安装其他库。你也可以 使用早晚会替代 easy_install 的 pip ,它除了安装包还可以做更多的事。

Flask 本身,以及其他所有在 cheeseshop 中可以找到的库要么是用 distribute 分发的, 要么是用老的 setuptools 或 distutils 分发的。

在这里我们假设你的应用名称是 yourapplication.py ,且没有使用模块,而是一个  。distribute 不支持分发标准模块,因此我们不 讨论模块的问题。关于如何把模块转换为包的信息参见 大型应用 方案。

使用 distribute 将使发布更复杂,也更加自动化。如果你想要完全自动化处理,请同时 阅读 使用 Fabric 部署 一节。

基础设置脚本

因为你已经安装了 Flask ,所以你应当已经安装了 setuptools 或 distribute 。如果 没有安装,不用怕,有一个 distribute_setup.py 脚本可以帮助你安装。只要下载这个 脚本,并在你的 Python 解释器中运行就可以了。

标准声明: 最好使用 virtualenv 。

你的设置代码应用放在 setup.py 文件中,这个文件应当位于应用旁边。这个文件名只是 一个约定,但是最好不要改变,因为大家都会去找这个文件。

是的,即使你使用 distribute ,你导入的包也是 setuptools 。 distribute 完全 向后兼容于 setuptools ,因此它使用相同的导入名称。

Flask 应用的基础 setup.py 文件示例如下:

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']
)

请记住,你必须显式的列出子包。如果你要 distribute 自动为你搜索包,你可以使用find_packages 函数:

from setuptools import setup, find_packages

setup(
    ...
    packages=find_packages()
)

大多数 setup 的参数可以望文生义,但是 include_package_data 和 zip_safe 可以不容易理解。 include_package_data 告诉 distribute 要搜索一个 MANIFEST.in 文件,把文件内容所匹配的所有条目作为包数据安装。可以通过使用这个 参数分发 Python 模块的静态文件和模板(参见 分发资源 )。 zip_safe 标志可用于强制或防止创建 zip 压缩包。通常你不会想要把包安装为 zip 压缩文件,因为一些工具不支持压缩文件,而且压缩文件比较难以调试。

分发资源

如果你尝试安装上文创建的包,你会发现诸如 static 或 templates 之类的文件夹 没有被安装。原因是 distribute 不知道要为你添加哪些文件。你要做的是:在你的 setup.py 文件旁边创建一个 MANIFEST.in 文件。这个文件列出了所有应当添加到 tar 压缩包的文件:

recursive-include yourapplication/templates *
recursive-include yourapplication/static *

不要忘了把 setup 函数的 include_package_data 参数设置为 True !否则即使把 内容在MANIFEST.in 文件中全部列出来也没有用。

声明依赖

依赖是在 install_requires 参数中声明的,这个参数是一个列表。列表中的每一项都是 一个需要在安装时从 PyPI 获得的包。缺省情况下,总是会获得最新版本的包,但你可以 指定最高版本和最低版本。示例:

install_requires=[
    'Flask>=0.2',
    'SQLAlchemy>=0.6',
    'BrokenPackage>=0.7,<=1.0'
]

我前面提到,依赖包都从 PyPI 获得的。但是如果要从别的地方获得包怎么办呢?你只要 还是按照上述方法写,然后提供一个可选地址列表就行了:

dependency_links=['http://example.com/yourfiles']

请确保页面上有一个目录列表,且页面上的链接指向正确的 tar 压缩包。这样 distribute 就会找到文件了。如果你的包在公司内部网络上,请提供指向服务器的 URL 。

安装 / 开发

要安装你的应用(理想情况下是安装到一个 virtualenv ),只要运行带 install 参数 的setup.py 脚本就可以了。它会将你的应用安装到 virtualenv 的 site-packages 文件夹下,同时下载并安装依赖:

$ python setup.py install

如果你正开发这个包,同时也希望相关依赖被安装,那么可以使用 develop 来代替:

$ python setup.py develop

这样做的好处是只安装一个指向 site-packages 的连接,而不是把数据复制到那里。这样 在开发过程中就不必每次修改以后再运行 install 了。

使用 Fabric 部署

Fabric 是一个 Python 工具,与 Makefiles 类似,但是能够在远程服务器上执行 命令。如果与适当的 Python 包( 大型应用 )与优良的配置( 配置管理 )相结合那么 Fabric 将是在外部服务器上部署 Flask 的利器。

在下文开始之前,有几点需要明确:

  • Fabric 1.0 需要要被安装到本地。本教程假设使用的是最新版本的 Fabric 。
  • 应用已经是一个包,且有一个可用的 setup.py 文件( 使用 Distribute 部署 )。
  • 在下面的例子中,我们假设远程服务器使用 mod_wsgi 。当然,你可以使用你自己 喜欢的服务器,但是在示例中我们选择 Apache + mod_wsgi ,因为它们设置方便, 且在没有 root 权限情况下可以方便的重载应用。
创建第一个 Fabfile

fabfile 是控制 Fabric 的东西,其文件名为 fabfile.py ,由 fab 命令执行。在 这个文件中定义的所有函数都会被视作 fab 子命令。这些命令将会在一个或多个主机上 运行。这些主机可以在 fabfile 中定义,也可以在命令行中定义。本例将在 fabfile 中 定义主机。

下面是第一个例子,比较级。它可以把当前的源代码上传至服务器,并安装到一个预先存在 的 virtual 环境:

from fabric.api import *

# 使用远程命令的用户名
env.user = 'appuser'
# 执行命令的服务器
env.hosts = ['server1.example.com', 'server2.example.com']

def pack():
    # 创建一个新的分发源,格式为 tar 压缩包
    local('python setup.py sdist --formats=gztar', capture=False)

def deploy():
    # 定义分发版本的名称和版本号
    dist = local('python setup.py --fullname', capture=True).strip()
    # 把 tar 压缩包格式的源代码上传到服务器的临时文件夹
    put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
    # 创建一个用于解压缩的文件夹,并进入该文件夹
    run('mkdir /tmp/yourapplication')
    with cd('/tmp/yourapplication'):
        run('tar xzf /tmp/yourapplication.tar.gz')
        # 现在使用 virtual 环境的 Python 解释器来安装包
        run('/var/www/yourapplication/env/bin/python setup.py install')
    # 安装完成,删除文件夹
    run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
    # 最后 touch .wsgi 文件,让 mod_wsgi 触发应用重载
    run('touch /var/www/yourapplication.wsgi')

上例中的注释详细,应当是容易理解的。以下是 fabric 提供的最常用命令的简要说明:

  • run - 在远程服务器上执行一个命令
  • local - 在本地机器上执行一个命令
  • put - 上传文件到远程服务器上
  • cd - 在服务器端改变目录。必须与 with 语句联合使用。
运行 Fabfile

那么如何运行 fabfile 呢?答案是使用 fab 命令。要在远程服务器上部署当前版本的 代码可以使用这个命令:

$ fab pack deploy

但是这个命令需要远程服务器上已经创建了 /var/www/yourapplication 文件夹,且/var/www/yourapplication/env 是一个 virtual 环境。更进一步,服务器上还没有 创建配置文件和 .wsgi 文件。那么,我们如何在一个新的服务器上创建一个基础环境 呢?

这个问题取决于你要设置多少台服务器。如果只有一台应用服务器(多数情况下),那么 在 fabfile 中创建命令有一点多余。当然,你可以这么做。这个命令可以称之为 setup或 bootstrap 。在使用命令时显式传递服务器名称:

$ fab -H newserver.example.com bootstrap

设置一个新服务器大致有以下几个步骤:

  1. 在 /var/www 创建目录结构:

    $ mkdir /var/www/yourapplication
    $ cd /var/www/yourapplication
    $ virtualenv --distribute env
    
  2. 上传一个新的 application.wsgi 文件和应用配置文件(如 application.cfg ) 到服务器上。

  3. 创建一个新的用于 yourapplication 的 Apache 配置并激活它。要确保激活 .wsgi 文件变动监视,这样在 touch 的时候可以自动重载应用。( 更多信息参见 mod_wsgi (Apache) )

现在的问题是: application.wsgi 和 application.cfg 文件从哪里来?

WSGI 文件

WSGI 文件必须导入应用,并且还必须设置一个环境变量用于告诉应用到哪里去搜索配置。 示例:

import os
os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg'
from yourapplication import app

应用本身必须像下面这样初始化自己才会根据环境变量搜索配置:

app = Flask(__name__)
app.config.from_object('yourapplication.default_config')
app.config.from_envvar('YOURAPPLICATION_CONFIG')

这个方法在 配置管理 一节已作了详细的介绍。

配置文件

上文已谈到,应用会根据 YOURAPPLICATION_CONFIG 环境变量找到正确的配置文件。 因此我们应当把配置文件放在应用可以找到的地方。在不同的电脑上配置文件是不同的, 所以一般我们不对配置文件作版本处理。

一个流行的方法是在一个独立的版本控制仓库为不同的服务器保存不同的配置文件,然后 在所有服务器进行检出。然后在需要的地方使用配置文件的符号链接(例如:/var/www/yourapplication )。

不管如何,我们这里只有一到两台服务器,因此我们可以预先手动上传配置文件。

第一次部署

现在我们可以进行第一次部署了。我已经设置好了服务器,因此服务器上应当已经有了 virtual 环境和已激活的 apache 配置。现在我们可以打包应用并部署它了:

$ fab pack deploy

Fabric 现在会连接所有服务器并运行 fabfile 中的所有命令。首先它会打包应用得到一个 tar 压缩包。然后会执行分发,把源代码上传到所有服务器并安装。感谢 setup.py 文件,所需要的依赖库会自动安装到 virtual 环境。

下一步

在前文的基础上,还有更多的方法可以全部署工作更加轻松:

  • 创建一个初始化新服务器的 bootstrap 命令。它可以初始化一个新的 virtual 环境、正确设置 apache 等等。
  • 把配置文件放入一个独立的版本库中,把活动配置的符号链接放在适当的地方。
  • 还可以把应用代码放在一个版本库中,在服务器上检出最新版本后安装。这样你可以 方便的回滚到老版本。
  • 挂接测试功能,方便部署到外部服务器进行测试。

使用 Fabric 是一件有趣的事情。你会发现在电脑上打出 fab deploy 是非常神奇的。 你可以看到你的应用被部署到一个又一个服务器上。

在 Flask 中使用 SQLite 3

在 Flask 中,你可以方便的按需打开数据库连接,并且在环境解散时关闭这个连接( 通常是请求结束的时候)。

以下是一个在 Flask 中使用 SQLite 3 的例子:

import sqlite3
from flask import g

DATABASE = '/path/to/database.db'

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = connect_to_database()
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

为了使用数据库,所有应用都必须准备好一个处于激活状态的环境。使用 get_db 函数可以得到数据库连接。当环境解散时,数据库连接会被切断。

注意:如果你使用的是 Flask 0.9 或者以前的版本,那么你必须使用flask._app_ctx_stack.top ,而不是 g 。因为 flask.g 对象是绑定到 请求的,而不是应用环境。

示例:

@app.route('/')
def index():
    cur = get_db().cursor()
    ...

Note

请记住,解散请求和应用环境的函数是一定会被执行的。即使请求前处理器执行失败或 根本没有执行,解散函数也会被执行。因此,我们必须保证在关闭数据库连接之前 数据库连接是存在的。

按需连接

上述方式(在第一次使用时连接数据库)的优点是只有在真正需要时才打开数据库连接。 如果你想要在一个请求环境之外使用数据库连接,那么你可以手动在 Python 解释器打开 应用环境:

with app.app_context():
    # now you can use get_db()
简化查询

现在,在每个请求处理函数中可以通过访问 g.db 来得到当前打开的数据库连接。为了 简化 SQLite 的使用,这里有一个有用的行工厂函数。该函数会转换每次从数据库返回的 结果。例如,为了得到字典类型而不是元组类型的返回结果,可以这样:

def make_dicts(cursor, row):
    return dict((cur.description[idx][0], value)
                for idx, value in enumerate(row))

db.row_factory = make_dicts

或者更简单的:

db.row_factory = sqlite3.Row

此外,把得到游标,执行查询和获得结果组合成一个查询函数不失为一个好办法:

def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

上述的方便的小函数与行工厂联合使用与使用原始的数据库游标和连接相比要方便多了。

可以这样使用上述函数:

for user in query_db('select * from users'):
    print user['username'], 'has the id', user['user_id']

只需要得到单一结果的用法:

user = query_db('select * from users where username = ?',
                [the_username], one=True)
if user is None:
    print 'No such user'
else:
    print the_username, 'has the id', user['user_id']

如果要给 SQL 语句传递参数,请在语句中使用问号来代替参数,并把参数放在一个列表中 一起传递。不要用字符串格式化的方式直接把参数加入 SQL 语句中,这样会给应用带来 SQL 注入 的风险。

初始化模式

关系数据库是需要模式的,因此一个应用常常需要一个 schema.sql 文件来创建 数据库。因此我们需要使用一个函数来其于模式创建数据库。下面这个函数可以完成这个 任务:

def init_db():
    with app.app_context():
        db = get_db()
        with app.open_resource('schema.sql', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

可以使用上述函数在 Python 解释器中创建数据库:

>>> from yourapplication import init_db
>>> init_db()

在 Flask 中使用 SQLAlchemy

许多人喜欢使用 SQLAlchemy 来访问数据库。建议在你的 Flask 应用中使用包来代替 模块,并把模型放入一个独立的模块中(参见 大型应用 )。虽然这 不是必须的,但是很有用。

有四种 SQLAlchemy 的常用方法,下面一一道来:

Flask-SQLAlchemy 扩展

因为 SQLAlchemy 是一个常用的数据库抽象层,并且需要一定的配置才能使用,因此我们 为你做了一个处理 SQLAlchemy 的扩展。如果你需要快速的开始使用 SQLAlchemy ,那么 推荐你使用这个扩展。

你可以从 PyPI 下载 Flask-SQLAlchemy 。

声明

SQLAlchemy 中的声明扩展是使用 SQLAlchemy 的最新方法,它允许你像 Django 一样, 在一个地方定义表和模型然后到处使用。除了以下内容,我建议你阅读 声明 的官方 文档。

以下是示例 database.py 模块:

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()

def init_db():
    # 在这里导入定义模型所需要的所有模块,这样它们就会正确的注册在
    # 元数据上。否则你就必须在调用 init_db() 之前导入它们。
    import yourapplication.models
    Base.metadata.create_all(bind=engine)

要定义模型的话,只要继承上面创建的 Base 类就可以了。你可能会奇怪这里为什么 不用理会线程(就像我们在 SQLite3 的例子中一样使用 g 对象)。 原因是 SQLAlchemy 已经用 scoped_session 为我们做好了此 类工作。

如果要在应用中以声明方式使用 SQLAlchemy ,那么只要把下列代码加入应用模块就可以 了。 Flask 会自动在请求结束时或者应用关闭时删除数据库会话:

from yourapplication.database import db_session

@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

以下是一个示例模型(放入 models.py 中):

from sqlalchemy import Column, Integer, String
from yourapplication.database import Base

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=True)
    email = Column(String(120), unique=True)

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

    def __repr__(self):
        return '<User %r>' % (self.name)

可以使用 init_db 函数来创建数据库:

>>> from yourapplication.database import init_db
>>> init_db()

在数据库中插入条目示例:

>>> from yourapplication.database import db_session
>>> from yourapplication.models import User
>>> u = User('admin', 'admin@localhost')
>>> db_session.add(u)
>>> db_session.commit()

查询很简单:

>>> User.query.all()
[<User u'admin'>]
>>> User.query.filter(User.name == 'admin').first()
<User u'admin'>
人工对象关系映射

人工对象关系映射相较于上面的声明方式有优点也有缺点。主要区别是人工对象关系映射 分别定义表和类并映射它们。这种方式更灵活,但是要多些代码。通常,这种方式与声明 方式一样运行,因此请确保把你的应用在包中分为多个模块。

示例 database.py 模块:

from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
metadata = MetaData()
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
def init_db():
    metadata.create_all(bind=engine)

就像声明方法一样,你需要在请求后或者应用环境解散后关闭会话。把以下代码放入你的 应用模块:

from yourapplication.database import db_session

@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

以下是一个示例表和模型(放入 models.py 中):

from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.orm import mapper
from yourapplication.database import metadata, db_session

class User(object):
    query = db_session.query_property()

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

    def __repr__(self):
        return '<User %r>' % (self.name)

users = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50), unique=True),
    Column('email', String(120), unique=True)
)
mapper(User, users)

查询和插入与声明方式的一样。

SQL 抽象层

如果你只需要使用数据库系统(和 SQL )抽象层,那么基本上只要使用引擎:

from sqlalchemy import create_engine, MetaData

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
metadata = MetaData(bind=engine)

然后你要么像前文中一样在代码中声明表,要么自动载入它们:

users = Table('users', metadata, autoload=True)

可以使用 insert 方法插入数据。为了使用事务,我们必须先得到一个连接:

>>> con = engine.connect()
>>> con.execute(users.insert(), name='admin', email='admin@localhost')

SQLAlchemy 会自动提交。

可以直接使用引擎或连接来查询数据库:

>>> users.select(users.c.id == 1).execute().first()
(1, u'admin', u'admin@localhost')

查询结果也是类字典元组:

>>> r = users.select(users.c.id == 1).execute().first()
>>> r['name']
u'admin'

你也可以把 SQL 语句作为字符串传递给 execute() 方法:

>>> engine.execute('select * from users where id = :1', [1]).first()
(1, u'admin', u'admin@localhost')

关于 SQLAlchemy 的更多信息请移步其 官方网站 。

上传文件

是的,这里要谈的是一个老问题:文件上传。文件上传的基本原理实际上很简单,基本上 是:

  1. 一个带有 enctype=multipart/form-data 的 <form> 标记,标记中含有 一个 <inputtype=file> 。
  2. 应用通过请求对象的 files 字典来访问文件。
  3. 使用文件的 save() 方法把文件永久 地保存在文件系统中。
简介

让我们从一个基本的应用开始,这个应用上传文件到一个指定目录,并把文件显示给用户。 以下是应用的引导代码:

import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

首先我们导入了一堆东西,大多数是浅显易懂的。werkzeug.secure_filename() 会 在稍后解释。UPLOAD_FOLDER 是上传文件要储存的目录,ALLOWED_EXTENSIONS 是允许 上传的文件扩展名的集合。接着我们给应用手动添加了一个 URL 规则。一般现在不会做 这个,但是为什么这么做了呢?原因是我们需要服务器(或我们的开发服务器)为我们提供 服务。因此我们只生成这些文件的 URL 的规则。

为什么要限制文件件的扩展名呢?如果直接向客户端发送数据,那么你可能不会想让用户 上传任意文件。否则,你必须确保用户不能上传 HTML 文件,因为 HTML 可能引起 XSS 问题(参见 跨站脚本攻击(XSS) )。如果服务器可以执行 PHP 文件,那么还必须确保不允许上传 .php 文件。但是谁又会在服务器上安装 PHP 呢,对不? :)

下一个函数检查扩展名是否合法,上传文件,把用户重定向到已上传文件的 URL:

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['file']
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form action="" method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''

那么 secure_filename() 函数到底是有什么用?有一条原则是“ 永远不要信任用户输入”。这条原则同样适用于已上传文件的文件名。所有提交的表单数据 可能是伪造的,文件名也可以是危险的。此时要谨记:在把文件保存到文件系统之前总是要 使用这个函数对文件名进行安检。

进一步说明

你可以会好奇 secure_filename() 做了哪些工作,如果不使用 它会有什么后果。假设有人把下面的信息作为 filename 传递给你的应用:

filename = "../../../../home/username/.bashrc"

假设 ../ 的个数是正确的,你会把它和 UPLOAD_FOLDER 结合在一起,那么用户 就可能有能力修改一个服务器上的文件,这个文件本来是用户无权修改的。这需要了解 应用是如何运行的,但是请相信我,黑客都是病态的 :)

现在来看看函数是如何工作的:

>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'

现在还剩下一件事:为已上传的文件提供服务。 Flask 0.5 版本开始我们可以使用一个 函数来完成这个任务:

from flask import send_from_directory

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'],
                               filename)

另外,可以把 uploaded_file 注册为 build_only 规则,并使用SharedDataMiddleware 。这种方式可以在 Flask 老版本中 使用:

from werkzeug import SharedDataMiddleware
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
                 build_only=True)
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
    '/uploads':  app.config['UPLOAD_FOLDER']
})

如果你现在运行应用,那么应该一切都应该按预期正常工作。

改进上传

New in version 0.6.

Flask 到底是如何处理文件上传的呢?如果上传的文件很小,那么会把它们储存在内存中。 否则就会把它们保存到一个临时的位置(通过 tempfile.gettempdir() 可以得到 这个位置)。但是,如何限制上传文件的尺寸呢?缺省情况下, Flask 是不限制上传文件 的尺寸的。可以通过设置配置的 MAX_CONTENT_LENGTH 来限制文件尺寸:

from flask import Flask, Request

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024

上面的代码会把尺寸限制为 16 M 。如果上传了大于这个尺寸的文件, Flask 会抛出一个RequestEntityTooLarge 异常。

Flask 0.6 版本中添加了这个功能。但是通过继承请求对象,在较老的版本中也可以实现 这个功能。更多信息请参阅 Werkzeug 关于文件处理的文档。

上传进度条

在不久以前,许多开发者是这样实现上传进度条的:分块读取上传的文件,在数据库中储存 上传的进度,然后在客户端通过 JavaScript 获取进度。简而言之,客户端每 5 秒钟向 服务器询问一次上传进度。觉得讽刺吗?客户端在明知故问。

现在有了更好的解决方案,更快且更可靠。网络发生了很大变化,你可以在客户端使用 HTML5 、 JAVA 、 Silverlight 或 Flash 获得更好的上传体验。请查看以下库,学习 一些优秀的上传的示例:

一个更简便的方案

因为所有应用中上传文件的方案基本相同,因此可以使用 Flask-Uploads 扩展来实现 文件上传。这个扩展实现了完整的上传机制,还具有白名单功能、黑名单功能以及其他 功能。

缓存

当你的应用变慢的时候,可以考虑加入缓存。至少这是最简单的加速方法。缓存有什么用? 假设有一个函数耗时较长,但是这个函数在五分钟前返回的结果还是正确的。那么我们就 可以考虑把这个函数的结果在缓存中存放一段时间。

Flask 本身不提供缓存,但是它的基础库之一 Werkzeug 有一些非常基本的缓存支持。它 支持多种缓存后端,通常你需要使用的是 memcached 服务器。

设置一个缓存

与创建 Flask 对象类似,你需要创建一个缓存对象并保持它。如果你 正在使用开发服务器,那么你可以创建一个 SimpleCache 对象。这个对象提供简单的缓存,它把 缓存项目保存在 Python 解释器的内存中:

from werkzeug.contrib.cache import SimpleCache
cache = SimpleCache()

如果你要使用 memcached ,那么请确保有 memcache 模块支持(你可以从 PyPI 获得模块)和一个正在运行的 memcached 服务器。 连接 memcached 服务器示例:

from werkzeug.contrib.cache import MemcachedCache
cache = MemcachedCache(['127.0.0.1:11211'])

如果你正在使用 App Engine ,那么你可以方便地连接到 App Engine memcache 服务器:

from werkzeug.contrib.cache import GAEMemcachedCache
cache = GAEMemcachedCache()
使用缓存

现在的问题是如何使用缓存呢?有两个非常重要的操作: get() 和 set() 。下面展示如何使用它们:

get() 用于从缓存中获得项目,调用时使用 一个字符串作为键名。如果项目存在,那么就会返回这个项目,否则返回 None:

rv = cache.get('my-item')

set() 用于把项目添加到缓存中。第一个参数 是键名,第二个参数是键值。还可以提供一个超时参数,当超过时间后项目会自动删除。

下面是一个完整的例子:

def get_my_item():
    rv = cache.get('my-item')
    if rv is None:
        rv = calculate_value()
        cache.set('my-item', rv, timeout=5 * 60)
    return rv

视图装饰器

Python 有一个非常有趣的功能:函数装饰器。这个功能可以使网络应用干净整洁。 Flask 中的每个视图都是一个装饰器,它可以被注入额外的功能。你可以已经用过了route() 装饰器。但是,你有可能需要使用你自己的装饰器。假设有 一个视图,只有已经登录的用户才能使用。如果用户访问时没有登录,则会被重定向到 登录页面。这种情况下就是使用装饰器的绝佳机会。

检查登录装饰器

让我们来实现这个装饰器。装饰器是一个返回函数的函数。听上去复杂,其实很简单。只要 记住一件事:装饰器用于更新函数的 __name__ 、 __module__ 和其他属性。这一点很 容易忘记,但是你不必人工更新函数属性,可以使用一个类似于装饰器的函数(functools.wraps() )。

下面是检查登录装饰器的例子。假设登录页面为 'login' ,当前用户被储存在 g.user中,如果还没有登录,其值为 None:

from functools import wraps
from flask import g, request, redirect, url_for

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if g.user is None:
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

如何使用这个装饰器呢?把这个装饰器放在最靠近函数的地方就行了。当使用更进一步的 装饰器时,请记住要把 route() 装饰器放在最外面:

@app.route('/secret_page')
@login_required
def secret_page():
    pass
缓存装饰器

假设有一个视图函数需要消耗昂贵的计算成本,因此你需要在一定时间内缓存这个视图的 计算结果。这种情况下装饰器是一个好的选择。我们假设你像 缓存 方案中一样设置了缓存。

下面是一个示例缓存函数。它根据一个特定的前缀(实际上是一个格式字符串)和请求的 当前路径生成缓存键。注意,我们先使用了一个函数来创建装饰器,这个装饰器用于装饰 函数。听起来拗口吧,确实有一点复杂,但是下面的示例代码还是很容易读懂的。

被装饰代码按如下步骤工作

  1. 基于基础路径获得当前请求的唯一缓存键。
  2. 从缓存中获取键值。如果获取成功则返回获取到的值。
  3. 否则调用原来的函数,并把返回值存放在缓存中,直至过期(缺省值为五分钟)。

代码:

from functools import wraps
from flask import request

def cached(timeout=5 * 60, key='view/%s'):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            cache_key = key % request.path
            rv = cache.get(cache_key)
            if rv is not None:
                return rv
            rv = f(*args, **kwargs)
            cache.set(cache_key, rv, timeout=timeout)
            return rv
        return decorated_function
    return decorator

注意,以上代码假设存在一个可用的实例化的 cache 对象,更多信息参见 缓存 方案。

模板装饰器

不久前, TurboGear 的人发明了模板装饰器这个通用模式。其工作原理是返回一个字典, 这个字典包含从视图传递给模板的值,模板自动被渲染。以下三个例子的功能是相同的:

@app.route('/')
def index():
    return render_template('index.html', value=42)

@app.route('/')
@templated('index.html')
def index():
    return dict(value=42)

@app.route('/')
@templated()
def index():
    return dict(value=42)

正如你所见,如果没有提供模板名称,那么就会使用 URL 映射的端点(把点转换为斜杠) 加上 '.html' 。如果提供了,那么就会使用所提供的模板名称。当装饰器函数返回 时,返回的字典就被传送到模板渲染函数。如果返回的是 None ,就会使用空字典。如果 返回的不是字典,那么就会直接传递原封不动的返回值。这样就可以仍然使用重定向函数或 返回简单的字符串。

以下是装饰器的代码:

from functools import wraps
from flask import request

def templated(template=None):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            template_name = template
            if template_name is None:
                template_name = request.endpoint \
                    .replace('.', '/') + '.html'
            ctx = f(*args, **kwargs)
            if ctx is None:
                ctx = {}
            elif not isinstance(ctx, dict):
                return ctx
            return render_template(template_name, **ctx)
        return decorated_function
    return decorator
端点装饰器

当你想要使用 werkzeug 路由系统,以便于获得更强的灵活性时,需要和 Rule 中定义的一样,把端点映射到视图函数。这样就需要 用的装饰器了。例如:

from flask import Flask
from werkzeug.routing import Rule

app = Flask(__name__)
app.url_map.add(Rule('/', endpoint='index'))

@app.endpoint('index')
def my_index():
    return "Hello world"

使用 WTForms 进行表单验证

当你必须处理浏览器提交的表单数据时,视图代码很快会变得难以阅读。有一些库可以 简化这个工作,其中之一便是 WTForms ,下面我们将介绍这个库。如果你必须处理 许多表单,那么应当尝试使用这个库。

如果要使用 WTForms ,那么首先要把表单定义为类。我推荐把应用分割为多个模块(大型应用 ),并为表单添加一个独立的模块。

使用一个扩展获得大部分 WTForms 的功能

Flask-WTF 扩展可以实现本方案的所有功能,并且还提供一些小的辅助工具。使用 这个扩展可以更好的使用表单和 Flask 。你可以从 PyPI 获得这个扩展。

表单

下面是一个典型的注册页面的示例:

from wtforms import Form, BooleanField, TextField, PasswordField, validators

class RegistrationForm(Form):
    username = TextField('Username', [validators.Length(min=4, max=25)])
    email = TextField('Email Address', [validators.Length(min=6, max=35)])
    password = PasswordField('New Password', [
        validators.Required(),
        validators.EqualTo('confirm', message='Passwords must match')
    ])
    confirm = PasswordField('Repeat Password')
    accept_tos = BooleanField('I accept the TOS', [validators.Required()])
视图

在视图函数中,表单用法示例如下:

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm(request.form)
    if request.method == 'POST' and form.validate():
        user = User(form.username.data, form.email.data,
                    form.password.data)
        db_session.add(user)
        flash('Thanks for registering')
        return redirect(url_for('login'))
    return render_template('register.html', form=form)

注意,这里我们默认视图使用了 SQLAlchemy ( 在 Flask 中使用 SQLAlchemy ),当然这 不是必须的。请根据你的实际情况修改代码。

请记住以下几点:

  1. 如果数据是通过 HTTP POST 方法提交的,请根据 form 的 值创建表单。如果是通过GET 方法提交的,则相应的是 args 。
  2. 调用 validate() 函数来验证数据。如果验证通过,则 函数返回 True ,否则返回False 。
  3. 通过 form.<NAME>.data 可以访问表单中单个值。
模板中的表单

现在我们来看看模板。把表单传递给模板后就可以轻松渲染它们了。看一看下面的示例 模板就可以知道有多轻松了。 WTForms 替我们完成了一半表单生成工作。为了做得更好, 我们可以写一个宏,通过这个宏渲染带有一个标签的字段和错误列表(如果有的话)。

以下是一个使用宏的示例 _formhelpers.html 模板:

{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
    <ul class=errors>
    {% for error in field.errors %}
      <li>{{ error }}</li>
    {% endfor %}
    </ul>
  {% endif %}
  </dd>
{% endmacro %}

上例中的宏接受一堆传递给 WTForm 字段函数的参数,为我们渲染字段。参数会作为 HTML 属性插入。例如你可以调用 render_field(form.username,class='username') 来 为输入元素添加一个类。注意: WTForms 返回标准的 Python unicode 字符串,因此我们 必须使用 |safe 过滤器告诉 Jinja2 这些数据已经经过 HTML 转义了。

以下是使用了上面的 _formhelpers.html 的 register.html 模板:

{% from "_formhelpers.html" import render_field %}
<form method=post action="/register">
  <dl>
    {{ render_field(form.username) }}
    {{ render_field(form.email) }}
    {{ render_field(form.password) }}
    {{ render_field(form.confirm) }}
    {{ render_field(form.accept_tos) }}
  </dl>
  <p><input type=submit value=Register>
</form>

更多关于 WTForms 的信息请移步 WTForms 官方网站 。

模板继承

Jinja 最有力的部分就是模板继承。模板继承允许你创建一个基础“骨架”模板。这个模板 中包含站点的常用元素,定义可以被子模板继承的  。

听起来很复杂其实做起来简单,看看下面的例子就容易理解了。

基础模板

这个模板的名称是 layout.html ,它定义了一个简单的 HTML 骨架,用于显示一个 简单的两栏页面。“子”模板的任务是用内容填充空的块:

<!doctype html>
<html>
  <head>
    {% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
  </head>
  <body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
      {% block footer %}
      &copy; Copyright 2010 by <a href="http://domain.invalid/">you</a>.
      {% endblock %}
    </div>
  </body>
</html>

在这个例子中, {% block %} 标记定义了四个可以被子模板填充的块。 block 标记告诉模板引擎这是一个可以被子模板重载的部分。

子模板

子模板示例:

{% extends "layout.html" %}
{% block title %}Index{% endblock %}
{% block head %}
  {{ super() }}
  <style type="text/css">
    .important { color: #336699; }
  </style>
{% endblock %}
{% block content %}
  <h1>Index</h1>
  <p class="important">
    Welcome on my awesome homepage.
{% endblock %}

这里 {% extends %} 标记是关键,它告诉模板引擎这个模板“扩展”了另一个模板, 当模板系统评估这个模板时会先找到父模板。这个扩展标记必须是模板中的第一个标记。 如果要使用父模板中的块内容,请使用 {{ super() }} 。

消息闪现

一个好的应用和用户界面都需要良好的反馈。如果用户得不到足够的反馈,那么应用最终 会被用户唾弃。 Flask 的闪现系统提供了一个良好的反馈方式。闪现系统的基本工作方式 是:在且只在下一个请求中访问上一个请求结束时记录的消息。一般我们结合布局模板来 使用闪现系统。

简单的例子

以下是一个完整的示例:

from flask import Flask, flash, redirect, render_template, \
     request, url_for

app = Flask(__name__)
app.secret_key = 'some_secret'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    error = None
    if request.method == 'POST':
        if request.form['username'] != 'admin' or \
           request.form['password'] != 'secret':
            error = 'Invalid credentials'
        else:
            flash('You were successfully logged in')
            return redirect(url_for('index'))
    return render_template('login.html', error=error)

if __name__ == "__main__":
    app.run()

以下是实现闪现的 layout.html 模板:

<!doctype html>
<title>My Application</title>
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class=flashes>
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}
{% block body %}{% endblock %}

以下是 index.html 模板:

{% extends "layout.html" %}
{% block body %}
  <h1>Overview</h1>
  <p>Do you want to <a href="{{ url_for('login') }}">log in?</a>
{% endblock %}

login 模板:

{% extends "layout.html" %}
{% block body %}
  <h1>Login</h1>
  {% if error %}
    <p class=error><strong>Error:</strong> {{ error }}
  {% endif %}
  <form action="" method=post>
    <dl>
      <dt>Username:
      <dd><input type=text name=username value="{{
          request.form.username }}">
      <dt>Password:
      <dd><input type=password name=password>
    </dl>
    <p><input type=submit value=Login>
  </form>
{% endblock %}
闪现消息的类别

New in version 0.3.

闪现消息还可以指定类别,如果没有指定,那么缺省的类别为 'message' 。不同的 类别可以给用户提供更好的反馈。例如错误消息可以使用红色背景。

使用 flash() 函数可以指定消息的类别:

flash(u'Invalid password provided', 'error')

模板中的 get_flashed_messages() 函数也应当返回类别,显示消息的循环 也要略作改变:

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class=flashes>
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

上例展示如何根据类别渲染消息,还可以给消息加上前缀,如 <strong>Error:</strong> 。

过滤闪现消息

New in version 0.9.

你可以视情况通过传递一个类别列表来过滤 get_flashed_messages() 的 结果。这个功能有助于在不同位置显示不同类别的消息。

{% with errors = get_flashed_messages(category_filter=["error"]) %}
{% if errors %}
<div class="alert-message block-message error">
  <a class="close" href="#"</a>
  <ul>
    {%- for msg in errors %}
    <li>{{ msg }}</li>
    {% endfor -%}
  </ul>
</div>
{% endif %}
{% endwith %}

通过 jQuery 使用 AJAX

jQuery 是一个小型的 JavaScript 库,通常用于简化 DOM 和 JavaScript 的使用。它 是一个非常好的工具,可以通过在服务端和客户端交换 JSON 来使网络应用更具有动态性。

JSON 是一种非常轻巧的传输格式,非常类似于 Python 原语(数字、字符串、字典和列表 )。 JSON 被广泛支持,易于解析。 JSON 在几年之前开始流行,在网络应用中迅速取代 了 XML 。

载入 jQuery

为了使用 jQuery ,你必须先把它下载下来,放在应用的静态目录中,并确保它被载入。 理想情况下你有一个用于所有页面的布局模板。在这个模板的 <body> 的底部添加一个 script 语句来载入 jQuery :

<script type=text/javascript src="{{
  url_for('static', filename='jquery.js') }}"></script>

另一个方法是使用 Google 的 AJAX 库 API 来载入 jQuery:

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="{{
  url_for('static', filename='jquery.js') }}">\x3C/script>')</script>

在这种方式中,应用会先尝试从 Google 下载 jQuery ,如果失败则会调用静态目录中的 备用 jQuery 。这样做的好处是如果用户已经去过使用与 Google 相同版本的 jQuery 的 网站后,访问你的网站时,页面可能会更快地载入,因为浏览器已经缓存了 jQuery 。

我的网站在哪里?

我的网站在哪里?如果你的应用还在开发中,那么答案很简单:它在本机的某个端口上, 且在服务器的根路径下。但是如果以后要把应用移到其他位置(例如http://example.com/myapp )上呢?在服务端,这个问题不成为问题,可以使用url_for() 函数来得到答案。但是如果我们使用 jQuery ,那么就不能硬码应用的路径,只能使用动态路径。怎么办?

一个简单的方法是在页面中添加一个 script 标记,设置一个全局变量来表示应用的根 路径。示例:

<script type=text/javascript>
  $SCRIPT_ROOT = {{ request.script_root|tojson|safe }};
</script>

在 Flask 0.10 版本以前版本中,使用 |safe 是有必要的,是为了使 Jinja 不要 转义 JSON 编码的字符串。通常这样做不是必须的,但是在 script 内部我们需要这么 做。

进一步说明

在 HTML 中, script 标记是用于声明 CDATA 的,也就是说声明的内容不会被 解析。<script> 与 </script> 之间的内容都会被作为脚本处理。这也意味着 在 script 标记之间不会存在任何 </ 。在这里 |tojson 会正确处理问题, 并为你转义斜杠( {{ "</script>"|tojson|safe }} 会被渲染为 "<\/script>" )。

在 Flask 0.10 版本中更进了一步,把所有 HTML 标记都用 unicode 转义了,这样使 Flask 自动把 HTML 转换为安全标记。

JSON 视图函数

现在让我们来创建一个服务端函数,这个函数接收两个 URL 参数(两个需要相加的数字 ),然后向应用返回一个 JSON 对象。下面这个例子是非常不实用的,因为一般会在 客户端完成类似工作,但这个例子可以简单明了地展示如何使用 jQuery 和 Flask:

from flask import Flask, jsonify, render_template, request
app = Flask(__name__)

@app.route('/_add_numbers')
def add_numbers():
    a = request.args.get('a', 0, type=int)
    b = request.args.get('b', 0, type=int)
    return jsonify(result=a + b)

@app.route('/')
def index():
    return render_template('index.html')

正如你所见,我还添加了一个 index 方法来渲染模板。这个模板会按前文所述载入 jQuery 。模板中有一个用于两个数字相加的表单和一个触发服务端函数的链接。

注意,这里我们使用了 get() 方法。它 不会调用失败。如果字典的键不存在,就会返回一个缺省值(这里是 0 )。更进一步 它还会把值转换为指定的格式(这里是 int )。在脚本( API 、 JavaScript 等) 触发的代码中使用它特别方便,因为在这种情况下不需要特殊的错误报告。

HTML

你的 index.html 模板要么继承一个已经载入 jQuery 和设置好 $SCRIPT_ROOT 变量的layout.html 模板,要么在模板开头就做好那两件事。下面就是应用的 HTML 示例 (index.html )。注意,我们把脚本直接放入了 HTML 中。通常更好的方式是放在 独立的脚本文件中:

<script type=text/javascript>
  $(function() {
    $('a#calculate').bind('click', function() {
      $.getJSON($SCRIPT_ROOT + '/_add_numbers', {
        a: $('input[name="a"]').val(),
        b: $('input[name="b"]').val()
      }, function(data) {
        $("#result").text(data.result);
      });
      return false;
    });
  });
</script>
<h1>jQuery Example</h1>
<p><input type=text size=5 name=a> +
   <input type=text size=5 name=b> =
   <span id=result>?</span>
<p><a href=# id=calculate>calculate server side</a>

这里不讲述 jQuery 运行详细情况,仅对上例作一个简单说明:

  1. $(function() ... }) 定义浏览器在页面的基本部分载入完成后立即执行的. 代码。
  2. $('selector') 选择一个元素供你操作。
  3. element.bind('event', func) 定义一个用户点击元素时运行的函数。如果函数 返回 false ,那么缺省行为就不会起作用(本例为转向 # URL )。
  4. $.getJSON(url, data, func) 向 url 发送一个 GET 请求,并把 data 对象的内容作为查询参数。一旦有数据返回,它将调用指定的函数,并把返回值作为 函数的参数。注意,我们可以在这里使用先前定义的 $SCRIPT_ROOT 变量。

如果你没有一个完整的概念,请从 github 下载 示例源代码 。

自定义出错页面

Flask 有一个方便的 abort() 函数,它可以通过一个 HTTP 出错代码退出 一个请求。它还提供一个包含基本说明的出错页面,页面显示黑白的文本,很朴素。

用户可以根据错误代码或多或少知道发生了什么错误。

常见出错代码

以下出错代码是用户常见的,即使应用正常也会出现这些出错代码:

404 Not Found
这是一个古老的“朋友,你使用了一个错误的 URL ”信息。这个信息出现得如此 频繁,以至于连刚上网的新手都知道 404 代表:该死的,我要看的东西不见了。 一个好的做法是确保 404 页面上有一些真正有用的东西,至少要有一个返回首页 的链接。
403 Forbidden
如果你的网站上有某种权限控制,那么当用户访问未获授权内容时应当发送 403 代码。因此请确保当用户尝试访问未获授权内容时得到正确的反馈。
410 Gone
你知道 “404 Not Found” 有一个名叫 “410 Gone” 的兄弟吗?很少有人使用这个 代码。如果资源以前曾经存在过,但是现在已经被删除了,那么就应该使用 410 代码,而不是 404 。如果你不是在数据库中把文档永久地删除,而只是给文档打 了一个删除标记,那么请为用户考虑,应当使用 410 代码,并显示信息告知用户 要找的东西已经删除。
500 Internal Server Error
这个代码通常表示程序出错或服务器过载。强烈建议把这个页面弄得友好一点, 因为你的应用 迟早 会出现故障的(参见 掌握应用错误 )。
出错处理器

一个出错处理器是一个函数,就像一个视图函数一样。与视图函数不同之处在于出错处理器 在出现错误时被调用,且传递错误。错误大多数是一个 HTTPException ,但是有一个例外:当出现内部服务器错误 时会把异常实例传递给出错处理器。

出错处理器使用 errorhandler() 装饰器注册,注册时应提供异常的 出代码。请记住, Flask 不会 为你设置出错代码,因此请确保在返回响应时,同时提供 HTTP 状态代码。

以下是一个处理 “404 Page Not Found” 异常的示例:

from flask import render_template

@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

示例模板:

{% extends "layout.html" %}
{% block title %}Page Not Found{% endblock %}
{% block body %}
  <h1>Page Not Found</h1>
  <p>What you were looking for is just not there.
  <p><a href="{{ url_for('index') }}">go somewhere nice</a>
{% endblock %}

惰性载入视图

Flask 通常使用装饰器。装饰器简单易用,只要把 URL 放在相应的函数的前面就可以了。 但是这种方式有一个缺点:使用装饰器的代码必须预先导入,否则 Flask 就无法真正找到 你的函数。

当你必须快速导入应用时,这就会成为一个问题。在 Google App Engine 或其他系统中, 必须快速导入应用。因此,如果你的应用存在这个问题,那么必须使用集中 URL 映射。

add_url_rule() 函数用于集中 URL 映射,与使用装饰器不同的是你 需要一个设置应用所有 URL 的专门文件。

转换为集中 URL 映射

假设有如下应用:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    pass

@app.route('/user/<username>')
def user(username):
    pass

为了集中映射,我们创建一个不使用装饰器的文件( views.py ):

def index():
    pass

def user(username):
    pass

在另一个文件中集中映射函数与 URL:

from flask import Flask
from yourapplication import views
app = Flask(__name__)
app.add_url_rule('/', view_func=views.index)
app.add_url_rule('/user/<username>', view_func=views.user)
延迟载入

至此,我们只是把视图与路由分离,但是模块还是预先载入了。理想的方式是按需载入 视图。下面我们使用一个类似函数的辅助类来实现按需载入:

from werkzeug import import_string, cached_property

class LazyView(object):

    def __init__(self, import_name):
        self.__module__, self.__name__ = import_name.rsplit('.', 1)
        self.import_name = import_name

    @cached_property
    def view(self):
        return import_string(self.import_name)

    def __call__(self, *args, **kwargs):
        return self.view(*args, **kwargs)

上例中最重要的是正确设置 __module__ 和 __name__ ,它被用于在不提供 URL 规则 的情况下正确命名 URL 规则。

然后可以这样集中定义 URL 规则:

from flask import Flask
from yourapplication.helpers import LazyView
app = Flask(__name__)
app.add_url_rule('/',
                 view_func=LazyView('yourapplication.views.index'))
app.add_url_rule('/user/<username>',
                 view_func=LazyView('yourapplication.views.user'))

还可以进一步优化代码:写一个函数调用 add_url_rule() ,加上 应用前缀和点符号:

def url(url_rule, import_name, **options):
    view = LazyView('yourapplication.' + import_name)
    app.add_url_rule(url_rule, view_func=view, **options)

url('/', 'views.index')
url('/user/<username>', 'views.user')

有一件事情要牢记:请求前和请求后处理器必须在第一个请求前导入。

其余的装饰器可以同样用上述方法改写。

在 Flask 中使用 MongoKit

现在使用文档型数据库来取代关系型数据库已越来越常见。本方案展示如何使用 MongoKit ,它是一个用于操作 MongoDB 的文档映射库。

本方案需要一个运行中的 MongoDB 服务器和已安装好的 MongoKit 库。

使用 MongoKit 有两种常用的方法,下面逐一说明:

声明

声明是 MongoKit 的缺省行为。这个思路来自于 Django 或 SQLAlchemy 的声明。

下面是一个示例 app.py 模块:

from flask import Flask
from mongokit import Connection, Document

# configuration
MONGODB_HOST = 'localhost'
MONGODB_PORT = 27017

# create the little application object
app = Flask(__name__)
app.config.from_object(__name__)

# connect to the database
connection = Connection(app.config['MONGODB_HOST'],
                        app.config['MONGODB_PORT'])

如果要定义模型,那么只要继承 MongoKit 的 Document 类就行了。如果你已经读过 SQLAlchemy 方案,那么可以会奇怪这里为什么没有使用会话,甚至没有定义一个init_db 函数。一方面是因为 MongoKit 没有类似会话在东西。有时候这样会多写一点 代码,但会使它的速度更快。另一方面是因为 MongoDB 是无模式的。这就意味着可以在 插入数据的时候修改数据结构。 MongoKit 也是无模式的,但会执行一些验证,以确保 数据的完整性。

以下是一个示例文档(把示例内容也放入 app.py ):

def max_length(length):
    def validate(value):
        if len(value) <= length:
            return True
        raise Exception('%s must be at most %s characters long' % length)
    return validate

class User(Document):
    structure = {
        'name': unicode,
        'email': unicode,
    }
    validators = {
        'name': max_length(50),
        'email': max_length(120)
    }
    use_dot_notation = True
    def __repr__(self):
        return '<User %r>' % (self.name)

# 在当前连接中注册用户文档
connection.register([User])

上例展示如何定义模式(命名结构)和字符串最大长度验证器。上例中还使用了一个 MongoKit 中特殊的 use_dot_notation 功能。缺省情况下, MongoKit 的运作方式和 Python 的字典类似。但是如果 use_dot_notation 设置为 True ,那么就可几乎像 其他 ORM 一样使用点符号来分隔属性。

可以像下面这样把条目插入数据库中:

>>> from yourapplication.database import connection
>>> from yourapplication.models import User
>>> collection = connection['test'].users
>>> user = collection.User()
>>> user['name'] = u'admin'
>>> user['email'] = u'admin@localhost'
>>> user.save()

注意, MongoKit 对于列类型的使用是比较严格的。对于 name 和 email 列,你都 不能使用 str 类型,应当使用 unicode 。

查询非常简单:

>>> list(collection.User.find())
[<User u'admin'>]
>>> collection.User.find_one({'name': u'admin'})
<User u'admin'>
PyMongo 兼容层

如果你只需要使用 PyMongo ,也可以使用 MongoKit 。在这种方式下可以获得最佳的 性能。注意,以下示例中,没有 MongoKit 与 Flask 整合的内容,整合的方式参见上文:

from MongoKit import Connection

connection = Connection()

使用 insert 方法可以插入数据。但首先必须先得到一个连接。这个连接类似于 SQL 界 的表。

>>> collection = connection['test'].users
>>> user = {'name': u'admin', 'email': u'admin@localhost'}
>>> collection.insert(user)

MongoKit 会自动提交。

直接使用集合查询数据库:

>>> list(collection.find())
[{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}]
>>> collection.find_one({'name': u'admin'})
{u'_id': ObjectId('4c271729e13823182f000000'), u'name': u'admin', u'email': u'admin@localhost'}

查询结果为类字典对象:

>>> r = collection.find_one({'name': u'admin'})
>>> r['email']
u'admin@localhost'

关于 MongoKit 的更多信息,请移步其 官方网站 。

添加一个页面图标

一个“页面图标”是浏览器在标签或书签中使用的图标,它可以给你的网站加上一个唯一 的标示,方便区别于其他网站。

那么如何给一个 Flask 应用添加一个页面图标呢?首先,显而易见的,需要一个图标。 图标应当是 16 X 16 像素的 ICO 格式文件。这不是规定的,但却是一个所有浏览器都 支持的事实上的标准。把 ICO 文件命名为 favicon.ico 并放入静态 文件目录 中。

现在我们要让浏览器能够找到你的图标,正确的做法是在你的 HTML 中添加一个链接。 示例:

<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">

对于大多数浏览器来说,这样就完成任务了,但是一些老古董不支持这个标准。老的标准 是把名为“ favicon.ico ”的图标放在服务器的根目录下。如果你的应用不是挂接在域的 根目录下,那么你需要定义网页服务器在根目录下提供这个图标,否则就无计可施了。 如果你的应用位于根目录下,那么你可以简单地进行重定向:

app.add_url_rule('/favicon.ico',
                 redirect_to=url_for('static', filename='favicon.ico'))

如果想要保存额外的重定向请求,那么还可以使用 send_from_directory() 函数来写一个视图:

import os
from flask import send_from_directory

@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')

上例中的 MIME 类型可以省略,浏览器会自动猜测类型。但是我们在例子中明确定义了, 省去了额外的猜测,反正这个类型是不变的。

上例会通过你的应用来提供图标,如果可能的话,最好配置你的专用服务器来提供图标, 配置方法参见网页服务器的文档。

另见

流内容

有时候你会需要把大量数据传送到客户端,不在内存中保存这些数据。当你想把运行中产生 的数据不经过文件系统,而是直接发送给客户端时,应当怎么做呢?

答案是使用生成器和直接响应。

基本用法

下面是一个在运行中产生大量 CSV 数据的基本视图函数。其技巧是调用一个内联函数生成 数据,把这个函数传递给一个响应对象:

from flask import Response

@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in iter_all_rows():
            yield ','.join(row) + '\n'
    return Response(generate(), mimetype='text/csv')

每个 yield 表达式被直接传送给浏览器。注意,有一些 WSGI 中间件可能会打断流 内容,因此在使用分析器或者其他工具的调试环境中要小心一些。

模板中的流内容

Jinja2 模板引擎也支持分片渲染模板。这个功能不是直接被 Flask 支持的,因为它太 特殊了,但是你可以方便地自已来做:

from flask import Response

def stream_template(template_name, **context):
    app.update_template_context(context)
    t = app.jinja_env.get_template(template_name)
    rv = t.stream(context)
    rv.enable_buffering(5)
    return rv

@app.route('/my-large-page.html')
def render_large_template():
    rows = iter_all_rows()
    return Response(stream_template('the_template.html', rows=rows))

上例的技巧是从 Jinja2 环境中获得应用的模板对象,并调用 stream() 来代替render() ,返回 一个流对象来代替一个字符串。由于我们绕过了 Flask 的模板渲染函数使用了模板对象 本身,因此我们需要调用 update_template_context() ,以确保 更新被渲染的内容。这样,模板遍历流内容。由于每次产生内容后,服务器都会把内容 发送给客户端,因此可能需要缓存来保存内容。我们使用了rv.enable_buffering(size) 来进行缓存。 5 是一个比较明智的缺省值。

环境中的流内容

New in version 0.9.

注意,当你生成流内容时,请求环境已经在函数执行时消失了。 Flask 0.9 为你提供了 一点帮助,让你可以在生成器运行期间保持请求环境:

from flask import stream_with_context, request, Response

@app.route('/stream')
def streamed_response():
    def generate():
        yield 'Hello '
        yield request.args['name']
        yield '!'
    return Response(stream_with_context(generate()))

如果没有使用 stream_with_context() 函数,那么就会引发 RuntimeError 错误。

延迟的请求回调

Flask 的设计思路之一是:响应对象创建后被传递给一串回调函数,这些回调函数可以修改 或替换响应对象。当请求处理开始的时候,响应对象还没有被创建。响应对象是由一个视图 函数或者系统中的其他组件按需创建的。

但是当响应对象还没有创建时,我们如何修改响应对象呢?比如在一个请求前函数中,我们 需要根据响应对象设置一个 cookie 。

通常我们选择避开这种情形。例如可以尝试把应用逻辑移动到请求后函数中。但是,有时候 这个方法让人不爽,或者让代码变得很丑陋。

变通的方法是把一堆回调函数贴到 g 对象上,并且在请求结束时调用这些 回调函数。这样你就可以在应用的任意地方延迟回调函数的执行。

装饰器

下面的装饰器是一个关键,它在 g 对象上注册一个函数列表:

from flask import 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
调用延迟的回调函数

至此,通过使用 after_this_request 装饰器,使得函数在请求结束时可以被调用。现在 我们来实现这个调用过程。我们把这些函数注册为 after_request() 回调函数:

@app.after_request
def call_after_request_callbacks(response):
    for callback in getattr(g, 'after_request_callbacks', ()):
        callback(response)
    return response
一个实例

现在我们可以方便地随时随地为特定请求注册一个函数,让这个函数在请求结束时被调用。 例如,你可以在请求前函数中把用户的当前语言记录到 cookie 中:

from flask import request

@app.before_request
def detect_user_language():
    language = request.cookies.get('user_lang')
    if language is None:
        language = guess_language_from_request()
        @after_this_request
        def remember_language(response):
            response.set_cookie('user_lang', language)
    g.language = language

添加 HTTP 方法重载

一些 HTTP 代理不支持所有 HTTP 方法或者不支持一些较新的 HTTP 方法(例如 PACTH )。在这种情况下,可以通过使用完全相反的协议,用一种 HTTP 方法来“代理”另一种 HTTP 方法。

实现的思路是让客户端发送一个 HTTP POST 请求,并设置 X-HTTP-Method-Override头部为需要的 HTTP 方法(例如 PATCH )。

通过 HTTP 中间件可以方便的实现:

class HTTPMethodOverrideMiddleware(object):
    allowed_methods = frozenset([
        'GET',
        'HEAD',
        'POST',
        'DELETE',
        'PUT',
        'PATCH',
        'OPTIONS'
    ])
    bodyless_methods = frozenset(['GET', 'HEAD', 'OPTIONS', 'DELETE'])

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        method = environ.get('HTTP_X_HTTP_METHOD_OVERRIDE', '').upper()
        if method in self.allowed_methods:
            method = method.encode('ascii', 'replace')
            environ['REQUEST_METHOD'] = method
        if method in self.bodyless_methods:
            environ['CONTENT_LENGTH'] = '0'
        return self.app(environ, start_response)

通过以下代码就可以与 Flask 一同工作了:

from flask import Flask

app = Flask(__name__)
app.wsgi_app = HTTPMethodOverrideMiddleware(app.wsgi_app)

请求内容校验

请求数据会由不同的代码来处理或者预处理。例如 JSON 数据和表单数据都来源于已经 读取并处理的请求对象,但是它们的处理代码是不同的。这样,当需要校验进来的请求 数据时就会遇到麻烦。因此,有时候就有必要使用一些 API 。

幸运的是可以通过包装输入流来方便地改变这种情况。

下面的例子演示在 WSGI 环境下读取和储存输入数据,得到数据的 SHA1 校验:

import hashlib

class ChecksumCalcStream(object):

    def __init__(self, stream):
        self._stream = stream
        self._hash = hashlib.sha1()

    def read(self, bytes):
        rv = self._stream.read(bytes)
        self._hash.update(rv)
        return rv

    def readline(self, size_hint):
        rv = self._stream.readline(size_hint)
        self._hash.update(rv)
        return rv

def generate_checksum(request):
    env = request.environ
    stream = ChecksumCalcStream(env['wsgi.input'])
    env['wsgi.input'] = stream
    return stream._hash

要使用上面的类,你只要在请求开始消耗数据之前钩接要计算的流就可以了。(按:小心 操作 request.form 或类似东西。例如 before_request_handlers 就应当小心不 要操作。)

用法示例:

@app.route('/special-api', methods=['POST'])
def special_api():
    hash = generate_checksum(request)
    # Accessing this parses the input stream
    files = request.files
    # At this point the hash is fully constructed.
    checksum = hash.hexdigest()
    return 'Hash was: %s' % checksum

基于 Celery 的后台任务

Celery 是一个 Python 编写的是一个异步任务队列/基于分布式消息传递的作业队列。 以前它有一个 Flask 的集成,但是从版本 3 开始,它进行了一些内部的重构,已经 不需要这个集成了。本文主要说明如何在 Flask 中正确使用 Celery 。本文假设你 已经阅读过了其官方文档中的 Celery 入门

安装 Celery

Celery 在 Python 包索引( PyPI )上榜上有名,因此可以使用 pip 或 easy_install 之类标准的 Python 工具来安装:

$ pip install celery
配置 Celery

你首先需要有一个 Celery 实例,这个实例称为 celery 应用。其地位就相当于 Flask 中Flask 一样。这个实例被用作所有 Celery 相关事务的入口,例如创建 任务、管理工人等等。因此它必须可以被其他模块导入。

例如,你可以把它放在一个 tasks 模块中。这样不需要重新配置,你就可以使用 tasks 的子类,增加 Flask 应用环境的支持,并钩接 Flask 的配置。

只要如下这样就可以在 Falsk 中使用 Celery 了:

from celery import Celery

def make_celery(app):
    celery = Celery(app.import_name, broker=app.config['CELERY_BROKER_URL'])
    celery.conf.update(app.config)
    TaskBase = celery.Task
    class ContextTask(TaskBase):
        abstract = True
        def __call__(self, *args, **kwargs):
            with app.app_context():
                return TaskBase.__call__(self, *args, **kwargs)
    celery.Task = ContextTask
    return celery

这个函数创建了一个新的 Celery 对象,使用了应用配置中的 broker ,并从 Flask 配置 中升级了 Celery 的其余配置。然后创建了一个任务子类,在一个应用环境中包装了任务 执行。

最小的例子

基于上文,以下是一个在 Flask 中使用 Celery 的最小例子:

from flask import Flask

app = Flask(__name__)
app.config.update(
    CELERY_BROKER_URL='redis://localhost:6379',
    CELERY_RESULT_BACKEND='redis://localhost:6379'
)
celery = make_celery(app)


@celery.task()
def add_together(a, b):
    return a + b

这个任务现在可以在后台调用了:

>>> result = add_together.delay(23, 42)
>>> result.wait()
65
运行 Celery 工人

至此,如果你已经按上文一步一步执行,你会失望地发现你的 .wait() 不会真正 返回。这是因为你还没有运行 celery 。你可以这样以工人方式运行 celery:

$ celery -A your_application worker

把 your_application 字符串替换为你创建 celery 对像的应用包或模块。

部署方式

Flask 应用可以采用多种方式部署。在开发时,你可以使用内置的服务器,但是在生产环境 下你就应当选择功能完整的服务器。下面为你提供几个可用的选择。

除了下面提到的服务器之外,如果你使用了其他的 WSGI 服务器,那么请阅读其文档中与 使用 WSGI 应用相关的部分。因为 Flask 应用对象的实质就是一个 WSGI 应用。

如果需要快速体验,请参阅《快速上手》中的 部署到一个网络服务器 。

mod_wsgi (Apache)

如果你正在使用 Apache 网络服务器,那么建议使用 mod_wsgi 。

小心

请务必把 app.run() 放在 if __name__ == '__main__': 内部或者放在单独 的文件中,这样可以保证它不会被调用。因为,每调用一次就会开启一个本地 WSGI 服务器。当我们使用 mod_wsgi 部署应用时,不需要使用本地服务器。

安装 mod_wsgi

可以使用包管理器或编译的方式安装 mod_wsgi 。在 UNIX 系统中如何使用源代码安装 请阅读 mod_wsgi 安装介绍 。

如果你使用的是 Ubuntu/Debian ,那么可以使用如下命令安装:

# apt-get install libapache2-mod-wsgi

在 FreeBSD 系统中,可以通过编译 www/mod_wsgi port 或使用 pkg_add 来安装mod_wsgi :

# pkg_add -r mod_wsgi

如果你使用 pkgsrc ,那么可以通过编译 www/ap2-wsgi 包来安装 mod_wsgi 。

如果你遇到子进程段错误的话,不要理它,重启服务器就可以了。

创建一个 .wsgi 文件

为了运行应用,你需要一个 yourapplication.wsgi 文件。这个文件包含 mod_wsgi 开始时需要运行的代码,通过代码可以获得应用对象。文件中的 application 对象就 是以后要使用的应用。

对于大多数应用来说,文件包含以下内容就可以了:

from yourapplication import app as application

如果你的应用没有创建函数,只是一个独立的实例,那么可以直接把实例导入为application 。

把文件放在一个以后可以找得到的地方(例如 /var/www/yourapplication ),并确保yourapplication 和所有需要使用的库都位于 pythonpath 中。如果你不想在整个系统 中安装,建议使用 virtual python 实例。请记住,最好把应用安装到虚拟环境中。 有一个可选项是在 .wsgi 文件中,在导入前加入路径:

import sys
sys.path.insert(0, '/path/to/the/application')
配置 Apache

最后一件事是为你的应用创建一个 Apache 配置文件。基于安全原因,在下例中我们告诉mod_wsgi 使用另外一个用户运行应用:

<VirtualHost *>
    ServerName example.com

    WSGIDaemonProcess yourapplication user=user1 group=group1 threads=5
    WSGIScriptAlias / /var/www/yourapplication/yourapplication.wsgi

    <Directory /var/www/yourapplication>
        WSGIProcessGroup yourapplication
        WSGIApplicationGroup %{GLOBAL}
        Order deny,allow
        Allow from all
    </Directory>
</VirtualHost>

注意: WSGIDaemonProcess 在 Windows 中不会被执行, 使用上面的配置 Apache 会拒绝 运行。在 Windows 系统下,请使用下面内容:

<VirtualHost *>
        ServerName example.com
        WSGIScriptAlias / C:\yourdir\yourapp.wsgi
        <Directory C:\yourdir>
                Order deny,allow
                Allow from all
        </Directory>
</VirtualHost>

更多信息参见 mod_wsgi 维基 。

故障排除

如果你的应用无法运行,请按以下指导排除故障:

问题: 应用无法运行,出错记录显示 SystemExit ignored
应用文件中有 app.run() 调用,但没有放在 if __name__ == '__main__': 块内。要么把这个调用放入块内,要么把它放在一个单独的 run.py 文件中。
问题: 权限错误
有可以是因为使用了错误的用户运行应用。请检查用户及其所在的组 (WSGIDaemonProcess 的 user 和 group 参数)是否有权限访问应用 文件夹。
问题: 打印时应用歇菜

请记住 mod_wsgi 不允许使用 sys.stdout 和 sys.stderr 。把WSGIRestrictStdout 设置为 off 可以去掉这个保护:

WSGIRestrictStdout Off

或者你可以在 .wsgi 文件中把标准输出替换为其他的流:

import sys
sys.stdout = sys.stderr
问题: 访问资源时遇到 IO 错误

你的应用可能是一个独立的 .py 文件,且你把它符号连接到了 site-packages 文件夹。这样是不对的,你应当要么把文件夹放到 pythonpath 中,要么把你的应用 转换为一个包。

产生这种错误的原因是对于非安装包来说,模块的文件名用于定位资源,如果使用 符号连接的话就会定位到错误的文件名。

支持自动重载

为了辅助部署工具,你可以激活自动重载。这样,一旦 .wsgi 文件有所变动,mod_wsgi 就会自动重新转入所有守护进程。

在 Directory 一节中加入以下指令就可以实现自动重载:

WSGIScriptReloading On
使用虚拟环境

使用虚拟环境的优点是不必全局安装应用所需要的依赖,这样我们就可以更好地按照自己 的需要进行控制。如果要在虚拟环境下使用 mod_wsgi ,那么我们要对 .wsgi 略作 改变。

在你的 .wsgi 文件顶部加入下列内容:

activate_this = '/path/to/env/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

以上代码会根据虚拟环境的设置载入相关路径。请记住路径必须是绝对路径。

独立 WSGI 容器

有一些用 Python 写的流行服务器可以容纳 WSGI 应用,提供 HTTP 服务。这些服务器是 独立运行的,你可以把代理从你的网络服务器指向它们。如果遇到问题,请阅读 代理设置 一节。

Gunicorn

Gunicorn ‘Green Unicorn’ 是一个 UNIX 下的 WSGI HTTP 服务器,它是一个 移植自 Ruby 的 Unicorn 项目的 pre-fork worker 模型。它既支持 eventlet , 也支持 greenlet 。在 Gunicorn 上运行 Flask 应用非常简单:

gunicorn myproject:app

Gunicorn 提供许多命令行参数,可以使用 gunicorn -h 来获得帮助。下面的例子 使用 4 worker 进程( -w 4 )来运行 Flask 应用,绑定到 localhost 的 4000 端口( -b127.0.0.1:4000 ):

gunicorn -w 4 -b 127.0.0.1:4000 myproject:app
Tornado

Tornado 是构建 FriendFeed 的服务器和工具的开源版本,具有良好的伸缩性,非 阻塞性。得益于其非阻塞的方式和对 epoll 的运用,它可以同步处理数以千计的独立 连接,因此 Tornado 是实时 Web 服务的一个理想框架。用它来服务 Flask 是小事一桩:

from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from yourapplication import app

http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()
Gevent

Gevent 是一个 Python 并发网络库,它使用了基于 libevent 事件循环的 greenlet 来提供一个高级同步 API:

from gevent.wsgi import WSGIServer
from yourapplication import app

http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()
Twisted Web

Twisted Web 是一个 Twisted 自带的网络服务器,是一个成熟的、异步的、事件 驱动的网络库。 Twisted Web 带有一个标准的 WSGI 容器,该容器可以使用 twistd 工具运行命令行来控制:

twistd web --wsgi myproject.app

这个命令会运行一个名为 app 的 Flask 应用,其模块名为 myproject 。

与 twistd 工具一样, Twisted Web 支持许多标记和选项。更多信息参见 twistd -h 和twistd web -h 。例如下面命令在前台运行一个来自 myproject 的应用, 端口为 8080:

twistd -n web --port 8080 --wsgi myproject.app
代理设置

如果你要在一个 HTTP 代理后面在上述服务器上运行应用,那么必须重写一些头部才行。 通常在 WSGI 环境中经常会出现问题的有两个变量: REMOTE_ADDR 和HTTP_HOST 。 你可以通过设置你的 httpd 来传递这些头部,或者在中间件中修正这些问题。 Werkzeug 带有一个修复工具可以用于常用的设置,但是你可能需要为特定的设置编写你 自己的 WSGI 中间件。

下面是一个简单的 nginx 配置,代理目标是 localhost 8000 端口提供的服务,设置了 适当的头部:

server {
    listen 80;

    server_name _;

    access_log  /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        proxy_pass         http://127.0.0.1:8000/;
        proxy_redirect     off;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    }
}

如果你的 httpd 无法提供这些头部,那么最常用的设置是调用 X-Forwarded-Host 定义 的主机和 X-Forwarded-For 定义的远程地址:

from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app)

头部可信问题

请注意,在非代理情况下使用这个中间件是有安全问题的,因为它会盲目信任恶意 客户端发来的头部。

如果你要根据另一个头部来重写一个头部,那么可以像下例一样使用修复工具:

class CustomProxyFix(object):

    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        host = environ.get('HTTP_X_FHOST', '')
        if host:
            environ['HTTP_HOST'] = host
        return self.app(environ, start_response)

app.wsgi_app = CustomProxyFix(app.wsgi_app)

uWSGI

uWSGI 也是部署 Flask 的途径之一,类似的部署途径还有 nginx 、 lighttpd 和 cherokee。其他部署途径的信息参见 FastCGI 和 独立 WSGI 容器 。使用 uWSGI 协议来部署 WSGI 应用的先决条件是 需要一个 uWSGI 服务器。 uWSGI 既是一个协议也是一个服务器。如果作为一个服务器, 它可以服务于 uWSGI 、 FastCGI 和 HTTP 协议。

最流行的 uWSGI 服务器是 uwsgi ,本文将使用它来举例,请先安装它。

小心

请务必把 app.run() 放在 if __name__ == '__main__': 内部或者放在单独 的文件中,这样可以保证它不会被调用。因为,每调用一次就会开启一个本地 WSGI 服务器。当我们使用 uWSGI 部署应用时,不需要使用本地服务器。

使用 uwsgi 启动你的应用

uwsgi 是基于 python 模块中的 WSGI 调用的。假设 Flask 应用名称为 myapp.py , 可以使用以下命令:

$ uwsgi -s /tmp/uwsgi.sock --module myapp --callable app

或者这个命令也行:

$ uwsgi -s /tmp/uwsgi.sock -w myapp:app
配置 nginx

一个 nginx 的基本 uWSGI 配置如下:

location = /yourapplication { rewrite ^ /yourapplication/; }
location /yourapplication { try_files $uri @yourapplication; }
location @yourapplication {
  include uwsgi_params;
  uwsgi_param SCRIPT_NAME /yourapplication;
  uwsgi_modifier1 30;
  uwsgi_pass unix:/tmp/uwsgi.sock;
}

这个配置把应用绑定到 /yourapplication 。如果你想要在根 URL 下运行应用非常 简单,因为你不必指出 WSGI PATH_INFO 或让 uwsgi 修改器来使用它:

location / { try_files $uri @yourapplication; }
location @yourapplication {
    include uwsgi_params;
    uwsgi_pass unix:/tmp/uwsgi.sock;
}

FastCGI

FastCGI 是部署 Flask 的途径之一,类似的部署途径还有 nginx 、 lighttpd 和 cherokee 。其他部署途径的信息参见 uWSGI 和 独立 WSGI 容器 。本文讲述的是使用 FastCGI 部署,因此先决条件 是要有一个 FastCGI 服务器。 flup 最流行的 FastCGI 服务器之一,我们将会在本文 中使用它。在阅读下文之前先安装好 flup 。

小心

请务必把 app.run() 放在 if __name__ == '__main__': 内部或者放在单独 的文件中,这样可以保证它不会被调用。因为,每调用一次就会开启一个本地 WSGI 服务器。当我们使用 FastCGI 部署应用时,不需要使用本地服务器。

创建一个 .fcgi 文件

首先你必须创建 FastCGI 服务器配置文件,我们把它命名为 yourapplication.fcgi:

#!/usr/bin/python
from flup.server.fcgi import WSGIServer
from yourapplication import app

if __name__ == '__main__':
    WSGIServer(app).run()

如果使用的是 Apache ,那么使用了这个文件之后就可以正常工作了。但是如果使用的是 nginx 或老版本的 lighttpd ,那么需要显式地把接口传递给 FastCGI 服务器,即把接口 的路径传递给 WSGIServer:

WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()

这个路径必须与服务器配置中定义的路径一致。

把这个 yourapplication.fcgi 文件放在一个以后可以找得到的地方,最好是/var/www/yourapplication 或类似的地方。

为了让服务器可以执行这个文件,请给文件加上执行位,确保这个文件可以执行:

# chmod +x /var/www/yourapplication/yourapplication.fcgi
配置 Apache

上面的例子对于基本的 Apache 部署已经够用了,但是你的 .fcgi 文件会暴露在应用的 URL 中,比如 example.com/yourapplication.fcgi/news/ 。有多种方法可以避免出现这 中情况。一个较好的方法是使用 ScriptAlias 配置指令:

<VirtualHost *>
    ServerName example.com
    ScriptAlias / /path/to/yourapplication.fcgi/
</VirtualHost>

如果你无法设置 ScriptAlias ,比如你使用的是一个共享的网络主机,那么你可以使用 WSGI 中间件把 yourapplication.fcgi 从 URL 中删除。你可以这样设置 .htaccess:

<IfModule mod_fcgid.c>
   AddHandler fcgid-script .fcgi
   <Files ~ (\.fcgi)>
       SetHandler fcgid-script
       Options +FollowSymLinks +ExecCGI
   </Files>
</IfModule>

<IfModule mod_rewrite.c>
   Options +FollowSymlinks
   RewriteEngine On
   RewriteBase /
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteRule ^(.*)$ yourapplication.fcgi/$1 [QSA,L]
</IfModule>

设置 yourapplication.fcgi:

#!/usr/bin/python
#: optional path to your local python site-packages folder
import sys
sys.path.insert(0, '<your_local_path>/lib/python2.6/site-packages')

from flup.server.fcgi import WSGIServer
from yourapplication import app

class ScriptNameStripper(object):
   def __init__(self, app):
       self.app = app

   def __call__(self, environ, start_response):
       environ['SCRIPT_NAME'] = ''
       return self.app(environ, start_response)

app = ScriptNameStripper(app)

if __name__ == '__main__':
    WSGIServer(app).run()
配置 lighttpd

一个 lighttpd 的基本 FastCGI 配置如下:

fastcgi.server = ("/yourapplication.fcgi" =>
    ((
        "socket" => "/tmp/yourapplication-fcgi.sock",
        "bin-path" => "/var/www/yourapplication/yourapplication.fcgi",
        "check-local" => "disable",
        "max-procs" => 1
    ))
)

alias.url = (
    "/static/" => "/path/to/your/static"
)

url.rewrite-once = (
    "^(/static($|/.*))$" => "$1",
    "^(/.*)$" => "/yourapplication.fcgi$1"

请记住启用 FastCGI 、 alias 和 rewrite 模块。以上配置把应用绑定到 /yourapplication。如果你想要让应用在根 URL 下运行,那么必须使用 LighttpdCGIRootFix 中间件来解决一个 lighttpd 缺陷。

请确保只有应用在根 URL 下运行时才使用上述中间件。更多信息请阅读 FastCGI 和 Python (注意,已经不再需要把一个接口显式传递给 run() 了)。

配置 nginx

在 nginx 上安装 FastCGI 应用有一些特殊,因为缺省情况下不传递 FastCGI 参数。

一个 nginx 的基本 FastCGI 配置如下:

location = /yourapplication { rewrite ^ /yourapplication/ last; }
location /yourapplication { try_files $uri @yourapplication; }
location @yourapplication {
    include fastcgi_params;
    fastcgi_split_path_info ^(/yourapplication)(.*)$;
    fastcgi_param PATH_INFO $fastcgi_path_info;
    fastcgi_param SCRIPT_NAME $fastcgi_script_name;
    fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
}

这个配置把应用绑定到 /yourapplication 。如果你想要在根 URL 下运行应用非常 简单,因为你不必指出如何计算出 PATH_INFO 和 SCRIPT_NAME:

location / { try_files $uri @yourapplication; }
location @yourapplication {
    include fastcgi_params;
    fastcgi_param PATH_INFO $fastcgi_script_name;
    fastcgi_param SCRIPT_NAME "";
    fastcgi_pass unix:/tmp/yourapplication-fcgi.sock;
}
运行 FastCGI 进程

Nginx 和其他服务器不会载入 FastCGI 应用,你必须自己载入。 Supervisor 可以管理 FastCGI 进程。 在启动时你可以使用其他 FastCGI 进程管理器或写一个脚本来运行 .fcgi文件,例如 使用一个 SysV init.d 脚本。如果是临时使用,你可以在一个 GNU screen 中运行 .fcgi 脚本。运行细节参见 man screen ,同时请注意这是一个手动启动方法, 不会在系统重启时自动启动:

$ screen
$ /var/www/yourapplication/yourapplication.fcgi
调试

在大多数服务器上, FastCGI 部署难以调试。通常服务器日志只会告诉你类似 “ premature end of headers ”的内容。为了调试应用,查找出错的原因,你必须切换 到正确的用户并手动执行应用。

下例假设你的应用是 application.fcgi ,且你的网络服务用户为 www-data:

$ su www-data
$ cd /var/www/yourapplication
$ python application.fcgi
Traceback (most recent call last):
  File "yourapplication.fcgi", line 4, in <module>
ImportError: No module named yourapplication

上面的出错信息表示 “yourapplication” 不在 python 路径中。原因可能有:

  • 使用了相对路径。在当前工作路径下路径出错。
  • 当前网络服务器设置未正确设置环境变量。
  • 使用了不同的 python 解释器。

CGI

如果其他的部署方式都不管用,那么就只能使用 CGI 了。 CGI 适应于所有主流服务器, 但是其性能稍弱。

这也是在 Google 的 App Engine 使用 Flask 应用的方法,其执行方式类似于 CGI 环境。

小心

请务必把 app.run() 放在 if __name__ == '__main__': 内部或者放在单独 的文件中,这样可以保证它不会被调用。因为,每调用一次就会开启一个本地 WSGI 服务器。当我们使用 CGI 或 App Engine 部署应用时,不需要使用本地服务器。

在使用 CGI 时,你还必须确保代码中不包含任何 print 语句,或者 sys.stdout 被重载,不会写入 HTTP 响应中。

创建一个 .cgi 文件

首先,你需要创建 CGI 应用文件。我们把它命名为 yourapplication.cgi:

#!/usr/bin/python
from wsgiref.handlers import CGIHandler
from yourapplication import app

CGIHandler().run(app)
服务器设置

设置服务器通常有两种方法。一种是把 .cgi 复制为 cgi-bin (并且使用 mod_rewrite 或其他类似东西来改写 URL );另一种是把服务器直接指向文件。

例如,如果使用 Apache ,那么可以把如下内容放入配置中:

ScriptAlias /app /path/to/the/application.cgi

在共享的网络服务器上,你可能无法变动 Apache 配置。在这种情况下,你可以使用你 的公共目录中的 .htaccess 文件。但是 ScriptAlias 指令会失效:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f # Don't interfere with static files
RewriteRule ^(.*)$ /path/to/the/application.cgi/$1 [L]

更多信息参见你所使用的服务器的文档。

大型应用

以下是一些建议,当你的代码库日益壮大或者应用需要规划时可以参考。

阅读源代码

Werkzeug ( WSGI )和 Jinja (模板)是两个被广泛使用的工具,而 Flask 起源就是 用于展示如何基于这两个工具创建你自己的框架。随着不断地开发, Flask 被越来越多 的人认可了。当你的代码库日益壮大时,不应当仅仅是使用 Flask ,而更应当理解它。 所以,请阅读 Flask 的源代码吧。 Flask 的源代码阅读方便,文档公开,有利于你直接 使用内部的 API 。 Flask 坚持把上游库的 API 文档化,并文档化自己内部的工具, 因此通过阅读源代码,可以为你的项目找到更好的切入点。

挂接,扩展

API 文档随处可见可用重载、挂接点和 信号 。你可以定制类似请求 或响应对象的自定义类。请深入研究你所使用的 API ,并在 Flask 发行版中有哪些可以 立即使用的可定制部分。请研究你的哪些项目可以重构为工具集或 Flask 扩展。你可以在 社区中发现很多 扩展 。如果找不到满意的, 那就写一个你自己的吧。

继承

Flask 类有许多方法专门为继承而设计。你可通过继承 Flask (参见链接的方法文档)快速的添加或者定制行为,并把子类 实例化为一个应用类。这种方法同样适用于 应用工厂 。

用中间件包装

应用调度 一文中详细阐述了如何使用中间件。你可以引入中间件来包装你的 Flask 实例,在你的应用和 HTTP 服务器之间的层有所作为。 Werkzeug 包含许多 中间件 。

派生

如果以下建议都没有用,那么直接派生 Flask 吧。 Flask 的主要代码都在 Werkzeug 和 Jinja2 这两个库内。这两个库起了主要作用。 Flask 只是把它们粘合在一起而已。对于 一个项目来讲,底层框架的切入点很重要。因为如果不重视这一点,那么框架会变得非常 复杂,势必带来陡峭的学习曲线,从而吓退用户。

Flask 并不推崇唯一版本。许多人为了避免缺陷,都使用打过补丁或修改过的版本。这个 理念在 Flask 的许可中也有所体现:你不必返回你对框架所做的修改。

分支的缺点是大多数扩展都会失效,因为新的框架会使用不同的导入名称。更进一步: 整合上游的变动将会变得十分复杂,上游变动越多,则整合越复杂。因此,创建分支一般 是不得不为之的最后一招。

专家级的伸缩性

对于大多数网络应用来说,最复杂的莫过于对于用户量和数据量提供良好的伸缩性。 Flask 本身具有良好的伸缩性,其伸缩性受限于你的应用代码、所使用的数据储存方式、 Python 实现和应用所运行的服务器。

如果服务器数量增加一倍,你的应用性能就增加一倍,那么就代表伸缩性好。如果伸缩性 不好,那么即使增加服务器的数量,也不会得到更好的性能。伸缩性更差的甚至不支持增加 第二台服务器。

Flask 中唯一影响伸缩性的因素是环境本地代理。Flask 中的环境本地代理可以被定义为 线程、进程或 greenlet 。如果你的服务器不支持这些,那么 Flask 就不能支持全局代理。 但是,当今主流的服务器都支持线程、进程或 greenlet ,以提高并发性。 Flask 的基础 库 Werkzeug 对于线程、进程或 greenlet 都能够提供良好的支持。

与社区沟通

不管你的代码库是否强大, Flask 开发者总是保持框架的可操作性。如果发现 Flask 有 什么问题,请立即通过邮件列表或 IRC 与社区进行沟通。对于 Flask 及其扩展的开发都 来说,提升其在大型应用中的功能的最佳途径是倾听用户的心声。

API 参考

这部分文档详细说明某个函数、类或方法。

API

这一章文档涵盖了 Flask 所有的接口,对于部分 Flask 所依赖的外部库,我们将最 重要的内容呈现在这里,并提供官方文档链接。

应用对象

class flask.Flask(import_namestatic_path=Nonestatic_url_path=None,static_folder='static'template_folder='templates'instance_path=None,instance_relative_config=False)

The flask object implements a WSGI application and acts as the central object. It is passed the name of the module or package of the application. Once it is created it will act as a central registry for the view functions, the URL rules, template configuration and much more.

The name of the package is used to resolve resources from inside the package or the folder the module is contained in depending on if the package parameter resolves to an actual python package (a folder with an __init__.py file inside) or a standard module (just a .py file).

For more information about resource loading, see open_resource().

Usually you create a Flask instance in your main module or in the __init__.py file of your package like this:

from flask import Flask
app = Flask(__name__)

About the First Parameter

The idea of the first parameter is to give Flask an idea what belongs to your application. This name is used to find resources on the file system, can be used by extensions to improve debugging information and a lot more.

So it’s important what you provide there. If you are using a single module,__name__ is always the correct value. If you however are using a package, it’s usually recommended to hardcode the name of your package there.

For example if your application is defined in yourapplication/app.py you should create it with one of the two versions below:

app = Flask('yourapplication')
app = Flask(__name__.split('.')[0])

Why is that? The application will work even with __name__, thanks to how resources are looked up. However it will make debugging more painful. Certain extensions can make assumptions based on the import name of your application. For example the Flask-SQLAlchemy extension will look for the code in your application that triggered an SQL query in debug mode. If the import name is not properly set up, that debugging information is lost. (For example it would only pick up SQL queries in yourapplication.app and not yourapplication.views.frontend)

New in version 0.7: The static_url_pathstatic_folder, and template_folderparameters were added.

New in version 0.8: The instance_path and instance_relative_config parameters were added.

Parameters:
  • import_name – the name of the application package
  • static_url_path – can be used to specify a different path for the static files on the web. Defaults to the name of thestatic_folder folder.
  • static_folder – the folder with static files that should be served at static_url_path. Defaults to the 'static' folder in the root path of the application.
  • template_folder – the folder that contains the templates that should be used by the application. Defaults to 'templates'folder in the root path of the application.
  • instance_path – An alternative instance path for the application. By default the folder 'instance' next to the package or module is assumed to be the instance path.
  • instance_relative_config – if set to True relative filenames for loading the config are assumed to be relative to the instance path instead of the application root.
add_template_filter(*args**kwargs)

Register a custom template filter. Works exactly like the template_filter()decorator.

Parameters: name – the optional name of the filter, otherwise the function name will be used.
add_template_global(*args**kwargs)

Register a custom template global function. Works exactly like thetemplate_global() decorator.

New in version 0.10.

Parameters: name – the optional name of the global function, otherwise the function name will be used.
add_template_test(*args**kwargs)

Register a custom template test. Works exactly like the template_test()decorator.

New in version 0.10.

Parameters: name – the optional name of the test, otherwise the function name will be used.
add_url_rule(*args**kwargs)

Connects a URL rule. Works exactly like the route() decorator. If a view_func is provided it will be registered with the endpoint.

Basically this example:

@app.route('/')
def index():
    pass

Is equivalent to the following:

def index():
    pass
app.add_url_rule('/', 'index', index)

If the view_func is not provided you will need to connect the endpoint to a view function like so:

app.view_functions['index'] = index

Internally route() invokes add_url_rule() so if you want to customize the behavior via subclassing you only need to change this method.

For more information refer to URL Route Registrations.

Changed in version 0.2: view_func parameter added.

Changed in version 0.6: OPTIONS is added automatically as method.

Parameters:
  • rule – the URL rule as string
  • endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
  • view_func – the function to call when serving a request to the provided endpoint
  • options – the options to be forwarded to the underlyingRule object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GETPOST etc.). By default a rule just listens forGET (and implicitly HEAD). Starting with Flask 0.6,OPTIONS is implicitly added and handled by the standard request handling.
after_request(*args**kwargs)

Register a function to be run after each request. Your function must take one parameter, a response_class object and return a new response object or the same (see process_response()).

As of Flask 0.7 this function might not be executed at the end of the request in case an unhandled exception occurred.

after_request_funcs = None

A dictionary with lists of functions that should be called after each request. The key of the dictionary is the name of the blueprint this function is active for,None for all requests. This can for example be used to open database connections or getting hold of the currently logged in user. To register a function here, use the after_request() decorator.

app_context()

Binds the application only. For as long as the application is bound to the current context the flask.current_app points to that application. An application context is automatically created when a request context is pushed if necessary.

Example usage:

with app.app_context():
    ...

New in version 0.9.

app_ctx_globals_class

The class that is used for the g instance.

Example use cases for a custom class:

  1. Store arbitrary attributes on flask.g.
  2. Add a property for lazy per-request database connectors.
  3. Return None instead of AttributeError on expected attributes.
  4. Raise exception if an unexpected attr is set, a “controlled” flask.g.

In Flask 0.9 this property was called request_globals_class but it was changed in 0.10 to app_ctx_globals_class because the flask.g object is not application context scoped.

New in version 0.10.

alias of _AppCtxGlobals

auto_find_instance_path()

Tries to locate the instance path if it was not provided to the constructor of the application class. It will basically calculate the path to a folder namedinstance next to your main file or the package.

New in version 0.8.

before_first_request(*args**kwargs)

Registers a function to be run before the first request to this instance of the application.

New in version 0.8.

before_first_request_funcs = None

A lists of functions that should be called at the beginning of the first request to this instance. To register a function here, use the before_first_request()decorator.

New in version 0.8.

before_request(*args**kwargs)

Registers a function to run before each request.

before_request_funcs = None

A dictionary with lists of functions that should be called at the beginning of the request. The key of the dictionary is the name of the blueprint this function is active for, None for all requests. This can for example be used to open database connections or getting hold of the currently logged in user. To register a function here, use the before_request() decorator.

blueprints = None

all the attached blueprints in a dictionary by name. Blueprints can be attached multiple times so this dictionary does not tell you how often they got attached.

New in version 0.7.

config = None

The configuration dictionary as Config. This behaves exactly like a regular dictionary but supports additional methods to load a config from files.

context_processor(*args**kwargs)

Registers a template context processor function.

create_global_jinja_loader()

Creates the loader for the Jinja2 environment. Can be used to override just the loader and keeping the rest unchanged. It’s discouraged to override this function. Instead one should override the jinja_loader() function instead.

The global loader dispatches between the loaders of the application and the individual blueprints.

New in version 0.7.

create_jinja_environment()

Creates the Jinja2 environment based on jinja_options andselect_jinja_autoescape(). Since 0.7 this also adds the Jinja2 globals and filters after initialization. Override this function to customize the behavior.

New in version 0.5.

create_url_adapter(request)

Creates a URL adapter for the given request. The URL adapter is created at a point where the request context is not yet set up so the request is passed explicitly.

New in version 0.6.

Changed in version 0.9: This can now also be called without a request object when the URL adapter is created for the application context.

debug

The debug flag. Set this to True to enable debugging of the application. In debug mode the debugger will kick in when an unhandled exception occurs and the integrated server will automatically reload the application if changes in the code are detected.

This attribute can also be configured from the config with the DEBUGconfiguration key. Defaults to False.

debug_log_format = '--------------------------------------------------------------------------------\n%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n%(message)s\n--------------------------------------------------------------------------------'

The logging format used for the debug logger. This is only used when the application is in debug mode, otherwise the attached logging handler does the formatting.

New in version 0.3.

default_config = ImmutableDict({'JSON_AS_ASCII': True, 'USE_X_SENDFILE': False, 'SESSION_COOKIE_PATH': None, 'SESSION_COOKIE_DOMAIN': None, 'SESSION_COOKIE_NAME': 'session', 'LOGGER_NAME': None, 'DEBUG': False, 'SECRET_KEY': None, 'MAX_CONTENT_LENGTH': None, 'APPLICATION_ROOT': None, 'SERVER_NAME': None, 'PREFERRED_URL_SCHEME': 'http', 'JSONIFY_PRETTYPRINT_REGULAR': True, 'TESTING': False, 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(31), 'PROPAGATE_EXCEPTIONS': None, 'TRAP_BAD_REQUEST_ERRORS': False, 'JSON_SORT_KEYS': True, 'SESSION_COOKIE_HTTPONLY': True, 'SEND_FILE_MAX_AGE_DEFAULT': 43200, 'PRESERVE_CONTEXT_ON_EXCEPTION': None, 'SESSION_COOKIE_SECURE': False, 'TRAP_HTTP_EXCEPTIONS': False})

Default configuration parameters.

dispatch_request()

Does the request dispatching. Matches the URL and returns the return value of the view or error handler. This does not have to be a response object. In order to convert the return value to a proper response object, call make_response().

Changed in version 0.7: This no longer does the exception handling, this code was moved to the new full_dispatch_request().

do_teardown_appcontext(exc=None)

Called when an application context is popped. This works pretty much the same as do_teardown_request() but for the application context.

New in version 0.9.

do_teardown_request(exc=None)

Called after the actual request dispatching and will call every asteardown_request() decorated function. This is not actually called by theFlask object itself but is always triggered when the request context is popped. That way we have a tighter control over certain resources under testing environments.

Changed in version 0.9: Added the exc argument. Previously this was always using the current exception information.

enable_modules = True

Enable the deprecated module support? This is active by default in 0.7 but will be changed to False in 0.8. With Flask 1.0 modules will be removed in favor of Blueprints

endpoint(*args**kwargs)

A decorator to register a function as an endpoint. Example:

@app.endpoint('example.endpoint')
def example():
    return "example"
Parameters: endpoint – the name of the endpoint
error_handler_spec = None

A dictionary of all registered error handlers. The key is None for error handlers active on the application, otherwise the key is the name of the blueprint. Each key points to another dictionary where they key is the status code of the http exception. The special key None points to a list of tuples where the first item is the class for the instance check and the second the error handler function.

To register a error handler, use the errorhandler() decorator.

errorhandler(*args**kwargs)

A decorator that is used to register a function give a given error code. Example:

@app.errorhandler(404)
def page_not_found(error):
    return 'This page does not exist', 404

You can also register handlers for arbitrary exceptions:

@app.errorhandler(DatabaseError)
def special_exception_handler(error):
    return 'Database connection failed', 500

You can also register a function as error handler without using theerrorhandler() decorator. The following example is equivalent to the one above:

def page_not_found(error):
    return 'This page does not exist', 404
app.error_handler_spec[None][404] = page_not_found

Setting error handlers via assignments to error_handler_spec however is discouraged as it requires fiddling with nested dictionaries and the special case for arbitrary exception types.

The first None refers to the active blueprint. If the error handler should be application wide None shall be used.

New in version 0.7: One can now additionally also register custom exception types that do not necessarily have to be a subclass of the HTTPException class.

Parameters: code – the code as integer for the handler
extensions = None

a place where extensions can store application specific state. For example this is where an extension could store database engines and similar things. For backwards compatibility extensions should register themselves like this:

if not hasattr(app, 'extensions'):
    app.extensions = {}
app.extensions['extensionname'] = SomeObject()

The key must match the name of the flaskext module. For example in case of a “Flask-Foo” extension in flaskext.foo, the key would be 'foo'.

New in version 0.7.

full_dispatch_request()

Dispatches the request and on top of that performs request pre and postprocessing as well as HTTP exception catching and error handling.

New in version 0.7.

get_send_file_max_age(filename)

Provides default cache_timeout for the send_file() functions.

By default, this function returns SEND_FILE_MAX_AGE_DEFAULT from the configuration of current_app.

Static file functions such as send_from_directory() use this function, andsend_file() calls this function on current_app when the given cache_timeout is None. If a cache_timeout is given in send_file(), that timeout is used; otherwise, this method is called.

This allows subclasses to change the behavior when sending files based on the filename. For example, to set the cache timeout for .js files to 60 seconds:

class MyFlask(flask.Flask):
    def get_send_file_max_age(self, name):
        if name.lower().endswith('.js'):
            return 60
        return flask.Flask.get_send_file_max_age(self, name)

New in version 0.9.

got_first_request

This attribute is set to True if the application started handling the first request.

New in version 0.8.

handle_exception(e)

Default exception handling that kicks in when an exception occurs that is not caught. In debug mode the exception will be re-raised immediately, otherwise it is logged and the handler for a 500 internal server error is used. If no such handler exists, a default 500 internal server error message is displayed.

New in version 0.3.

handle_http_exception(e)

Handles an HTTP exception. By default this will invoke the registered error handlers and fall back to returning the exception as response.

New in version 0.3.

handle_url_build_error(errorendpointvalues)

Handle BuildError on url_for().

handle_user_exception(e)

This method is called whenever an exception occurs that should be handled. A special case are HTTPExceptions which are forwarded by this function to thehandle_http_exception() method. This function will either return a response value or reraise the exception with the same traceback.

New in version 0.7.

has_static_folder

This is True if the package bound object’s container has a folder named'static'.

New in version 0.5.

init_jinja_globals()

Deprecated. Used to initialize the Jinja2 globals.

New in version 0.5.

Changed in version 0.7: This method is deprecated with 0.7. Overridecreate_jinja_environment() instead.

inject_url_defaults(endpointvalues)

Injects the URL defaults for the given endpoint directly into the values dictionary passed. This is used internally and automatically called on URL building.

New in version 0.7.

instance_path = None

Holds the path to the instance folder.

New in version 0.8.

jinja_env

The Jinja2 environment used to load templates.

jinja_loader

The Jinja loader for this package bound object.

New in version 0.5.

jinja_options = ImmutableDict({'extensions': ['jinja2.ext.autoescape', 'jinja2.ext.with_']})

Options that are passed directly to the Jinja2 environment.

json_decoder

The JSON decoder class to use. Defaults to JSONDecoder.

New in version 0.10.

alias of JSONDecoder

json_encoder

The JSON encoder class to use. Defaults to JSONEncoder.

New in version 0.10.

alias of JSONEncoder

log_exception(exc_info)

Logs an exception. This is called by handle_exception() if debugging is disabled and right before the handler is called. The default implementation logs the exception as error on the logger.

New in version 0.8.

logger

logging.Logger object for this application. The default configuration is to log to stderr if the application is in debug mode. This logger can be used to (surprise) log messages. Here some examples:

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

New in version 0.3.

logger_name

The name of the logger to use. By default the logger name is the package name passed to the constructor.

New in version 0.4.

make_config(instance_relative=False)

Used to create the config attribute by the Flask constructor. Theinstance_relative parameter is passed in from the constructor of Flask (there named instance_relative_config) and indicates if the config should be relative to the instance path or the root path of the application.

New in version 0.8.

make_default_options_response()

This method is called to create the default OPTIONS response. This can be changed through subclassing to change the default behavior of OPTIONSresponses.

New in version 0.7.

make_null_session()

Creates a new instance of a missing session. Instead of overriding this method we recommend replacing the session_interface.

New in version 0.7.

make_response(rv)

Converts the return value from a view function to a real response object that is an instance of response_class.

The following types are allowed for rv:

response_class the object is returned unchanged
str a response object is created with the string as body
unicode a response object is created with the string encoded to utf-8 as body
a WSGI function the function is called as WSGI application and buffered as response object
tuple A tuple in the form (response, status,headers) where response is any of the types defined here, status is a string or an integer andheaders is a list of a dictionary with header values.
Parameters: rv – the return value from the view function

Changed in version 0.9: Previously a tuple was interpreted as the arguments for the response object.

name

The name of the application. This is usually the import name with the difference that it’s guessed from the run file if the import name is main. This name is used as a display name when Flask needs the name of the application. It can be set and overridden to change the value.

New in version 0.8.

open_instance_resource(resourcemode='rb')

Opens a resource from the application’s instance folder (instance_path). Otherwise works like open_resource(). Instance resources can also be opened for writing.

Parameters:
  • resource – the name of the resource. To access resources within subfolders use forward slashes as separator.
  • mode – resource file opening mode, default is ‘rb’.
open_resource(resourcemode='rb')

Opens a resource from the application’s resource folder. To see how this works, consider the following folder structure:

/myapplication.py
/schema.sql
/static
    /style.css
/templates
    /layout.html
    /index.html

If you want to open the schema.sql file you would do the following:

with app.open_resource('schema.sql') as f:
    contents = f.read()
    do_something_with(contents)
Parameters:
  • resource – the name of the resource. To access resources within subfolders use forward slashes as separator.
  • mode – resource file opening mode, default is ‘rb’.
open_session(request)

Creates or opens a new session. Default implementation stores all session data in a signed cookie. This requires that the secret_key is set. Instead of overriding this method we recommend replacing the session_interface.

Parameters: request – an instance of request_class.
permanent_session_lifetime

timedelta which is used to set the expiration date of a permanent session. The default is 31 days which makes a permanent session survive for roughly one month.

This attribute can also be configured from the config with thePERMANENT_SESSION_LIFETIME configuration key. Defaults totimedelta(days=31)

preprocess_request()

Called before the actual request dispatching and will call every asbefore_request() decorated function. If any of these function returns a value it’s handled as if it was the return value from the view and further request handling is stopped.

This also triggers the url_value_processor() functions before the actualbefore_request() functions are called.

preserve_context_on_exception

Returns the value of the PRESERVE_CONTEXT_ON_EXCEPTIONconfiguration value in case it’s set, otherwise a sensible default is returned.

New in version 0.7.

process_response(response)

Can be overridden in order to modify the response object before it’s sent to the WSGI server. By default this will call all the after_request() decorated functions.

Changed in version 0.5: As of Flask 0.5 the functions registered for after request execution are called in reverse order of registration.

Parameters: response – a response_class object.
Returns: a new response object or the same, has to be an instance ofresponse_class.
propagate_exceptions

Returns the value of the PROPAGATE_EXCEPTIONS configuration value in case it’s set, otherwise a sensible default is returned.

New in version 0.7.

register_blueprint(*args**kwargs)

Registers a blueprint on the application.

New in version 0.7.

register_error_handler(code_or_exceptionf)

Alternative error attach function to the errorhandler() decorator that is more straightforward to use for non decorator usage.

New in version 0.7.

register_module(module**options)

Registers a module with this application. The keyword argument of this function are the same as the ones for the constructor of the Module class and will override the values of the module if provided.

Changed in version 0.7: The module system was deprecated in favor for the blueprint system.

request_class

The class that is used for request objects. See Request for more information.

alias of Request

request_context(environ)

Creates a RequestContext from the given environment and binds it to the current context. This must be used in combination with the with statement because the request is only bound to the current context for the duration of thewith block.

Example usage:

with app.request_context(environ):
    do_something_with(request)

The object returned can also be used without the with statement which is useful for working in the shell. The example above is doing exactly the same as this code:

ctx = app.request_context(environ)
ctx.push()
try:
    do_something_with(request)
finally:
    ctx.pop()

Changed in version 0.3: Added support for non-with statement usage and withstatement is now passed the ctx object.

Parameters: environ – a WSGI environment
response_class

The class that is used for response objects. See Response for more information.

alias of Response

route(rule**options)

A decorator that is used to register a view function for a given URL rule. This does the same thing as add_url_rule() but is intended for decorator usage:

@app.route('/')
def index():
    return 'Hello World'

For more information refer to URL Route Registrations.

Parameters:
  • rule – the URL rule as string
  • endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
  • options – the options to be forwarded to the underlyingRule object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GETPOST etc.). By default a rule just listens forGET (and implicitly HEAD). Starting with Flask 0.6,OPTIONS is implicitly added and handled by the standard request handling.
run(host=Noneport=Nonedebug=None**options)

Runs the application on a local development server. If the debug flag is set the server will automatically reload for code changes and show a debugger in case an exception happened.

If you want to run the application in debug mode, but disable the code execution on the interactive debugger, you can pass use_evalex=False as parameter. This will keep the debugger’s traceback screen active, but disable code execution.

Keep in Mind

Flask will suppress any server error with a generic error page unless it is in debug mode. As such to enable just the interactive debugger without the code reloading, you have to invoke run() with debug=True anduse_reloader=False. Setting use_debugger to True without being in debug mode won’t catch any exceptions because there won’t be any to catch.

Changed in version 0.10: The default port is now picked from the SERVER_NAMEvariable.

Parameters:
  • host – the hostname to listen on. Set this to '0.0.0.0' to have the server available externally as well. Defaults to'127.0.0.1'.
  • port – the port of the webserver. Defaults to 5000 or the port defined in the SERVER_NAME config variable if present.
  • debug – if given, enable or disable debug mode. See debug.
  • options – the options to be forwarded to the underlying Werkzeug server. See werkzeug.serving.run_simple()for more information.
save_session(sessionresponse)

Saves the session if it needs updates. For the default implementation, checkopen_session(). Instead of overriding this method we recommend replacing the session_interface.

Parameters:
secret_key

If a secret key is set, cryptographic components can use this to sign cookies and other things. Set this to a complex random value when you want to use the secure cookie for instance.

This attribute can also be configured from the config with the SECRET_KEYconfiguration key. Defaults to None.

select_jinja_autoescape(filename)

Returns True if autoescaping should be active for the given template name.

New in version 0.5.

send_static_file(filename)

Function used internally to send static files from the static folder to the browser.

New in version 0.5.

The secure cookie uses this for the name of the session cookie.

This attribute can also be configured from the config with theSESSION_COOKIE_NAME configuration key. Defaults to 'session'

session_interface = <flask.sessions.SecureCookieSessionInterface object>

the session interface to use. By default an instance ofSecureCookieSessionInterface is used here.

New in version 0.8.

should_ignore_error(error)

This is called to figure out if an error should be ignored or not as far as the teardown system is concerned. If this function returns True then the teardown handlers will not be passed the error.

New in version 0.10.

teardown_appcontext(*args**kwargs)

Registers a function to be called when the application context ends. These functions are typically also called when the request context is popped.

Example:

ctx = app.app_context()
ctx.push()
...
ctx.pop()

When ctx.pop() is executed in the above example, the teardown functions are called just before the app context moves from the stack of active contexts. This becomes relevant if you are using such constructs in tests.

Since a request context typically also manages an application context it would also be called when you pop a request context.

When a teardown function was called because of an exception it will be passed an error object.

New in version 0.9.

teardown_appcontext_funcs = None

A list of functions that are called when the application context is destroyed. Since the application context is also torn down if the request ends this is the place to store code that disconnects from databases.

New in version 0.9.

teardown_request(*args**kwargs)

Register a function to be run at the end of each request, regardless of whether there was an exception or not. These functions are executed when the request context is popped, even if not an actual request was performed.

Example:

ctx = app.test_request_context()
ctx.push()
...
ctx.pop()

When ctx.pop() is executed in the above example, the teardown functions are called just before the request context moves from the stack of active contexts. This becomes relevant if you are using such constructs in tests.

Generally teardown functions must take every necessary step to avoid that they will fail. If they do execute code that might fail they will have to surround the execution of these code by try/except statements and log occurring errors.

When a teardown function was called because of a exception it will be passed an error object.

Debug Note

In debug mode Flask will not tear down a request on an exception immediately. Instead if will keep it alive so that the interactive debugger can still access it. This behavior can be controlled by the PRESERVE_CONTEXT_ON_EXCEPTIONconfiguration variable.

teardown_request_funcs = None

A dictionary with lists of functions that are called after each request, even if an exception has occurred. The key of the dictionary is the name of the blueprint this function is active for, None for all requests. These functions are not allowed to modify the request, and their return values are ignored. If an exception occurred while processing the request, it gets passed to each teardown_request function. To register a function here, use the teardown_request() decorator.

New in version 0.7.

template_context_processors = None

A dictionary with list of functions that are called without argument to populate the template context. The key of the dictionary is the name of the blueprint this function is active for, None for all requests. Each returns a dictionary that the template context is updated with. To register a function here, use thecontext_processor() decorator.

template_filter(*args**kwargs)

A decorator that is used to register custom template filter. You can specify a name for the filter, otherwise the function name will be used. Example:

@app.template_filter()
def reverse(s):
    return s[::-1]
Parameters: name – the optional name of the filter, otherwise the function name will be used.
template_global(*args**kwargs)

A decorator that is used to register a custom template global function. You can specify a name for the global function, otherwise the function name will be used. Example:

@app.template_global()
def double(n):
    return 2 * n

New in version 0.10.

Parameters: name – the optional name of the global function, otherwise the function name will be used.
template_test(*args**kwargs)

A decorator that is used to register custom template test. You can specify a name for the test, otherwise the function name will be used. Example:

@app.template_test()
def is_prime(n):
    if n == 2:
        return True
    for i in range(2, int(math.ceil(math.sqrt(n))) + 1):
        if n % i == 0:
            return False
    return True

New in version 0.10.

Parameters: name – the optional name of the test, otherwise the function name will be used.
test_client(use_cookies=True)

Creates a test client for this application. For information about unit testing head over to 测试 Flask 应用.

Note that if you are testing for assertions or exceptions in your application code, you must set app.testing True in order for the exceptions to propagate to the test client. Otherwise, the exception will be handled by the application (not visible to the test client) and the only indication of an AssertionError or other exception will be a 500 status code response to the test client. See the testingattribute. For example:

app.testing = True
client = app.test_client()

The test client can be used in a with block to defer the closing down of the context until the end of the with block. This is useful if you want to access the context locals for testing:

with app.test_client() as c:
    rv = c.get('/?vodka=42')
    assert request.args['vodka'] == '42'

See FlaskClient for more information.

Changed in version 0.4: added support for with block usage for the client.

New in version 0.7: The use_cookies parameter was added as well as the ability to override the client to be used by setting the test_client_class attribute.

test_client_class = None

the test client that is used with when test_client is used.

New in version 0.7.

test_request_context(*args**kwargs)

Creates a WSGI environment from the given values (seewerkzeug.test.EnvironBuilder() for more information, this function accepts the same arguments).

testing

The testing flag. Set this to True to enable the test mode of Flask extensions (and in the future probably also Flask itself). For example this might activate unittest helpers that have an additional runtime cost which should not be enabled by default.

If this is enabled and PROPAGATE_EXCEPTIONS is not changed from the default it’s implicitly enabled.

This attribute can also be configured from the config with the TESTINGconfiguration key. Defaults to False.

trap_http_exception(e)

Checks if an HTTP exception should be trapped or not. By default this will return False for all exceptions except for a bad request key error ifTRAP_BAD_REQUEST_ERRORS is set to True. It also returns True ifTRAP_HTTP_EXCEPTIONS is set to True.

This is called for all HTTP exceptions raised by a view function. If it returnsTrue for any exception the error handler for this exception is not called and it shows up as regular exception in the traceback. This is helpful for debugging implicitly raised HTTP exceptions.

New in version 0.8.

update_template_context(context)

Update the template context with some commonly used variables. This injects request, session, config and g into the template context as well as everything template context processors want to inject. Note that the as of Flask 0.6, the original values in the context will not be overridden if a context processor decides to return a value with the same key.

Parameters: context – the context as a dictionary that is updated in place to add extra variables.
url_build_error_handlers = None

A list of functions that are called when url_for() raises a BuildError. Each function registered here is called with errorendpoint and values. If a function returns None or raises a BuildError the next function is tried.

New in version 0.9.

url_default_functions = None

A dictionary with lists of functions that can be used as URL value preprocessors. The key None here is used for application wide callbacks, otherwise the key is the name of the blueprint. Each of these functions has the chance to modify the dictionary of URL values before they are used as the keyword arguments of the view function. For each function registered this one should also provide a url_defaults() function that adds the parameters automatically again that were removed that way.

New in version 0.7.

url_defaults(*args**kwargs)

Callback function for URL defaults for all view functions of the application. It’s called with the endpoint and values and should update the values passed in place.

url_map = None

The Map for this instance. You can use this to change the routing converters after the class was created but before any routes are connected. Example:

from werkzeug.routing import BaseConverter

class ListConverter(BaseConverter):
    def to_python(self, value):
        return value.split(',')
    def to_url(self, values):
        return ','.join(BaseConverter.to_url(value)
                        for value in values)

app = Flask(__name__)
app.url_map.converters['list'] = ListConverter
url_rule_class

The rule object to use for URL rules created. This is used by add_url_rule(). Defaults to werkzeug.routing.Rule.

New in version 0.7.

alias of Rule

url_value_preprocessor(*args**kwargs)

Registers a function as URL value preprocessor for all view functions of the application. It’s called before the view functions are called and can modify the url values provided.

url_value_preprocessors = None

A dictionary with lists of functions that can be used as URL value processor functions. Whenever a URL is built these functions are called to modify the dictionary of values in place. The key None here is used for application wide callbacks, otherwise the key is the name of the blueprint. Each of these functions has the chance to modify the dictionary

New in version 0.7.

use_x_sendfile

Enable this if you want to use the X-Sendfile feature. Keep in mind that the server has to support this. This only affects files sent with the send_file()method.

New in version 0.2.

This attribute can also be configured from the config with theUSE_X_SENDFILE configuration key. Defaults to False.

view_functions = None

A dictionary of all view functions registered. The keys will be function names which are also used to generate URLs and the values are the function objects themselves. To register a view function, use the route() decorator.

wsgi_app(environstart_response)

The actual WSGI application. This is not implemented in __call__ so that middlewares can be applied without losing a reference to the class. So instead of doing this:

app = MyMiddleware(app)

It’s a better idea to do this instead:

app.wsgi_app = MyMiddleware(app.wsgi_app)

Then you still have the original application object around and can continue to call methods on it.

Changed in version 0.7: The behavior of the before and after request callbacks was changed under error conditions and a new callback was added that will always execute at the end of the request, independent on if an error occurred or not. See 回调和错误处理.

Parameters:
  • environ – a WSGI environment
  • start_response – a callable accepting a status code, a list of headers and an optional exception context to start the response

Blueprint Objects

class flask.Blueprint(nameimport_namestatic_folder=None,static_url_path=Nonetemplate_folder=Noneurl_prefix=Nonesubdomain=None,url_defaults=None)

Represents a blueprint. A blueprint is an object that records functions that will be called with the BlueprintSetupState later to register functions or other things on the main application. See 使用蓝图的模块化应用 for more information.

New in version 0.7.

add_app_template_filter(fname=None)

Register a custom template filter, available application wide. LikeFlask.add_template_filter() but for a blueprint. Works exactly like theapp_template_filter() decorator.

Parameters: name – the optional name of the filter, otherwise the function name will be used.
add_app_template_global(fname=None)

Register a custom template global, available application wide. LikeFlask.add_template_global() but for a blueprint. Works exactly like theapp_template_global() decorator.

New in version 0.10.

Parameters: name – the optional name of the global, otherwise the function name will be used.
add_app_template_test(fname=None)

Register a custom template test, available application wide. LikeFlask.add_template_test() but for a blueprint. Works exactly like theapp_template_test() decorator.

New in version 0.10.

Parameters: name – the optional name of the test, otherwise the function name will be used.
add_url_rule(ruleendpoint=Noneview_func=None**options)

Like Flask.add_url_rule() but for a blueprint. The endpoint for theurl_for() function is prefixed with the name of the blueprint.

after_app_request(f)

Like Flask.after_request() but for a blueprint. Such a function is executed after each request, even if outside of the blueprint.

after_request(f)

Like Flask.after_request() but for a blueprint. This function is only executed after each request that is handled by a function of that blueprint.

app_context_processor(f)

Like Flask.context_processor() but for a blueprint. Such a function is executed each request, even if outside of the blueprint.

app_errorhandler(code)

Like Flask.errorhandler() but for a blueprint. This handler is used for all requests, even if outside of the blueprint.

app_template_filter(name=None)

Register a custom template filter, available application wide. LikeFlask.template_filter() but for a blueprint.

Parameters: name – the optional name of the filter, otherwise the function name will be used.
app_template_global(name=None)

Register a custom template global, available application wide. LikeFlask.template_global() but for a blueprint.

New in version 0.10.

Parameters: name – the optional name of the global, otherwise the function name will be used.
app_template_test(name=None)

Register a custom template test, available application wide. LikeFlask.template_test() but for a blueprint.

New in version 0.10.

Parameters: name – the optional name of the test, otherwise the function name will be used.
app_url_defaults(f)

Same as url_defaults() but application wide.

app_url_value_preprocessor(f)

Same as url_value_preprocessor() but application wide.

before_app_first_request(f)

Like Flask.before_first_request(). Such a function is executed before the first request to the application.

before_app_request(f)

Like Flask.before_request(). Such a function is executed before each request, even if outside of a blueprint.

before_request(f)

Like Flask.before_request() but for a blueprint. This function is only executed before each request that is handled by a function of that blueprint.

context_processor(f)

Like Flask.context_processor() but for a blueprint. This function is only executed for requests handled by a blueprint.

endpoint(endpoint)

Like Flask.endpoint() but for a blueprint. This does not prefix the endpoint with the blueprint name, this has to be done explicitly by the user of this method. If the endpoint is prefixed with a . it will be registered to the current blueprint, otherwise it’s an application independent endpoint.

errorhandler(code_or_exception)

Registers an error handler that becomes active for this blueprint only. Please be aware that routing does not happen local to a blueprint so an error handler for 404 usually is not handled by a blueprint unless it is caused inside a view function. Another special case is the 500 internal server error which is always looked up from the application.

Otherwise works as the errorhandler() decorator of the Flask object.

get_send_file_max_age(filename)

Provides default cache_timeout for the send_file() functions.

By default, this function returns SEND_FILE_MAX_AGE_DEFAULT from the configuration of current_app.

Static file functions such as send_from_directory() use this function, andsend_file() calls this function on current_app when the given cache_timeout is None. If a cache_timeout is given in send_file(), that timeout is used; otherwise, this method is called.

This allows subclasses to change the behavior when sending files based on the filename. For example, to set the cache timeout for .js files to 60 seconds:

class MyFlask(flask.Flask):
    def get_send_file_max_age(self, name):
        if name.lower().endswith('.js'):
            return 60
        return flask.Flask.get_send_file_max_age(self, name)

New in version 0.9.

has_static_folder

This is True if the package bound object’s container has a folder named'static'.

New in version 0.5.

jinja_loader

The Jinja loader for this package bound object.

New in version 0.5.

make_setup_state(appoptionsfirst_registration=False)

Creates an instance of BlueprintSetupState() object that is later passed to the register callback functions. Subclasses can override this to return a subclass of the setup state.

open_resource(resourcemode='rb')

Opens a resource from the application’s resource folder. To see how this works, consider the following folder structure:

/myapplication.py
/schema.sql
/static
    /style.css
/templates
    /layout.html
    /index.html

If you want to open the schema.sql file you would do the following:

with app.open_resource('schema.sql') as f:
    contents = f.read()
    do_something_with(contents)
Parameters:
  • resource – the name of the resource. To access resources within subfolders use forward slashes as separator.
  • mode – resource file opening mode, default is ‘rb’.
record(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 make_setup_state() method.

record_once(func)

Works like record() but wraps the function in another function that will ensure the function is only called once. If the blueprint is registered a second time on the application, the function passed is not called.

register(appoptionsfirst_registration=False)

Called by Flask.register_blueprint() to register a blueprint on the application. This can be overridden to customize the register behavior. Keyword arguments from register_blueprint() are directly forwarded to this method in the options dictionary.

route(rule**options)

Like Flask.route() but for a blueprint. The endpoint for the url_for()function is prefixed with the name of the blueprint.

send_static_file(filename)

Function used internally to send static files from the static folder to the browser.

New in version 0.5.

teardown_app_request(f)

Like Flask.teardown_request() but for a blueprint. Such a function is executed when tearing down each request, even if outside of the blueprint.

teardown_request(f)

Like Flask.teardown_request() but for a blueprint. This function is only executed when tearing down requests handled by a function of that blueprint. Teardown request functions are executed when the request context is popped, even when no actual request was performed.

url_defaults(f)

Callback function for URL defaults for this blueprint. It’s called with the endpoint and values and should update the values passed in place.

url_value_preprocessor(f)

Registers a function as URL value preprocessor for this blueprint. It’s called before the view functions are called and can modify the url values provided.

Incoming Request Data

class flask.Request(environpopulate_request=Trueshallow=False)

The request object used by default in Flask. Remembers the matched endpoint and view arguments.

It is what ends up as request. If you want to replace the request object used you can subclass this and set request_class to your subclass.

The request object is a Request subclass and provides all of the attributes Werkzeug defines plus a few Flask specific ones.

form

MultiDict with the parsed form data from POST or PUT requests. Please keep in mind that file uploads will not end up here, but instead in the filesattribute.

args

MultiDict with the parsed contents of the query string. (The part in the URL after the question mark).

values

CombinedMultiDict with the contents of both form and args.

cookies

dict with the contents of all cookies transmitted with the request.

stream

If the incoming form data was not encoded with a known mimetype the data is stored unmodified in this stream for consumption. Most of the time it is a better idea to use data which will give you that data as a string. The stream only returns the data once.

headers

The incoming request headers as a dictionary like object.

data

Contains the incoming request data as string in case it came with a mimetype Flask does not handle.

files

MultiDict with files uploaded as part of a POST or PUT request. Each file is stored as FileStorage object. It basically behaves like a standard file object you know from Python, with the difference that it also has a save() function that can store the file on the filesystem.

environ

The underlying WSGI environment.

method

The current request method (POSTGET etc.)

path
script_root
url
base_url
url_root

Provides different ways to look at the current URL. Imagine your application is listening on the following URL:

http://www.example.com/myapplication

And a user requests the following URL:

http://www.example.com/myapplication/page.html?x=y

In this case the values of the above mentioned attributes would be the following:

path /page.html
script_root /myapplication
base_url http://www.example.com/myapplication/page.html
url http://www.example.com/myapplication/page.html?x=y
url_root http://www.example.com/myapplication/
is_xhr

True if the request was triggered via a JavaScript XMLHttpRequest. This only works with libraries that support the X-Requested-With header and set it toXMLHttpRequest. Libraries that do that are prototype, jQuery and Mochikit and probably some more.

blueprint

The name of the current blueprint

endpoint

The endpoint that matched the request. This in combination with view_argscan be used to reconstruct the same or a modified URL. If an exception happened when matching, this will be None.

get_json(force=Falsesilent=Falsecache=True)

Parses the incoming JSON request data and returns it. If parsing fails theon_json_loading_failed() method on the request object will be invoked. By default this function will only load the json data if the mimetype isapplication/json but this can be overriden by the force parameter.

Parameters:
  • force – if set to True the mimetype is ignored.
  • silent – if set to False this method will fail silently and return False.
  • cache – if set to True the parsed JSON data is remembered on the request.
json

If the mimetype is application/json this will contain the parsed JSON data. Otherwise this will be None.

The get_json() method should be used instead.

max_content_length

Read-only view of the MAX_CONTENT_LENGTH config key.

module

The name of the current module if the request was dispatched to an actual module. This is deprecated functionality, use blueprints instead.

on_json_loading_failed(e)

Called if decoding of the JSON data failed. The return value of this method is used by get_json() when an error occurred. The default implementation just raises a BadRequest exception.

Changed in version 0.10: Removed buggy previous behavior of generating a random JSON response. If you want that behavior back you can trivially add it by subclassing.

New in version 0.8.

routing_exception = None

if matching the URL failed, this is the exception that will be raised / was raised as part of the request handling. This is usually a NotFound exception or something similar.

url_rule = None

the internal URL rule that matched the request. This can be useful to inspect which methods are allowed for the URL from a before/after handler (request.url_rule.methods) etc.

New in version 0.6.

view_args = None

a dict of view arguments that matched the request. If an exception happened when matching, this will be None.

class flask.request

To access incoming request data, you can use the global request object. Flask parses incoming request data for you and gives you access to it through that global object. Internally Flask makes sure that you always get the correct data for the active thread if you are in a multithreaded environment.

This is a proxy. See 关于代理 for more information.

The request object is an instance of a Request subclass and provides all of the attributes Werkzeug defines. This just shows a quick overview of the most important ones.

Response Objects

class flask.Response(response=Nonestatus=Noneheaders=None,mimetype=Nonecontent_type=Nonedirect_passthrough=False)

The response object that is used by default in Flask. Works like the response object from Werkzeug but is set to have an HTML mimetype by default. Quite often you don’t have to create this object yourself because make_response() will take care of that for you.

If you want to replace the response object used you can subclass this and setresponse_class to your subclass.

headers

Headers object representing the response headers.

status

A string with a response status.

status_code

The response status as integer.

data

A descriptor that calls get_data() and set_data(). This should not be used and will eventually get deprecated.

mimetype

The mimetype (content type without charset etc.)

Sets a cookie. The parameters are the same as in the cookie Morsel object in the Python standard library but it accepts unicode data, too.

Parameters:
  • key – the key (name) of the cookie to be set.
  • value – the value of the cookie.
  • max_age – should be a number of seconds, or None(default) if the cookie should last only as long as the client’s browser session.
  • expires – should be a datetime object or UNIX timestamp.
  • domain – if you want to set a cross-domain cookie. For example, domain=".example.com" will set a cookie that is readable by the domain www.example.com,foo.example.com etc. Otherwise, a cookie will only be readable by the domain that set it.
  • path – limits the cookie to a given path, per default it will span the whole domain.

Sessions

If you have the Flask.secret_key set you can use sessions in Flask applications. A session basically makes it possible to remember information from one request to another. The way Flask does this is by using a signed cookie. So the user can look at the session contents, but not modify it unless they know the secret key, so make sure to set that to something complex and unguessable.

To access the current session you can use the session object:

class flask.session

The session object works pretty much like an ordinary dict, with the difference that it keeps track on modifications.

This is a proxy. See 关于代理 for more information.

The following attributes are interesting:

new

True if the session is new, False otherwise.

modified

True if the session object detected a modification. Be advised that modifications on mutable structures are not picked up automatically, in that situation you have to explicitly set the attribute to True yourself. Here an example:

# this change is not picked up because a mutable object (here
# a list) is changed.
session['objects'].append(42)
# so mark it as modified yourself
session.modified = True
permanent

If set to True the session lives for permanent_session_lifetime seconds. The default is 31 days. If set to False (which is the default) the session will be deleted when the user closes the browser.

Session Interface

New in version 0.8.

The session interface provides a simple way to replace the session implementation that Flask is using.

class flask.sessions.SessionInterface

The basic interface you have to implement in order to replace the default session interface which uses werkzeug’s securecookie implementation. The only methods you have to implement are open_session() and save_session(), the others have useful defaults which you don’t need to change.

The session object returned by the open_session() method has to provide a dictionary like interface plus the properties and methods from the SessionMixin. We recommend just subclassing a dict and adding that mixin:

class Session(dict, SessionMixin):
    pass

If open_session() returns None Flask will call into make_null_session() to create a session that acts as replacement if the session support cannot work because some requirement is not fulfilled. The default NullSession class that is created will complain that the secret key was not set.

To replace the session interface on an application all you have to do is to assignflask.Flask.session_interface:

app = Flask(__name__)
app.session_interface = MySessionInterface()

New in version 0.8.

Helpful helper method that returns the cookie domain that should be used for the session cookie if session cookies are used.

Returns True if the session cookie should be httponly. This currently just returns the value of the SESSION_COOKIE_HTTPONLY config var.

Returns the path for which the cookie should be valid. The default implementation uses the value from the SESSION_COOKIE_PATH`` config var if it’s set, and falls back to APPLICATION_ROOT or uses / if it’s None.

Returns True if the cookie should be secure. This currently just returns the value of the SESSION_COOKIE_SECURE setting.

get_expiration_time(appsession)

A helper method that returns an expiration date for the session or None if the session is linked to the browser session. The default implementation returns now + the permanent session lifetime configured on the application.

is_null_session(obj)

Checks if a given object is a null session. Null sessions are not asked to be saved.

This checks if the object is an instance of null_session_class by default.

make_null_session(app)

Creates a null session which acts as a replacement object if the real session support could not be loaded due to a configuration error. This mainly aids the user experience because the job of the null session is to still support lookup without complaining but modifications are answered with a helpful error message of what failed.

This creates an instance of null_session_class by default.

null_session_class

make_null_session() will look here for the class that should be created when a null session is requested. Likewise the is_null_session() method will perform a typecheck against this type.

alias of NullSession

open_session(apprequest)

This method has to be implemented and must either return None in case the loading failed because of a configuration error or an instance of a session object which implements a dictionary like interface + the methods and attributes onSessionMixin.

pickle_based = False

A flag that indicates if the session interface is pickle based. This can be used by flask extensions to make a decision in regards to how to deal with the session object.

New in version 0.10.

save_session(appsessionresponse)

This is called for actual sessions returned by open_session() at the end of the request. This is still called during a request context so if you absolutely need access to the request you can do that.

class flask.sessions.SecureCookieSessionInterface

The default session interface that stores sessions in signed cookies through theitsdangerous module.

static digest_method()

the hash function to use for the signature. The default is sha1

key_derivation = 'hmac'

the name of the itsdangerous supported key derivation. The default is hmac.

salt = 'cookie-session'

the salt that should be applied on top of the secret key for the signing of cookie based sessions.

serializer = <flask.sessions.TaggedJSONSerializer object>

A python serializer for the payload. The default is a compact JSON derived serializer with support for some extra Python types such as datetime objects or tuples.

session_class

alias of SecureCookieSession

class flask.sessions.SecureCookieSession(initial=None)

Baseclass for sessions based on signed cookies.

class flask.sessions.NullSession(initial=None)

Class used to generate nicer error messages if sessions are not available. Will still allow read-only access to the empty session but fail on setting.

class flask.sessions.SessionMixin

Expands a basic dictionary with an accessors that are expected by Flask extensions and users for the session.

modified = True

for some backends this will always be True, but some backends will default this to false and detect changes in the dictionary for as long as changes do not happen on mutable structures in the session. The default mixin implementation just hardcodes True in.

new = False

some session backends can tell you if a session is new, but that is not necessarily guaranteed. Use with caution. The default mixin implementation just hardcodes False in.

permanent

this reflects the '_permanent' key in the dict.

flask.sessions.session_json_serializer = <flask.sessions.TaggedJSONSerializer object>

A customized JSON serializer that supports a few extra types that we take for granted when serializing (tuples, markup objects, datetime).

This object provides dumping and loading methods similar to simplejson but it also tags certain builtin Python objects that commonly appear in sessions. Currently the following extended values are supported in the JSON it dumps:

Notice

The PERMANENT_SESSION_LIFETIME config key can also be an integer starting with Flask 0.8. Either catch this down yourself or use the permanent_session_lifetimeattribute on the app which converts the result to an integer automatically.

Test Client

class flask.testing.FlaskClient(applicationresponse_wrapper=None,use_cookies=Trueallow_subdomain_redirects=False)

Works like a regular Werkzeug test client but has some knowledge about how Flask works to defer the cleanup of the request context stack to the end of a with body when used in a with statement. For general information about how to use this class refer to werkzeug.test.Client.

Basic usage is outlined in the 测试 Flask 应用 chapter.

session_transaction(*args**kwds)

When used in combination with a with statement this opens a session transaction. This can be used to modify the session that the test client uses. Once the with block is left the session is stored back.

with client.session_transaction() as session:
session[‘value’] = 42

Internally this is implemented by going through a temporary test request context and since session handling could depend on request variables this function accepts the same arguments as test_request_context() which are directly passed through.

Application Globals

To share data that is valid for one request only from one function to another, a global variable is not good enough because it would break in threaded environments. Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request. In a nutshell: it does the right thing, like it does for request and session.

flask.g

Just store on this whatever you want. For example a database connection or the user that is currently logged in.

Starting with Flask 0.10 this is stored on the application context and no longer on the request context which means it becomes available if only the application context is bound and not yet a request. This is especially useful when combined with the 伪造资源和环境 pattern for testing.

Additionally as of 0.10 you can use the get() method to get an attribute or None(or the second argument) if it’s not set. These two usages are now equivalent:

user = getattr(flask.g, 'user', None)
user = flask.g.get('user', None)

It’s now also possible to use the in operator on it to see if an attribute is defined and it yields all keys on iteration.

This is a proxy. See 关于代理 for more information.

Useful Functions and Classes

flask.current_app

Points to the application handling the request. This is useful for extensions that want to support multiple applications running side by side. This is powered by the application context and not by the request context, so you can change the value of this proxy by using the app_context() method.

This is a proxy. See 关于代理 for more information.

flask.has_request_context()

If you have code that wants to test if a request context is there or not this function can be used. For instance, you may want to take advantage of request information if the request object is available, but fail silently if it is unavailable.

class User(db.Model):

    def __init__(self, username, remote_addr=None):
        self.username = username
        if remote_addr is None and has_request_context():
            remote_addr = request.remote_addr
        self.remote_addr = remote_addr

Alternatively you can also just test any of the context bound objects (such asrequest or g for truthness):

class User(db.Model):

    def __init__(self, username, remote_addr=None):
        self.username = username
        if remote_addr is None and request:
            remote_addr = request.remote_addr
        self.remote_addr = remote_addr

New in version 0.7.

flask.copy_current_request_context(f)

A helper function that decorates a function to retain the current request context. This is useful when working with greenlets. The moment the function is decorated a copy of the request context is created and then pushed when the function is called.

Example:

import gevent
from flask import copy_current_request_context

@app.route('/')
def index():
    @copy_current_request_context
    def do_some_work():
        # do some work here, it can access flask.request like you
        # would otherwise in the view function.
        ...
    gevent.spawn(do_some_work)
    return 'Regular response'

New in version 0.10.

flask.has_app_context()

Works like has_request_context() but for the application context. You can also just do a boolean check on the current_app object instead.

New in version 0.9.

flask.url_for(endpoint**values)

Generates a URL to the given endpoint with the method provided.

Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments. If the value of a query argument is None, the whole pair is skipped. In case blueprints are active you can shortcut references to the same blueprint by prefixing the local endpoint with a dot (.).

This will reference the index function local to the current blueprint:

url_for('.index')

For more information, head over to the Quickstart.

To integrate applications, Flask has a hook to intercept URL build errors throughFlask.build_error_handler. The url_for function results in a BuildErrorwhen the current app does not have a URL for the given endpoint and values. When it does, the current_app calls its build_error_handler if it is not None, which can return a string to use as the result of url_for (instead of url_for‘s default to raise the BuildError exception) or re-raise the exception. An example:

def external_url_handler(error, endpoint, **values):
    "Looks up an external URL when `url_for` cannot build a URL."
    # This is an example of hooking the build_error_handler.
    # Here, lookup_url is some utility function you've built
    # which looks up the endpoint in some external URL registry.
    url = lookup_url(endpoint, **values)
    if url is None:
        # External lookup did not have a URL.
        # Re-raise the BuildError, in context of original traceback.
        exc_type, exc_value, tb = sys.exc_info()
        if exc_value is error:
            raise exc_type, exc_value, tb
        else:
            raise error
    # url_for will use this result, instead of raising BuildError.
    return url

app.build_error_handler = external_url_handler

Here, error is the instance of BuildError, and endpoint and **values are the arguments passed into url_for. Note that this is for building URLs outside the current application, and not for handling 404 NotFound errors.

New in version 0.10: The _scheme parameter was added.

New in version 0.9: The _anchor and _method parameters were added.

New in version 0.9: Calls Flask.handle_build_error() on BuildError.

Parameters:
  • endpoint – the endpoint of the URL (name of the function)
  • values – the variable arguments of the URL rule
  • _external – if set to True, an absolute URL is generated. Server address can be changed via SERVER_NAMEconfiguration variable which defaults to localhost.
  • _scheme – a string specifying the desired URL scheme. The_external parameter must be set to True or a ValueError is raised.
  • _anchor – if provided this is added as anchor to the URL.
  • _method – if provided this explicitly specifies an HTTP method.
flask.abort(code)

Raises an HTTPException for the given status code. For example to abort request handling with a page not found exception, you would call abort(404).

Parameters: code – the HTTP error code.
flask.redirect(locationcode=302)

Return a response object (a WSGI application) that, if called, redirects the client to the target location. Supported codes are 301, 302, 303, 305, and 307. 300 is not supported because it’s not a real redirect and 304 because it’s the answer for a request with a request with defined If-Modified-Since headers.

New in version 0.6: The location can now be a unicode string that is encoded using the iri_to_uri() function.

Parameters:
  • location – the location the response should redirect to.
  • code – the redirect status code. defaults to 302.
flask.make_response(*args)

Sometimes it is necessary to set additional headers in a view. Because views do not have to return response objects but can return a value that is converted into a response object by Flask itself, it becomes tricky to add headers to it. This function can be called instead of using a return and you will get a response object which you can use to attach headers.

If view looked like this and you want to add a new header:

def index():
    return render_template('index.html', foo=42)

You can now do something like this:

def index():
    response = make_response(render_template('index.html', foo=42))
    response.headers['X-Parachutes'] = 'parachutes are cool'
    return response

This function accepts the very same arguments you can return from a view function. This for example creates a response with a 404 error code:

response = make_response(render_template('not_found.html'), 404)

The other use case of this function is to force the return value of a view function into a response which is helpful with view decorators:

response = make_response(view_function())
response.headers['X-Parachutes'] = 'parachutes are cool'

Internally this function does the following things:

New in version 0.6.

flask.after_this_request(f)

Executes a function after this request. This is useful to modify response objects. The function is passed the response object and has to return the same or a new one.

Example:

@app.route('/')
def index():
    @after_this_request
    def add_header(response):
        response.headers['X-Foo'] = 'Parachute'
        return response
    return 'Hello World!'

This is more useful if a function other than the view function wants to modify a response. For instance think of a decorator that wants to add some headers without converting the return value into a response object.

New in version 0.9.

flask.send_file(filename_or_fpmimetype=Noneas_attachment=False,attachment_filename=Noneadd_etags=Truecache_timeout=None,conditional=False)

Sends the contents of a file to the client. This will use the most efficient method available and configured. By default it will try to use the WSGI server’s file_wrapper support. Alternatively you can set the application’s use_x_sendfileattribute to True to directly emit an X-Sendfile header. This however requires support of the underlying webserver for X-Sendfile.

By default it will try to guess the mimetype for you, but you can also explicitly provide one. For extra security you probably want to send certain files as attachment (HTML for instance). The mimetype guessing requires a filename or anattachment_filename to be provided.

Please never pass filenames to this function from user sources without checking them first. Something like this is usually sufficient to avoid security problems:

if '..' in filename or filename.startswith('/'):
    abort(404)

New in version 0.2.

New in version 0.5: The add_etagscache_timeout and conditional parameters were added. The default behavior is now to attach etags.

Changed in version 0.7: mimetype guessing and etag support for file objects was deprecated because it was unreliable. Pass a filename if you are able to, otherwise attach an etag yourself. This functionality will be removed in Flask 1.0

Changed in version 0.9: cache_timeout pulls its default from application config, when None.

Parameters:
  • filename_or_fp – the filename of the file to send. This is relative to the root_path if a relative path is specified. Alternatively a file object might be provided in which case X-Sendfile might not work and fall back to the traditional method. Make sure that the file pointer is positioned at the start of data to send before calling send_file().
  • mimetype – the mimetype of the file if provided, otherwise auto detection happens.
  • as_attachment – set to True if you want to send this file with a Content-Disposition: attachment header.
  • attachment_filename – the filename for the attachment if it differs from the file’s filename.
  • add_etags – set to False to disable attaching of etags.
  • conditional – set to True to enable conditional responses.
  • cache_timeout – the timeout in seconds for the headers. When None (default), this value is set byget_send_file_max_age() of current_app.
flask.send_from_directory(directoryfilename**options)

Send a file from a given directory with send_file(). This is a secure way to quickly expose static files from an upload folder or something similar.

Example usage:

@app.route('/uploads/<path:filename>')
def download_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'],
                               filename, as_attachment=True)

Sending files and Performance

It is strongly recommended to activate either X-Sendfile support in your webserver or (if no authentication happens) to tell the webserver to serve files for the given path on its own without calling into the web application for improved performance.

New in version 0.5.

Parameters:
  • directory – the directory where all the files are stored.
  • filename – the filename relative to that directory to download.
  • options – optional keyword arguments that are directly forwarded to send_file().
flask.safe_join(directoryfilename)

Safely join directory and filename.

Example usage:

@app.route('/wiki/<path:filename>')
def wiki_page(filename):
    filename = safe_join(app.config['WIKI_FOLDER'], filename)
    with open(filename, 'rb') as fd:
        content = fd.read() # Read and process the file content...
Parameters:
  • directory – the base directory.
  • filename – the untrusted filename relative to that directory.
Raises:

NotFound if the resulting path would fall out of directory.

flask.escape(s) → markup

Convert the characters &, <, >, ‘, and ” in string s to HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. Marks return value as markup string.

class flask.Markup

Marks a string as being safe for inclusion in HTML/XML output without needing to be escaped. This implements the __html__ interface a couple of frameworks and web applications use. Markup is a direct subclass of unicode and provides all the methods of unicode just that it escapes arguments passed and always returnsMarkup.

The escape function returns markup objects so that double escaping can’t happen.

The constructor of the Markup class can be used for three different things: When passed an unicode object it’s assumed to be safe, when passed an object with an HTML representation (has an __html__ method) that representation is used, otherwise the object passed is converted into a unicode string and then assumed to be safe:

>>> Markup("Hello <em>World</em>!")
Markup(u'Hello <em>World</em>!')
>>> class Foo(object):
...  def __html__(self):
...   return '<a href="#">foo</a>'
...
>>> Markup(Foo())
Markup(u'<a href="#">foo</a>')

If you want object passed being always treated as unsafe you can use the escape()classmethod to create a Markup object:

>>> Markup.escape("Hello <em>World</em>!")
Markup(u'Hello &lt;em&gt;World&lt;/em&gt;!')

Operations on a markup string are markup aware which means that all arguments are passed through the escape() function:

>>> em = Markup("<em>%s</em>")
>>> em % "foo & bar"
Markup(u'<em>foo &amp; bar</em>')
>>> strong = Markup("<strong>%(text)s</strong>")
>>> strong % {'text': '<blink>hacker here</blink>'}
Markup(u'<strong>&lt;blink&gt;hacker here&lt;/blink&gt;</strong>')
>>> Markup("<em>Hello</em> ") + "<foo>"
Markup(u'<em>Hello</em> &lt;foo&gt;')
classmethod escape(s)

Escape the string. Works like escape() with the difference that for subclasses of Markup this function would return the correct subclass.

striptags()

Unescape markup into an text_type string and strip all tags. This also resolves known HTML4 and XHTML entities. Whitespace is normalized to one:

>>> Markup("Main &raquo;  <em>About</em>").striptags()
u'Main \xbb About'
unescape()

Unescape markup again into an text_type string. This also resolves known HTML4 and XHTML entities:

>>> Markup("Main &raquo; <em>About</em>").unescape()
u'Main \xbb <em>About</em>'

Message Flashing

flask.flash(messagecategory='message')

Flashes a message to the next request. In order to remove the flashed message from the session and to display it to the user, the template has to callget_flashed_messages().

Changed in version 0.3: category parameter added.

Parameters:
  • message – the message to be flashed.
  • category – the category for the message. The following values are recommended: 'message' for any kind of message, 'error'for errors, 'info' for information messages and 'warning' for warnings. However any kind of string can be used as category.
flask.get_flashed_messages(with_categories=Falsecategory_filter=[])

Pulls all flashed messages from the session and returns them. Further calls in the same request to the function will return the same messages. By default just the messages are returned, but when with_categories is set to True, the return value will be a list of tuples in the form (category, message) instead.

Filter the flashed messages to one or more categories by providing those categories in category_filter. This allows rendering categories in separate html blocks. Thewith_categories and category_filter arguments are distinct:

  • with_categories controls whether categories are returned with message text (True gives a tuple, where False gives just the message text).
  • category_filter filters the messages down to only those matching the provided categories.

See 消息闪现 for examples.

Changed in version 0.3: with_categories parameter added.

Changed in version 0.9: category_filter parameter added.

Parameters:
  • with_categories – set to True to also receive categories.
  • category_filter – whitelist of categories to limit return values

JSON Support

Flask uses simplejson for the JSON implementation. Since simplejson is provided both by the standard library as well as extension Flask will try simplejson first and then fall back to the stdlib json module. On top of that it will delegate access to the current application’s JSOn encoders and decoders for easier customization.

So for starters instead of doing:

try:
    import simplejson as json
except ImportError:
    import json

You can instead just do this:

from flask import json

For usage examples, read the json documentation in the standard lirbary. The following extensions are by default applied to the stdlib’s JSON module:

  1. datetime objects are serialized as RFC 822 strings.
  2. Any object with an __html__ method (like Markup) will ahve that method called and then the return value is serialized as string.

The htmlsafe_dumps() function of this json module is also available as filter called|tojson in Jinja2. Note that inside script tags no escaping must take place, so make sure to disable escaping with |safe if you intend to use it inside script tags unless you are using Flask 0.10 which implies that:

<script type=text/javascript>
    doSomethingWith({{ user.username|tojson|safe }});
</script>
flask.json.jsonify(*args**kwargs)

Creates a Response with the JSON representation of the given arguments with anapplication/json mimetype. The arguments to this function are the same as to thedict constructor.

Example usage:

from flask import jsonify

@app.route('/_get_current_user')
def get_current_user():
    return jsonify(username=g.user.username,
                   email=g.user.email,
                   id=g.user.id)

This will send a JSON response like this to the browser:

{
    "username": "admin",
    "email": "admin@localhost",
    "id": 42
}

For security reasons only objects are supported toplevel. For more information about this, have a look at JSON 安全.

This function’s response will be pretty printed if it was not requested with X-Requested-With: XMLHttpRequest to simplify debugging unless theJSONIFY_PRETTYPRINT_REGULAR config parameter is set to false.

New in version 0.2.

flask.json.dumps(obj**kwargs)

Serialize obj to a JSON formatted str by using the application’s configured encoder (json_encoder) if there is an application on the stack.

This function can return unicode strings or ascii-only bytestrings by default which coerce into unicode strings automatically. That behavior by default is controlled by the JSON_AS_ASCII configuration variable and can be overriden by the simplejsonensure_ascii parameter.

flask.json.dump(objfp**kwargs)

Like dumps() but writes into a file object.

flask.json.loads(s**kwargs)

Unserialize a JSON object from a string s by using the application’s configured decoder (json_decoder) if there is an application on the stack.

flask.json.load(fp**kwargs)

Like loads() but reads from a file object.

class flask.json.JSONEncoder(skipkeys=Falseensure_ascii=True,check_circular=Trueallow_nan=Truesort_keys=Falseindent=None,separators=Noneencoding='utf-8'default=None)

The default Flask JSON encoder. This one extends the default simplejson encoder by also supporting datetime objects, UUID as well as Markup objects which are serialized as RFC 822 datetime strings (same as the HTTP date format). In order to support more data types override the default() method.

default(o)

Implement this method in a subclass such that it returns a serializable object for o, or calls the base implementation (to raise a TypeError).

For example, to support arbitrary iterators, you could implement default like this:

def default(self, o):
    try:
        iterable = iter(o)
    except TypeError:
        pass
    else:
        return list(iterable)
    return JSONEncoder.default(self, o)
class flask.json.JSONDecoder(encoding=Noneobject_hook=None,parse_float=Noneparse_int=Noneparse_constant=Nonestrict=True,object_pairs_hook=None)

The default JSON decoder. This one does not change the behavior from the default simplejson encoder. Consult the json documentation for more information. This decoder is not only used for the load functions of this module but also Request.

Template Rendering

flask.render_template(template_name_or_list**context)

Renders a template from the template folder with the given context.

Parameters:
  • template_name_or_list – the name of the template to be rendered, or an iterable with template names the first one existing will be rendered
  • context – the variables that should be available in the context of the template.
flask.render_template_string(source**context)

Renders a template from the given template source string with the given context.

Parameters:
  • source – the sourcecode of the template to be rendered
  • context – the variables that should be available in the context of the template.
flask.get_template_attribute(template_nameattribute)

Loads a macro (or variable) a template exports. This can be used to invoke a macro from within Python code. If you for example have a template named _cider.htmlwith the following contents:

{% macro hello(name) %}Hello {{ name }}!{% endmacro %}

You can access this from Python code like this:

hello = get_template_attribute('_cider.html', 'hello')
return hello('World')

New in version 0.2.

Parameters:
  • template_name – the name of the template
  • attribute – the name of the variable of macro to access

Configuration

class flask.Config(root_pathdefaults=None)

Works exactly like a dict but provides ways to fill it from files or special dictionaries. There are two common patterns to populate the config.

Either you can fill the config from a config file:

app.config.from_pyfile('yourconfig.cfg')

Or alternatively you can define the configuration options in the module that callsfrom_object() or provide an import path to a module that should be loaded. It is also possible to tell it to use the same module and with that provide the configuration values just before the call:

DEBUG = True
SECRET_KEY = 'development key'
app.config.from_object(__name__)

In both cases (loading from any Python file or loading from modules), only uppercase keys are added to the config. This makes it possible to use lowercase values in the config file for temporary values that are not added to the config or to define the config keys in the same file that implements the application.

Probably the most interesting way to load configurations is from an environment variable pointing to a file:

app.config.from_envvar('YOURAPPLICATION_SETTINGS')

In this case before launching the application you have to set this environment variable to the file you want to use. On Linux and OS X use the export statement:

export YOURAPPLICATION_SETTINGS='/path/to/config/file'

On windows use set instead.

Parameters:
  • root_path – path to which files are read relative from. When the config object is created by the application, this is the application’s root_path.
  • defaults – an optional dictionary of default values
from_envvar(variable_namesilent=False)

Loads a configuration from an environment variable pointing to a configuration file. This is basically just a shortcut with nicer error messages for this line of code:

app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
Parameters:
  • variable_name – name of the environment variable
  • silent – set to True if you want silent failure for missing files.
Returns:

bool. True if able to load config, False otherwise.

from_object(obj)

Updates the values from the given object. An object can be of one of the following two types:

  • a string: in this case the object with that name will be imported
  • an actual object reference: that object is used directly

Objects are usually either modules or classes.

Just the uppercase variables in that object are stored in the config. Example usage:

app.config.from_object('yourapplication.default_config')
from yourapplication import default_config
app.config.from_object(default_config)

You should not use this function to load the actual configuration but rather configuration defaults. The actual config should be loaded withfrom_pyfile() and ideally from a location not within the package because the package might be installed system wide.

Parameters: obj – an import name or object
from_pyfile(filenamesilent=False)

Updates the values in the config from a Python file. This function behaves as if the file was imported as module with the from_object() function.

Parameters:
  • filename – the filename of the config. This can either be an absolute filename or a filename relative to the root path.
  • silent – set to True if you want silent failure for missing files.

New in version 0.7: silent parameter.

Extensions

flask.ext

This module acts as redirect import module to Flask extensions. It was added in 0.8 as the canonical way to import Flask extensions and makes it possible for us to have more flexibility in how we distribute extensions.

If you want to use an extension named “Flask-Foo” you would import it from ext as follows:

from flask.ext import foo

New in version 0.8.

Stream Helpers

flask.stream_with_context(generator_or_function)

Request contexts disappear when the response is started on the server. This is done for efficiency reasons and to make it less likely to encounter memory leaks with badly written WSGI middlewares. The downside is that if you are using streamed responses, the generator cannot access request bound information any more.

This function however can help you keep the context around for longer:

from flask import stream_with_context, request, Response

@app.route('/stream')
def streamed_response():
    @stream_with_context
    def generate():
        yield 'Hello '
        yield request.args['name']
        yield '!'
    return Response(generate())

Alternatively it can also be used around a specific generator:

from flask import stream_with_context, request, Response

@app.route('/stream')
def streamed_response():
    def generate():
        yield 'Hello '
        yield request.args['name']
        yield '!'
    return Response(stream_with_context(generate()))

New in version 0.9.

Useful Internals

class flask.ctx.RequestContext(appenvironrequest=None)

The request context contains all request relevant information. It is created at the beginning of the request and pushed to the _request_ctx_stack and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided.

Do not attempt to use this class directly, instead use test_request_context()and request_context() to create this object.

When the request context is popped, it will evaluate all the functions registered on the application for teardown execution (teardown_request()).

The request context is automatically popped at the end of the request for you. In debug mode the request context is kept around if exceptions happen so that interactive debuggers have a chance to introspect the data. With 0.4 this can also be forced for requests that did not fail and outside of DEBUG mode. By setting'flask._preserve_context' to True on the WSGI environment the context will not pop itself at the end of the request. This is used by the test_client() for example to implement the deferred cleanup functionality.

You might find this helpful for unittests where you need the information from the context local around for a little longer. Make sure to properly pop() the stack yourself in that situation, otherwise your unittests will leak memory.

copy()

Creates a copy of this request context with the same request object. This can be used to move a request context to a different greenlet. Because the actual request object is the same this cannot be used to move a request context to a different thread unless access to the request object is locked.

New in version 0.10.

match_request()

Can be overridden by a subclass to hook into the matching of the request.

pop(exc=None)

Pops the request context and unbinds it by doing that. This will also trigger the execution of functions registered by the teardown_request() decorator.

Changed in version 0.9: Added the exc argument.

push()

Binds the request context to the current context.

flask._request_ctx_stack

The internal LocalStack that is used to implement all the context local objects used in Flask. This is a documented instance and can be used by extensions and application code but the use is discouraged in general.

The following attributes are always present on each layer of the stack:

app
the active Flask application.
url_adapter
the URL adapter that was used to match the request.
request
the current request object.
session
the active session object.
g
an object with all the attributes of the flask.g object.
flashes
an internal cache for the flashed messages.

Example usage:

from flask import _request_ctx_stack

def get_session():
    ctx = _request_ctx_stack.top
    if ctx is not None:
        return ctx.session
class flask.ctx.AppContext(app)

The application context binds an application object implicitly to the current thread or greenlet, similar to how the RequestContext binds request information. The application context is also implicitly created if a request context is created but the application is not on top of the individual application context.

pop(exc=None)

Pops the app context.

push()

Binds the app context to the current context.

flask._app_ctx_stack

Works similar to the request context but only binds the application. This is mainly there for extensions to store data.

New in version 0.9.

class flask.blueprints.BlueprintSetupState(blueprintappoptions,first_registration)

Temporary holder object for registering a blueprint with the application. An instance of this class is created by the make_setup_state() method and later passed to all register callback functions.

add_url_rule(ruleendpoint=Noneview_func=None**options)

A helper method to register a rule (and optionally a view function) to the application. The endpoint is automatically prefixed with the blueprint’s name.

app = None

a reference to the current application

blueprint = None

a reference to the blueprint that created this setup state.

first_registration = None

as blueprints can be registered multiple times with the application and not everything wants to be registered multiple times on it, this attribute can be used to figure out if the blueprint was registered in the past already.

options = None

a dictionary with all options that were passed to the register_blueprint()method.

subdomain = None

The subdomain that the blueprint should be active for, None otherwise.

url_defaults = None

A dictionary with URL defaults that is added to each and every URL that was defined with the blueprint.

url_prefix = None

The prefix that should be used for all URLs defined on the blueprint.

Signals

New in version 0.6.

flask.signals_available

True if the signalling system is available. This is the case when blinker is installed.

flask.template_rendered

This signal is sent when a template was successfully rendered. The signal is invoked with the instance of the template as template and the context as dictionary (namedcontext).

flask.request_started

This signal is sent before any request processing started but when the request context was set up. Because the request context is already bound, the subscriber can access the request with the standard global proxies such as request.

flask.request_finished

This signal is sent right before the response is sent to the client. It is passed the response to be sent named response.

flask.got_request_exception

This signal is sent when an exception happens during request processing. It is sentbefore the standard exception handling kicks in and even in debug mode, where no exception handling happens. The exception itself is passed to the subscriber asexception.

flask.request_tearing_down

This signal is sent when the application is tearing down the request. This is always called, even if an error happened. An exc keyword argument is passed with the exception that caused the teardown.

Changed in version 0.9: The exc parameter was added.

flask.appcontext_tearing_down

This signal is sent when the application is tearing down the application context. This is always called, even if an error happened. An exc keyword argument is passed with the exception that caused the teardown. The sender is the application.

flask.appcontext_pushed

This signal is sent when an application context is pushed. The sender is the application.

New in version 0.10.

flask.appcontext_popped

This signal is sent when an application context is popped. The sender is the application. This usually falls in line with the appcontext_tearing_down signal.

New in version 0.10.

flask.message_flashed

This signal is sent when the application is flashing a message. The messages is sent as message keyword argument and the category as category.

New in version 0.10.

class flask.signals.Namespace

An alias for blinker.base.Namespace if blinker is available, otherwise a dummy class that creates fake signals. This class is available for Flask extensions that want to provide the same fallback system as Flask itself.

signal(namedoc=None)

Creates a new signal for this namespace if blinker is available, otherwise returns a fake signal that has a send method that will do nothing but will fail with a RuntimeError for all other operations, including connecting.

Class-Based Views

New in version 0.7.

class flask.views.View

Alternative way to use view functions. A subclass has to implementdispatch_request() which is called with the view arguments from the URL routing system. If methods is provided the methods do not have to be passed to theadd_url_rule() method explicitly:

class MyView(View):
    methods = ['GET']

    def dispatch_request(self, name):
        return 'Hello %s!' % name

app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))

When you want to decorate a pluggable view you will have to either do that when the view function is created (by wrapping the return value of as_view()) or you can use the decorators attribute:

class SecretView(View):
    methods = ['GET']
    decorators = [superuser_required]

    def dispatch_request(self):
        ...

The decorators stored in the decorators list are applied one after another when the view function is created. Note that you can not use the class based decorators since those would decorate the view class and not the generated view function!

classmethod as_view(name*class_args**class_kwargs)

Converts the class into an actual view function that can be used with the routing system. Internally this generates a function on the fly which will instantiate the View on each request and call the dispatch_request()method on it.

The arguments passed to as_view() are forwarded to the constructor of the class.

decorators = []

The canonical way to decorate class-based views is to decorate the return value of as_view(). However since this moves parts of the logic from the class declaration to the place where it’s hooked into the routing system.

You can place one or more decorators in this list and whenever the view function is created the result is automatically decorated.

New in version 0.8.

dispatch_request()

Subclasses have to override this method to implement the actual view function code. This method is called with all the arguments from the URL rule.

methods = None

A for which methods this pluggable view can handle.

class flask.views.MethodView

Like a regular class-based view but that dispatches requests to particular methods. For instance if you implement a method called get() it means you will response to'GET' requests and the dispatch_request() implementation will automatically forward your request to that. Also options is set for you automatically:

class CounterAPI(MethodView):

    def get(self):
        return session.get('counter', 0)

    def post(self):
        session['counter'] = session.get('counter', 0) + 1
        return 'OK'

app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))

URL Route Registrations

Generally there are three ways to define rules for the routing system:

  1. You can use the flask.Flask.route() decorator.
  2. You can use the flask.Flask.add_url_rule() function.
  3. You can directly access the underlying Werkzeug routing system which is exposed as flask.Flask.url_map.

Variable parts in the route can be specified with angular brackets (/user/<username>). By default a variable part in the URL accepts any string without a slash however a different converter can be specified as well by using <converter:name>.

Variable parts are passed to the view function as keyword arguments.

The following converters are available:

string accepts any text without a slash (the default)
int accepts integers
float like int but for floating point values
path like the default but also accepts slashes

Here are some examples:

@app.route('/')
def index():
    pass

@app.route('/<username>')
def show_user(username):
    pass

@app.route('/post/<int:post_id>')
def show_post(post_id):
    pass

An important detail to keep in mind is how Flask deals with trailing slashes. The idea is to keep each URL unique so the following rules apply:

  1. If a rule ends with a slash and is requested without a slash by the user, the user is automatically redirected to the same page with a trailing slash attached.
  2. If a rule does not end with a trailing slash and the user requests the page with a trailing slash, a 404 not found is raised.

This is consistent with how web servers deal with static files. This also makes it possible to use relative link targets safely.

You can also define multiple rules for the same function. They have to be unique however. Defaults can also be specified. Here for example is a definition for a URL that accepts an optional page:

@app.route('/users/', defaults={'page': 1})
@app.route('/users/page/<int:page>')
def show_users(page):
    pass

This specifies that /users/ will be the URL for page one and /users/page/N will be the URL for page N.

Here are the parameters that route() and add_url_rule() accept. The only difference is that with the route parameter the view function is defined with the decorator instead of the view_func parameter.

rule the URL rule as string
endpoint the endpoint for the registered URL rule. Flask itself assumes that the name of the view function is the name of the endpoint if not explicitly stated.
view_func the function to call when serving a request to the provided endpoint. If this is not provided one can specify the function later by storing it in the view_functions dictionary with the endpoint as key.
defaults A dictionary with defaults for this rule. See the example above for how defaults work.
subdomain specifies the rule for the subdomain in case subdomain matching is in use. If not specified the default subdomain is assumed.
**options the options to be forwarded to the underlying Rule object. A change to Werkzeug is handling of method options. methods is a list of methods this rule should be limited to (GETPOST etc.). By default a rule just listens for GET (and implicitly HEAD). Starting with Flask 0.6, OPTIONS is implicitly added and handled by the standard request handling. They have to be specified as keyword arguments.

View Function Options

For internal usage the view functions can have some attributes attached to customize behavior the view function would normally not have control over. The following attributes can be provided optionally to either override some defaults toadd_url_rule() or general behavior:

  • __name__: The name of a function is by default used as endpoint. If endpoint is provided explicitly this value is used. Additionally this will be prefixed with the name of the blueprint by default which cannot be customized from the function itself.
  • methods: If methods are not provided when the URL rule is added, Flask will look on the view function object itself is an methods attribute exists. If it does, it will pull the information for the methods from there.
  • provide_automatic_options: if this attribute is set Flask will either force enable or disable the automatic implementation of the HTTP OPTIONS response. This can be useful when working with decorators that want to customize the OPTIONSresponse on a per-view basis.
  • required_methods: if this attribute is set, Flask will always add these methods when registering a URL rule even if the methods were explicitly overriden in theroute() call.

Full example:

def index():
    if request.method == 'OPTIONS':
        # custom options handling here
        ...
    return 'Hello World!'
index.provide_automatic_options = False
index.methods = ['GET', 'OPTIONS']

app.add_url_rule('/', index)

New in version 0.8: The provide_automatic_options functionality was added.

其他材料

这部分文档包括:设计要点、法律信息和变动记录。

Flask 的设计思路

为什么 Flask 要这样做,而不是那样做?如果你对这点好奇,那么本节可以满足 你的好奇心。当与其他框架直接进行比较时, Flask 的设计思路乍看可能显得武断 并且令人吃惊,下面我们就来看看为什么在设计的时候进行这样决策。

显式的应用对象

一个基于 WSGI 的 Python web 应用必须有一个实现实际的应用的中心调用对象。 在 Flask 中,中心调用对象是一个 Flask 类的实例。每个 Flask 应用必须创建一个该类的实例,并且把模块的名称传递给该实例。但是为什么 Flask 不自动把这些事都做好呢?

下面的代码:

from flask import Flask
app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'

如果没有一个显式的应用对象,那么会是这样的:

from hypothetical_flask import route

@route('/')
def index():
    return 'Hello World!'

使用对象的主要有三个原因。最重要的一个原因是显式对象可以保证实例的唯一性。 有很多方法可以用单个应用对象来冒充多应用,比如维护一个应用堆栈,但是这样 将会导致一些问题,这里我就不展开了。现在的问题是:一个微框架何时会需要 多应用?最好的回答是当进行单元测试的时候。在进行测试时,创建一个最小应用 用于测试特定的功能,是非常有用的。当这个最小应用的应用对象被删除时,将会 释放其占用的所有资源。

另外当使用显式对象时,你可以继承基类( Flask ), 以便于修改特定的功能。如果不使用显式对象,那么就无从下手了。

第二个原因也很重要,那就是 Flask 需要包的名称。当你创建一个 Flask 实例时, 通常会传递 __name__ 作为包的名称。 Flask 根据包的名称来载入也模块相关 的正确资源。通过 Python 杰出的反射功能,就可以找到模板和静态文件(参见 open_resource())。很显然,有其他的框架不需要任何配置 就可以载入与模块相关的模板。但其前提是必须使用当前工作目录,这是一个不可靠 的实现方式。当前工作目录是进程级的,如果有多个应用使用同一个进程( web 服务器可能在你不知情的情况下这样做),那么当前工作目录就不可用了。还有更 糟糕的情况:许多 web 服务器把文档根目录作为当前工作目录,如果你的应用所在 的目录不是文档根目录,那么就会出错。

第三个原因是“显式比隐式更好”。这个对象就是你的 WSGI 应用,你不必再记住其他 东西。如果你要实现一个 WSGI 中间件,那么只要封装它就可以了(还有更好的 方式,可以不丢失应用对象的引用,参见: wsgi_app() )。

再者,只有这样设计才能使用工厂函数来创建应用,方便单元测试和类似的工作 (参见: 应用工厂 )。

路由系统

Flask 使用 Werkzeug 路由系统,该系统是自动根据复杂度来为路由排序的。也就是 说你可以以任意顺序来声明路由,路由系统仍然能够正常工作。为什么要实现这个 功能?因为当应用被切分成多个模块时,基于路由的装饰器会以乱序触发,所以这个 功能是必须的。

另一点是 Werkzeug 路由系统必须确保 URL 是唯一的,并且会把模糊路由重定向到 标准的 URL 。

唯一模板引擎

Flask 原生只使用 Jinja2 模板引擎。为什么不设计一个可插拔的模板引擎接口? 当然,你可以在 Flask 中使用其他模板引擎,但是当前 Flask 原生只会支持 Jinja2 。将来也许 Flask 会使用其他引擎,但是永远只会绑定一个模板引擎。

模板引擎与编程语言类似,每个引擎都有自己的一套工作方式。表面上它们都看 上去差不多:你把一套变量丢给引擎,然后得到字符串形式的模板。

但是相似之处也仅限于此。例如 Jinja2 有丰富的过滤系统、有一定的模板继承 能力、支持从模板内或者 Python 代码内复用块(宏)、所有操作都使用 Unicode 、支持迭代模板渲染以及可配置语法等等。而比如 Genshi 基于 XML 流 赋值,其模板继承基于 XPath 的能力。再如 Mako 使用类似 Python 模块的方式 来处理模板。

当一个应用或者框架与一个模板引擎结合在一起的时候,事情就不只是渲染模板 这么简单了。例如, Flask 使用了 Jinja2 的强大的自动转义功能。同时 Flask 也为 Jinja2 提供了在模板中操作宏的途径。

一个不失模板引擎独特性的模板抽象层本身就是一门学问,因此这不是一个 Flask 之类的微框架应该考虑的事情。

此外,只使用一个模板语言可以方便扩展。你可以使用你自己的模板语言,但扩展 仍然使用 Jinja 。

我依赖所以我微

为什么 Flask 依赖两个库( Werkzeug 和 Jinja2 ),但还是自称是微框架? 为什么不可以呢?如果我们看一看 Web 开发的另一大阵营 Ruby ,那么可以发现 一个与 WSGI 十分相似的协议。这个协议被称为 Rack ,除了名称不同外,基本 可以视作 Ruby 版的 WSGI 。但是几乎所有 Ruby 应用都不直接使用 Rack 协议, 而是使用一个相同名字的库。在 Python 中,与 Rack 库等价的有 WebOb (前身是 Paste )和 Werkzeug 两个库。 Paste 任然可用,但是个人认为正逐步 被 WebOb 取代。WebOb 和 Werkzeug 的开发初衷都是:做一个 WSGI 协议的出色 实现,让其他应用受益。

正应为 Werkzeug 出色地实现了 WSGI 协议(有时候这是一个复杂的任务),使得 依赖于 Werkzeug 的 Flask 受益良多。同时要感谢 Python 包管理的近期开发, 包依赖问题已经解决,几乎没有理由不使用包依赖的方式。

线程本地对象

是的,通常情况下使用线程局域变量不是一个明智的主意。它们在不基于线程概念的服务器上会导致问题,并且使得大型应用难以维护。但 Flask 不仅为大型应用或异步服务器设计。 Flask 想要使得编写一个传统 web 应用的过程快速而简单。

一些关于基于 Flask 大型应用的灵感,见文档的 聚沙成塔 一节。

Flask 使用线程本地对象(实际是环境本地对象,它们同意支持 greenlet 环境) 来支持请求、会话和一个可以放置你自己的东西的额外对象( g )。 为什么要这样做?这不是一个坏主意吗?

是的,通常情况下使用线程本地对象不是一个明智的选择,这会在不是基于线程理念的 服务器上造成麻烦,并且加大大型应用的维护难度。但是 Flask 不仅是为大型应用或 异步服务器设计的, Flask 还想简化和加速传统 web 应用的开发。

一些关于基于 Flask 大型应用的灵感,见文档的 大型应用 一节。

Flask 是什么,不是什么

Flask 永远不会包含数据库层,也不会有表单库或是这个方面的其它东西。 Flask 本身只是 Werkezug 和 Jinja2 的之间的桥梁,前者实现一个合适的 WSGI 应用,后者处理模板。 当然, Flask 也绑定了一些通用的标准库包,比如 logging 。 除此之外其它所有一切都交给扩展来实现。

为什么呢?因为人们有不同的偏好和需求, Flask 不可能把所有的需求都囊括在 核心里。大多数 web 应用会需要一个模板引擎。然而不是每个应用都需要一个 SQL 数据库的。

Flask 的理念是为所有应用建立一个良好的基础,其余的一切都取决于你自己或者 扩展。

HTML/XHTML 常见问答

Flask 的文档和示例应用使用 HTML5 。你可能会注意到,在许多情况下,当结束标记 是可选的时候,并不使用它们,这样 HTML 会更简洁且加载更迅速。因为在开发者中, 有许多关于 HTML 和 XHTML 的混淆,本文档尝试回答一些主要的疑问。

XHTML 的历史

有一段时间, XHTML 横空出世,大有取代 HTML 之势。然而时至今日,鲜有真正使用 XHTML (根据 XML 规则处理的 HTML )的网站。出现这种情况的原因很多。其一是 Internet Explorer 对 XHTML 支持不完善。 根据规范要求 XHTML 必须使用application/xhtml+xml MIME 类型,但是 Internet Explorer 却拒绝读取这个 MIME 类型的文件。

虽然通过配置 Web 服务器来为 XHTML 提供正确的服务相对简单,但是却很少有人这么 做。这可能是因为正确地使用 XHTML 是一件很痛苦的事情。

痛中之通是 XML 苛刻的(严厉且无情)错误处理。当 XML 处理中遭遇错误时,浏览器 会把一个丑陋的错误消息显示给用户,而不是尝试从错误中恢并显示出能显示的部分。 web 上大多数的 (X)HTML 是基于非 XML 的模板引擎(比如 Flask 所使用的 Jinja) 生成的。而这些模板引擎并不会阻止你偶然创建无效的 XHTML 。也有基于 XML 的模板 引擎,诸如 Kid 和流行的 Genshi ,但是它们通常具有更大的运行时开销, 并且用 起来很不爽,因为它们必须遵守 XML 规则。

大多数用户,不管怎样,假设他们正确地使用了 XHTML 。他们在文档的顶部写下一个 XHTML doctype ,并且闭合了所有必要闭合的标签( 在 XHTML 中 <br> 要写作 <br/> 或 <br></br> )。即使文档可以正确地通过 XHTML 验证,然而真正 决定浏览器中 XHTML/HTML 处理的是前面提到的,经常不被正确设置的 MIME 类型。 一旦类型错误,有效的 XHTML 会被视作无效的 HTML 处理。

XHTML 也改变了使用 JavaScript 的方式。要在 XHTML 下正确地工作,程序员不得不 使用带有 XHTML 名称空间的 DOM 接口来查询 HTML 元素。

HTML5 的历史

HTML5 规范是由网络超文本应用技术工作组( WHATWG )于 2004 年开始制定的,最初 的名称是“ Web 应用1.0 ”。 WHATWG 由主要的浏览器供应商苹果、 Mozilla 和 Opera 组成。 HTML5 规范的目标是编写一个新的更好的 HTML 规范,该规范是基于现有浏览器 的行为的,而不是不切实际的,不向后兼容的。

例如,在 HTML4 中 <title/Hello/ 与 <title>Hello</title> 理论上完全 相同。然而,由于人们沿用了 <link /> 之类的 XHTML-like 标签, 浏览器就会 识别为 XHTML 而不是 HTML 。

2007 年, W3C 以这个规范为基础,制定了一个新的 HTML 规范,也就是 HTML5 。现在, 随着 XHTML 2 工作组的解散,而且 HTML5 正在被所有主流浏览器供应商实现,XHTML 逐渐失去了吸引力。

HTML 对比 XHTML

下面的表格展示 HTML 4.01 、 XHTML 1.1 和 HTML5 简要功能比价。(不包括 XHTML 1.0 ,因为它已经被 XHTML 1.1 和几乎不使用的 XHTML5 代替。)

 HTML4.01XHTML1.1HTML5
<tag/value/ == <tag>value</tag> Yes [1] No No
支持 <br/> No Yes Yes [2]
支持 <script/> No Yes No
应该解析为 text/html Yes No [3] Yes
应该解析为 application/xhtml+xml No Yes No
严格的错误处理 No Yes No
内联 SVG No Yes Yes
内联 MathML No Yes Yes
<video> 标记 No No Yes
<audio> 标记 No No Yes
新的语义标记,比如 <article> No No Yes
[1] 这是一个从 SGML 继承过来的隐晦的功能。由于上述的原因,它通常不被浏览器 支持。
[2] 这用于兼容根据 XHTML 规范为 <br> 之类的标记生成的服务代码。它不应该 在新代码中出现。
[3] XHTML 1.0 是考虑向后兼容,允许呈现为 text/html 的最后一个 XHTML 标准。

“严格”意味着什么?

HTML5 严格地定义了解析规则,但是同时也明确地规定了浏览器如何处理解析错误。 而不是像 XHTML 一样,只是简单的终止解析。有的人对有显而易见的语法错误的标记 任然能够得到预想的结果感到疑惑不解(例如结尾标记缺失或者属性值未用引号包裹)。

之所以能够得到预想的结果,有的是因为大多数浏览器会宽容处理错误标记,有的是因为 错误已经被指定了解决方式。以下结构在 HTML5 标准中是可选的,但一定被浏览器支持:

  • 用 <html> 标签包裹文档。
  • 把页首元素包裹在 <head> 里或把主体元素包裹在 <body> 里。
  • 闭合 <p> 、 <li> 、 <dt> 、 <dd> 、 <tr> 、 <td> 、 <th> 、 <tbody> 、 <thead> 或<tfoot> 标签。
  • 用引号包裹属性值,只要它们不含有空白字符或其特殊字符(比如 < 、 > 、 ' 或 ")。
  • 布尔属性必须赋值。

这意味着下面的页面在 HTML5 中是完全有效的:

<!doctype html>
<title>Hello HTML5</title>
<div class=header>
  <h1>Hello HTML5</h1>
  <p class=tagline>HTML5 is awesome
</div>
<ul class=nav>
  <li><a href=/index>Index</a>
  <li><a href=/downloads>Downloads</a>
  <li><a href=/about>About</a>
</ul>
<div class=body>
  <h2>HTML5 is probably the future</h2>
  <p>
    There might be some other things around but in terms of
    browser vendor support, HTML5 is hard to beat.
  <dl>
    <dt>Key 1
    <dd>Value 1
    <dt>Key 2
    <dd>Value 2
  </dl>
</div>

HTML5 中的新技术

HTML5 增加了许多新功能,使得网络应用易写易用。

  • <audio> 和 <video> 标记提供了不使用 QuickTime 或 Flash 之类的 复杂附件的嵌入音频和视频的方式。
  • 像 <article> 、 <header> 、 <nav> 和 <time> 之类的语义化 元素,使得内容易于理解。
  • <canvas> 标记支持强大的绘图 API ,减少了图形化展示数据时在服务器端生成 图像的需求。
  • 新的表单控件类型,比如 <input type="data"> 方便用户代理输入和验证数据。
  • 高级 JavaScript API ,诸如 Web Storage 、 Web Workers 、 Web Sockets 、 地理位置以及离线应用。

除了上述功能之外, HTML5 还添加了许多其它的特性。 Mark Pilgrim 即将出版的 Dive Into HTML5 一书是 HTML5 新特性的优秀指导书。目前,并不是所有的新特性 都已被浏览器支持,无论如何,请谨慎使用。

应该使用什么?

当前情况下,答案是 HTML5 。考虑到 web 浏览器最新的开发,几乎没有理由再使用 XHTML 。综上所述:

  • Internet Explorer (令人悲伤的是目前市场份额处于领先) 对 XHTML 支持不佳。
  • 许多 JavaScript 库也不支持 XHTML ,原因是它需要复杂的命名空间 API 。
  • HTML5 添加了数个新特性,包括语义标记和期待已久的 <audio> 和 <video> 标记。
  • 它背后获得了大多数浏览器供应商的支持。
  • 它易于编写,而且更简洁。

对于大多数应用,毫无疑问使用 HTML5 比 XHTML 更好。

安全注意事项

Web 应用常常会面对各种各样的安全问题,因此要把所有问题都解决是很难的。 Flask 尝试为你解决许多安全问题,但是更多的还是只能靠你自己。

跨站脚本攻击(XSS)

跨站脚本攻击是指在一个网站的环境中注入恶任意的 HTML (包括附带的 JavaScript )。要防防御这种攻击,开发者需要正确地转义文本,使其不能包含恶意的 HTML 标记。更多的相关信息请参维基百科上在文章: Cross-Site Scripting 。

在 Flask 中,除非显式指明不转义, Jinja2 会自动转义所有值。这样可以排除所有 模板导致的 XSS 问题,但是其它地方仍需小心:

  • 不使用 Jinja2 生成 HTML 。
  • 在用户提交的数据上调用了 Markup 。
  • 发送上传的 HTML ,永远不要这么做,使用 Content-Disposition: attachment 标头来避免这个问题。
  • 发送上传的文本文件。一些浏览器基于文件开头几个字节来猜测文件的 content-type ,用户可以利用这个漏洞来欺骗浏览器,通过伪装文本文件来执行 HTML 。

另一件非常重要的漏洞是不用引号包裹的属性值。虽然 Jinja2 可以通过转义 HTML 来保护你免受 XSS 问题,但是仍无法避免一种情况:属性注入的 XSS 。为了免受这种 攻击,必须确保在属性中使用 Jinja 表达式时,始终用单引号或双引号包裹:

<a href="{{ href }}">the text</a>

为什么必须这么做?因为如果不这么做,攻击者可以轻易地注入自制的 JavaScript 处理器。例如一个攻击者可以注入以下 HTML+JavaScript 代码:

onmouseover=alert(document.cookie)

当用户鼠标停放在这个链接上时,会在警告窗口里显示 cookie 信息。一个精明的 攻击者可能还会执行其它的 JavaScript 代码,而不是把 cookie 显示给用户。结合 CSS 注入,攻击者甚至可以把元素填满整个页面,这样用户把鼠标停放在页面上的 任何地方都会触发攻击。

跨站请求伪造( CSRF )

另一个大问题是 CSRF 。这个问题非常复杂,因此我不会在此详细展开,只是介绍 CSRF 是什么以及在理论上如何避免这个问题。

如果你的验证信息存储在 cookie 中,那么你就使用了隐式的状态管理。“已登入” 这个状态由一个 cookie 控制,并且这个 cookie 在页面的每个请求中都会发送。 不幸的是,在第三方站点发送的请求中也会发送这个 cookie 。如果你不注意这点, 一些人可能会通过社交引擎来欺骗应用的用户在不知情的状态下做一些蠢事。

假设你有一个特定的 URL ,当你发送 POST 请求时会删除一个用户的资料(例如http://example.com/user/delete 。如果一个攻击者现在创造一个页面并通过 页面中的 JavaScript 发送这个 post 请求,只要诱骗用户加载该页面,那么用户的 资料就会被删除。

设象在有数百万的并发用户的 Facebook 上,某人放出一些小猫图片的链接。当用户 访问那个页面欣赏毛茸茸的小猫图片时,他们的资料就被删除了。

那么如何预防这个问题呢?基本思路是:对于每个要求修改服务器内容的请求,应该 使用一次性令牌,并存储在 cookie 里, 并且 在发送表单数据的同时附上它。 在服务器再次接收数据之后,需要比较两个令牌,并确保它们相等。

为什么 Flask 没有替你做这件事?因为这应该是表单验证框架做的事,而 Flask 不 包括表单验证。

JSON 安全

ECMAScript 5 的变更

从 ECMAScript 5 开始,常量的行为改变了。现在它们不由 Array 或其它 的构造函数构造,而是由 Array 的内建构造函数构造,关闭了这个特殊的 攻击媒介。

JSON 本身是一种高级序列化格式,所以它几乎没有什么可以导致安全问题,对吗? 你不能声明导致问题的递归结构,唯一可能导致破坏的就是非常大的响应可能导致 接收端在某种意义上拒绝服务。

然而有一个陷阱。由于浏览器在 CSRF 问题上处理方式, JSON 也不能幸免。幸运 的是, JavaScript 规范中有一个怪异的部分可以轻易地解决这一问题。 Flask 在这方面做了一点工作,为你避免一些风险。不幸的是,只有在 jsonify() 中有这样的保护,所以使用其它方法生成 JSON 仍然有 风险。

那么,问题出在哪里?如何避免?问题的根源是数组是 JSON 中的一等公民。设想 有一个 JavaScript 写的用户界面,在界面中导出你所有朋友的姓名和电子邮件 地址,常见的是在 JSON 请求中发送如下数据:

[
    {"username": "admin",
     "email": "admin@localhost"}
]

当然只能你登入的时候,针对本人才可以这么做。而且,它对一个特定 URL 上的所有GET 请求都这么做。假设请求的 URL 是 http://example.com/api/get_friends.json

那么如果一个聪明的黑客把这个嵌入到他自己的网站上,并用社交引擎使得受害者访问 他的网站,会发生什么:

<script type=text/javascript>
var captured = [];
var oldArray = Array;
function Array() {
  var obj = this, id = 0, capture = function(value) {
    obj.__defineSetter__(id++, capture);
    if (value)
      captured.push(value);
  };
  capture();
}
</script>
<script type=text/javascript
  src=http://example.com/api/get_friends.json></script>
<script type=text/javascript>
Array = oldArray;
// now we have all the data in the captured array.
</script>

如果你懂得一些 JavaScript 的内部工作机制,你会知道给构造函数打补丁和为 setter 注册回调是可能的。一个攻击者可以利用这点(像上面一样上)来获取 所有你导出的 JSON 文件中的数据。如果在 script 标签中定义了内容类型是 text/javascript ,浏览器会完全忽略 application/json 的 mimetype ,而把其作为 JavaScript 来求值。因为顶层数组元素是允许的(虽然 没用)且我们在自己的构造函数中挂钩,在这个页面载入后, JSON 响应中的数据 会出现在 captured 数组中。

因为在 JavaScript 中对象文字( {...} )处于顶层是一个语法错误,攻 击者可能不只是用 script 标签加载数据并请求一个外部的 URL 。所以, Flask 所做的只是在使用jsonify() 时允许对象作为顶层元素。应当确保 使用普通的 JSON 生成函数时也这么做。

Flask 中的 Unicode

Flask 与 Jinja2 、 Werkzeug 一样,文本方面完全基于 Unicode ,大多数与 web 相关的 Python 库都是这样处理文本的。如果你还不了解 Unicode ,最好先阅读 The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets 。 本文档尝试介绍一些基本的知识,以便于能够愉快地处理与 Unicode 相关的问题。

自动转换

为了提供基本的、无痛的 Unicode 支持, Flask 做了以下假设:

  • 你网站上文本编码是 UTF-8 。
  • 你在内部对文本始终只使用 Unicode ,除非是只有 ASCII 字符的文字字符串。
  • 只要通过协议传送字节,都离不开编码和解码过程。

所以,这对你来说有什么意义?

HTTP 是基于字节的,不仅是协议,用于定位服务器文档的系统也是这样(即 URI 或 URL )。然而,通常在 HTTP 上传送的 HTML 支持很多种字符集,并且需要在 HTTP header 中注明。为了避免不必要的复杂性, Flask 假设你发送的都是 UTF-8 编码的 Unicode,Flask 会为你完成编码工作,并设置适当的 header。

如果你使用 SQLAlchemy 或类似的 ORM 系统操作数据库,道理也是同样的。一些 数据库已经使用传输 Unicode 的协议,即使没有,SQLALchemy 或其它 ORM 也会 自动处理好这个问题。

金科玉律

经验法则:如果不是处理二进制数据,一律使用 Unicode 。在 Python 2.x 中, 如何使用 Unicode ?

  • 只使用 ASCII charpoints (基本是数字、非变音或非奇特的拉丁字母)时, 可以使用常规的字符串常量( 'Hello World' )。
  • 如果你的字符串里有 ASCII 之外的东西,需要把这个字符串标记为 Unicode 字符串,方法是加上一个小写 u 作为前辍(比如 u'Hänsel und Gretel' )
  • 如果在 Python 文件中使用了非 Unicode 字符,那么需要告诉 Python 使用了 何种编码。这里,我再次建议使用 UTF-8 。你可以在 Python 源文件的第一行 或第二行写入 -*- coding: utf-8 -*- 来告知解释器你的编码类型。
  • Jinja 被配置为以 UTF-8 解码模板文件,所以请同时确保你的编辑器使用 UTF-8 编码保存文件。

自助编码和解码

如果你打交道的文件系统或环境不是真正基于 Unicode 编码的话,那么使用 Unicode 接口时需要妥善地解码。比如,当从文件系统中加载一个文件,并嵌入到 Jinja2 模板时,需要按照文件的编码来解码。这里有一个老问题就是文本文件不指定其本身 的编码。所以帮你自己一个忙,限定在文本文件中使用 UTF-8 。

无论如何,转入一个 Unicode 文件,可以使用内置的 str.decode() 方法:

def read_file(filename, charset='utf-8'):
    with open(filename, 'r') as f:
        return f.read().decode(charset)

把 Unicode 转换成指定的字符集( UTF-8 ),可以使用 unicode.encode() 方法:

def write_file(filename, contents, charset='utf-8'):
    with open(filename, 'w') as f:
        f.write(contents.encode(charset))

配置编辑器

现在的大多数编辑器默认存储为 UTF-8 ,但是如果你的编辑器不是,你必须重新配置。 下面是设置你编辑器存储为 UTF-8 的常用做法:

  • Vim: 在你的 .vimrc 文件中加入 set enc=utf-8

  • Emacs: 要么使用 encoding cookie,要么把这段文字加入到你的 .emacs 文件:

    (prefer-coding-system 'utf-8)
    (setq default-buffer-file-coding-system 'utf-8)
    
  • Notepad++:

    1. 打开 设置 -> 首选项 ...
    2. 选择“新建/缺省路径”选项卡
    3. 选择“ UTF-8 无 BOM ”作为编码

    同样也建议使用 Unix 的换行格式,可以在相同的面板中选择,但不是必须的。

Flask 扩展开发

Flask 作为一个微框架,为了让第三方库可以运作,经常需要做一些重复工作。为了 避免重复劳动,我们创建了 Flask 扩展注册表 ,把这些重复工作进行抽象,使 其可以支持不同项目。

如果你需要创建自己的扩展,那么本文可以帮助你让扩展立马上线运行。

剖析一个扩展

扩展都放在一个名如 flask_something 的包中。其中的“ something ”就是 扩展所要连接的库的名称。例如假设你要为 Flask 添加 simplexml 库的支持, 那么扩展的包名称就应该是 flask_simplexml 。

但是,真正扩展的名称(可读名称)应当形如“ Flask-SimpleXML ”。请确保名称 中包含“ Flask ”,并且注意大小写。这样用户就可以在他们的 setup.py 文件 中注册依赖。

Flask 设置了一个名为 flask.ext 重定向包,用户应当使用这个包来导入 扩展。假设你的扩展包名为 flask_something ,那么用户应当导入为 flask.ext.something 。这样做是为了从老的命名空间过渡,详见 扩展导入的迁移 。

但是扩展具体是怎么样的呢?一个扩展必须保证可以同时在多个 Flask 应用中工作。 这是必要条件,因为许多人为了进行单元测试,会使用类似 应用工厂 模式来创建应用并且需要支持多套配置。因此,你的应用支持这种行为非常重要。

最重要的是,扩展必须与一个 setup.py 文件一起分发,并且在 PyPI 上注册。 同时,用于开发的 checkout 链接也应该能工作,以便于在 virtualenv 中安装开发 版本,而不是手动下载库。

Flask 扩展必须使用 BSD 或 MIT 或更自由的许可证来许可,这样才能被添加进 Flask 扩展注册表。请记住, Flask 扩展注册表是比较稳健的,并且扩展在发布前 会进行预审是否符合要求。

“ Hello Flaskext! ”

好吧,让我们开展创建一个 Flask 扩展。这个扩展的用途是提供最基本的 SQLite3 支持。

首先创建如下结构的文件夹和文件:

flask-sqlite3/
    flask_sqlite3.py
    LICENSE
    README

以下是最重要的文件及其内容:

setup.py

接下来 setup.py 是必需的,该文件用于安装你的 Flask 扩展。文件内容如下:

"""
Flask-SQLite3
-------------

This is the description for that library
"""
from setuptools import setup


setup(
    name='Flask-SQLite3',
    version='1.0',
    url='http://example.com/flask-sqlite3/',
    license='BSD',
    author='Your Name',
    author_email='your-email@example.com',
    description='Very short description',
    long_description=__doc__,
    py_modules=['flask_sqlite3'],
    # if you would be using a package instead use packages instead
    # of py_modules:
    # packages=['flask_sqlite3'],
    zip_safe=False,
    include_package_data=True,
    platforms='any',
    install_requires=[
        'Flask'
    ],
    classifiers=[
        'Environment :: Web Environment',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: BSD License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
        'Topic :: Software Development :: Libraries :: Python Modules'
    ]
)

代码相当多,但是你可以从现有的扩展中直接复制/粘贴,并修改相应的内容。

flask_sqlite3.py

这个文件是你的扩展的具体实现。但是一个扩展到底是怎么样的?最佳实践是什么? 继续阅读吧。

初始化扩展

许多扩展会需要某种类型的初始化步骤。例如,假设一个应用像文档中建议的一样 ( 在 Flask 中使用 SQLite 3 )正在连接到 SQLite 。那么,扩展如何获知应用对象的名称?

相当简单:你把名称传递给扩展。

推荐两种初始化扩展的方式:

初始化函数:

如果你的扩展名为 helloworld ,那么你可能有一个名为 init_helloworld(app[,extra_args]) 的函数。该函数用来为应用初始化 扩展,它可以在处理器之前或之后。

初始化类:

初始化类与初始化函数的工作方式大致相同,区别是类在以后可以进一步改动。 例如,查看一下 OAuth 扩展 的工作方式:有一个 OAuth 对象提供一些 辅助函数(比如 OAuth.remote_app )来创建使用 OAuth 的远程应用的引用。

使用哪种方式取决于你。对于 SQLite 3 扩展,我们会使用基于类的方式,因为这样 可以提供给用户一个用于打开和关闭数据库连接的对象。

当使用类初始化方式时,重要的一点是鼓励在模块层内共享。这种情况下,对象本身 在任何情况下不得存储任何应用的特定状态,而必须可以在不同的应用之间共享。

扩展的代码

以下是 flask_sqlite3.py 的内容,可以复制/粘贴:

import sqlite3
from flask import current_app

# Find the stack on which we want to store the database connection.
# Starting with Flask 0.9, the _app_ctx_stack is the correct one,
# before that we need to use the _request_ctx_stack.
try:
    from flask import _app_ctx_stack as stack
except ImportError:
    from flask import _request_ctx_stack as stack


class SQLite3(object):

    def __init__(self, app=None):
        self.app = app
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.config.setdefault('SQLITE3_DATABASE', ':memory:')
        # Use the newstyle teardown_appcontext if it's available,
        # otherwise fall back to the request context
        if hasattr(app, 'teardown_appcontext'):
            app.teardown_appcontext(self.teardown)
        else:
            app.teardown_request(self.teardown)

    def connect(self):
        return sqlite3.connect(current_app.config['SQLITE3_DATABASE'])

    def teardown(self, exception):
        ctx = stack.top
        if hasattr(ctx, 'sqlite3_db'):
            ctx.sqlite3_db.close()

    @property
    def connection(self):
        ctx = stack.top
        if ctx is not None:
            if not hasattr(ctx, 'sqlite3_db'):
                ctx.sqlite3_db = self.connect()
            return ctx.sqlite3_db

那么这是这些代码的含义是什么:

  1. __init__ 方法接收应用对象,该对象是可选的。如果提供了该对象,那么 就调用init_app 。

  2. init_app 方法使得 SQLite3 对象不需要应用对象就可以实例化。这个 方法支持工厂模式来创建应用。 init_app 会配置数据库。如果不提供 配置,默认配置为内存数据库。此外, init_app 方法附加了 teardown 处理器。它会试图使用新样式的应用环境处理器,并且如果它不存在,退回到 请求环境处理器。

  3. 接下来,我们定义了 connect 方法来打开一个数据库连接。

  4. 最后,我们添加一个 connection 属性,首次访问时打开数据库连接,并把 它存储在环境中。这也是处理资源的推荐方式:在资源第一次使用时获取资源, 即惰性获取。

    注意这里,我们把数据库连接通过 _app_ctx_stack.top 附加到应用环境的 栈顶。扩展应该使用上下文的栈顶来存储它们自己的信息,并使用足够复杂的 名称。注意如果应用使用的是不支持它的老版本的 Flask 我们退回到 _request_ctx_stack.top

那么为什么我们决定在此使用基于类的方法?因为我们的扩展是这样使用的:

from flask import Flask
from flask_sqlite3 import SQLite3

app = Flask(__name__)
app.config.from_pyfile('the-config.cfg')
db = SQLite3(app)

你可以在视图中这样使用数据库:

@app.route('/')
def show_all():
    cur = db.connection.cursor()
    cur.execute(...)

同样地,如果在请求之外,并且使用支持应用环境的 Flask 0.9 或之后的版本, 可以用同样的方式使用数据库:

with app.app_context():
    cur = db.connection.cursor()
    cur.execute(...)

在 with 块的末尾,销毁处理器会自动执行。

另外, init_app 方法用于在创建应用时支持工厂模式:

db = Sqlite3()
# Then later on.
app = create_app('the-config.cfg')
db.init_app(app)

记住已审核的 Flask 扩展必须支持用工厂模式来创建应用(下面会解释)。

init_app 的注意事项

如你所见, init_app 不分配 app 到 self 。这是故意的!基于 类的 Flask 扩展必须只在应用传递到构造函数时才在对象上存储应用。这告诉 扩展:我对使用多个应用没有兴趣。

当扩展需要找到当前应用,且没有一个指向当前应用的引用时,必须使用 current_app环境局部变量或用一种你可以显式传递应用的方法 更改 API 。

使用 _app_ctx_stack

在上面的例子中,在每个请求之前,一个 sqlite3_db 变量被分配到_app_ctx_stack.top 。在一个视图函数中,这个变量可以使用 SQLite3 的属性connection 来访问。在请求解散时, sqlite3_db 连接被关闭。 通过使用这个模式,在请求持续的期间,可以访问 相同 的 sqlite3 数据库连接。

如果 _app_ctx_stack 因为用户使用了老版本的 Flask 不存在, 建议退化到绑定在请求中的 _request_ctx_stack 。

解散形为

本段只有在想要支持 Flask 0.6 版本和更老版本时有用

因为在 Flask 0.7 版本中修改了在请求的最后运行的相关函数,所以如果你的扩展 需要继续支持 Flask 的老版本,那么必须格外小心。下面的模式是一个新旧兼顾的 好方法:

def close_connection(response):
    ctx = _request_ctx_stack.top
    ctx.sqlite3_db.close()
    return response

if hasattr(app, 'teardown_request'):
    app.teardown_request(close_connection)
else:
    app.after_request(close_connection)

严格地讲,上面的代码是错误的,因为解散函数接受异常且典型地不返回任何东西。 然而,因为返回值被丢弃,假设中间的代码不触碰传递的参数,这刚好有用。

学习借鉴

本文只是谈了一些扩展开发的皮毛。如果想要深入,那么查看 Flask 扩展注册表 上已有的扩展是明智的。如果你感到迷失,还可以通过 邮件列表 和 IRC 频道 学习到优秀的 APIs 。尤其当你要开发一个全新的扩展时,建议先多看 多问多听,这样不仅可以知道别人的需求,同时也避免多人重复开发。

谨记:设计优秀的 API 是艰难的。因此请先在邮件列表里介绍你的项目,让其他 开发者在 API 设计上助你一臂之力。

最好的 Flask 扩展是那些共享 API 智慧的扩展,因此越早共享越有效。

已审核的扩展

Flask 有已审核的扩展的概念。已审核的扩展会被视作 Flask 的一部分来测试,以 保证扩展在新版本发布时不会出问题。这些已审核的扩展会在 Flask 扩展注册表 中列出,并有相应的标记。如果你想要自己的扩展通过审核,请遵循以下的指导方针:

  1. 一个已审核的 Flask 扩展需要一个维护者。如果一个扩展作者想要放弃项目, 那么项目应该寻找一个新的维护者,包括移交完整的源码托管和 PyPI 访问。 如果找不到新的维护者,请赋予 Flask 核心团队访问权限。
  2. 一个已审核的 Flask 扩展必须提供一个名如 flask_extensionname 的包或 模块。它们也可以存在于一个 flaskext 命名空间包内部,但是现在不推荐 这么做。
  3. 它必须带有测试套件,套件可以使用 make test 或者 python setup.py test 来调用。如果是使用 make test 调用的测试 套件,那么必须保证所有的依赖可以自动安装。如果是使用 python setup.py test 调用的测试套件,那么测试的依赖可以在setup.py 文件中定义。 测试套件分发的必要组成部分。
  4. 已审核的扩展的 API 可以通过下面特性的检查:
    • 一个已审核的扩展必须支持在同一个 Python 进程中运行的多个应用。
    • 它必须支持使用工厂模式创建应用
  5. 许可协议必须是 BSD/MIT/WTFPL 协议。
  6. 官方扩展的命名模式是 Flask-ExtensionName 或 ExtensionName-Flask 。
  7. 已审核的扩展必须在 setup.py 文件里定义所有的依赖关系,除非在 PyPI 上不可用。
  8. 扩展的文档必须使用两种 Flask 的 Sphinx 文档主题中的一个。
  9. setup.py 描述(即 PyPI 描述)必须链接到文档、网站(如果有的话), 并且必须有自动安装开发版本的链接( PackageName==dev )。
  10. 安装脚本中的 zip_safe 标志必须被设置为 False ,即使扩展对于 压缩是安全的。
  11. 现行扩展必须支持 Python 2.6 和 Python 2.7 。

扩展导入的迁移

一段时间,我们曾推荐对 Flask 扩展使用命名空间包。但这在实践中被证明是有 问题的,因为许多不同命名空间包系统存在竞争,并且 pip 会自动在不同的系统 中切换,这给用户带来了许多问题。

现在,我们推荐命名包为 flask_foo 替代过时的 flaskext.foo 。Flask 0.8 引入了重定向导入系统,允许从 flask.ext.foo 导入,并且如果 flaskext.foo 失败时,会首先尝试 flask_foo 。

Flask 扩展应该鼓励用户从 flask.ext.foo 导入,而不是 flask_foo 或 flaskext_foo,这样扩展可以迁移到新的包名称而不烦扰用户。

Pocoo 风格指南

所有 Pocoo 项目都遵循 Pocoo 风格指南, Flask 项目也不例外。 Flask 补丁必须 遵循这个指南,同时也推荐 Flask 扩展遵循这个指南。

一般而言, Pocoo 风格指南遵循 PEP 8 ,有一些小差异和扩充。

总体布局

缩进:
4个空格。不使用制表符,没有例外。
最大行长:
软限制为 79 个字符,不超过 84 个字符。尝试合理放置 break 、 continue 和 return声明来避免代码过度嵌套。
续行:

可以使用反斜杠来续行,续行应对齐最后一个点号或等于号,或者缩进四个空格:

this_is_a_very_long(function_call, 'with many parameters') \
    .that_returns_an_object_with_an_attribute

MyModel.query.filter(MyModel.scalar > 120) \
             .order_by(MyModel.name.desc()) \
             .limit(10)

如果你在括号内的换行,那么续行应对齐括号:

this_is_a_very_long(function_call, 'with many parameters',
                    23, 42, 'and even more')

对于有许多元素的元组或列表,在起始括号后立即换行:

items = [
    'this is the first', 'set of items', 'with more items',
    'to come in this line', 'like this'
]
空行:

顶层函数和类由两个空行分隔,其它一个空行。不要使用过多空行来分隔代码 逻辑段。例如:

def hello(name):
    print 'Hello %s!' % name


def goodbye(name):
    print 'See you %s.' % name


class MyClass(object):
    """This is a simple docstring"""

    def __init__(self, name):
        self.name = name

    def get_annoying_name(self):
        return self.name.upper() + '!!!!111'

表达式和语句

常规空格规则:
  • 不是单词的一元运算符不使用空格(例如: - 、 ~ 等等),在圆括号 也是这样。
  • 用空格包围二元运算符。

对:

exp = -1.05
value = (item_value / item_count) * offset / exp
value = my_list[index]
value = my_dict['key']

错:

exp = - 1.05
value = ( item_value / item_count ) * offset / exp
value = (item_value/item_count)*offset/exp
value=( item_value/item_count ) * offset/exp
value = my_list[ index ]
value = my_dict ['key']
禁止 Yoda 语句:

永远不要用变量来比较常量,而是用常量来比较变量:

对:

if method == 'md5':
    pass

错:

if 'md5' == method:
    pass
比较:
  • 针对任意类型使用 == 和 !=
  • 针对单一类型使用 is 和 is not (例如: foo is not None )
  • 永远不要与 True 或 False 作比较(例如永远不要写 foo == False , 而应当写not foo )
排除检验:
使用 foo not in bar 而不是 not foo in bar
实例检验:
使用 isinstance(a, C) 而不是 type(A) is C ,但是通常应当避免检验 实例,而应当检验特性。

命名约定

  • 类名: CamelCase ,缩写词大写( HTTPWriter 而不是 HttpWriter )
  • 变量名: lowercase_with_underscores
  • 方法和函数名: lowercase_with_underscores
  • 常量: UPPERCASE_WITH_UNDERSCORES
  • 预编译正则表达式: name_re

被保护的成员以单个下划线作为前缀,混合类则使用双下划线。

如果使用关键字作为类的名称,那么在名称末尾添加下划线。与内置构件冲突是允许 的,请 一定不要 用在变量名后添加下划线的方式解决冲突。如果函数需要访问 一个隐蔽的内置构件,请重新绑定内置构件到一个不同的名字。

函数和方法参数:
  • 类方法: cls 作为第一个参数
  • 实例方法: self 作为第一个参数
  • 用于属性的 lambda 表达式应该把第一个参数替换为 x , 像 display_name =property(lambda x: x.real_name or x.username) 中一样

文档字符串

文档字符串约定:

所有的文档字符串为 Sphinx 可理解的 reStructuredText 格式。它们的形态 因行数不同而不同。如果只有一行,三引号闭合在同一行,否则开头的三引号 与文本在同一行,结尾的三引号独立一行:

def foo():
    """This is a simple docstring"""


def bar():
    """This is a longer docstring with so much information in there
    that it spans three lines.  In this case the closing triple quote
    is on its own line.
    """
模块头:

模块头包含一个 utf-8 编码声明(即使没有使用非 ASCII 字符,也始终推 荐这么做)和一个标准的文档字符串:

# -*- coding: utf-8 -*-
"""
    package.module
    ~~~~~~~~~~~~~~

    A brief description goes here.

    :copyright: (c) YEAR by AUTHOR.
    :license: LICENSE_NAME, see LICENSE_FILE for more details.
"""

谨记使用合适的版权和许可证文件以利于通过 Flask 扩展审核。

注释

注释的规则与文档字符串类似。两者都使用 reStructuredText 格式。如果一个 注释被用于一个说明类属性,在起始的井号( # )后加一个冒号:

class User(object):
    #: the name of the user as unicode string
    name = Column(String)
    #: the sha1 hash of the password + inline salt
    pw_hash = Column(String)

Python 3 支持

Flask 及其所有依赖包都支持 Python 3 ,因此理论上来说你能够在 Python 3 使用。然而在 Python 3 环境下开始你的下一个项目之前你需要留心一些事情。

要求

如果你要在 Python 3 环境下使用 Flask ,那么必须使用 Python 3.3 或者更高 版本。3.2 以及更早的版本是  支持的。

除此之外你还必须使用最新的 itsdangerousJinja2 and Werkzeug 版本。

API 稳定性

Python 3 的 unicode 和字节使用方式使编写底层代码变得困难。主要影响了 WSGI 中间件以及与 WSGI 提供的信息进行交互。 Werkzeug 已经用高级帮助器封装了所有 信息,但是其中一些是专门为支持 Python 3 添加的,还比较新。

许多关于 WSGI 细节的文档编写于 WSGI 支持 Python 3 之前。尽管 Werkzeug 和 Flask 在 Python 2.x 版本的 API 在支持 Python 3 时基本不用改动,但是不作 保证。

用户少

根据 PyPI 下载统计, Python 3 用户不到 Python 2 用户的 1% 。因此,如果你 遇到 Python3 的特定问题,那么恐怕很难在互联网上搜索到答案。

小生态系统

大多数 Flask 扩、所有的文档和绝大多数 PyPI 提供的库都还不支持 Python 3 。 即使你现在清楚地知道 Python 3 支持你的项目以及项目所需要的一切,但是谁能 预料半年以后的事呢?如果你富于冒险精神,那么可以自己移植库,但最好乞求一颗 勇敢的心。

建议

除非你已经很熟悉版本的差异,否则在生态系统成熟之前,建议仍然使用当前版本的 Python 。

升级之痛苦大部分针对低级别的库,例如 Flask 和 Werkzeug ,而不是较高级别的 应用程序。例如, Flask 仓库中的所有示例都同时支持 Python 2 和 Python 3 , 不需要改动一行代码。

Upgrading to Newer Releases

Flask itself is changing like any software is changing over time. Most of the changes are the nice kind, the kind where you don’t have to change anything in your code to profit from a new release.

However every once in a while there are changes that do require some changes in your code or there are changes that make it possible for you to improve your own code quality by taking advantage of new features in Flask.

This section of the documentation enumerates all the changes in Flask from release to release and how you can change your code to have a painless updating experience.

If you want to use the easy_install command to upgrade your Flask installation, make sure to pass it the -U parameter:

$ easy_install -U Flask

Version 0.10

The biggest change going from 0.9 to 0.10 is that the cookie serialization format changed from pickle to a specialized JSON format. This change has been done in order to avoid the damage an attacker can do if the secret key is leaked. When you upgrade you will notice two major changes: all sessions that were issued before the upgrade are invalidated and you can only store a limited amount of types in the session. The new sessions are by design much more restricted to only allow JSON with a few small extensions for tuples and strings with HTML markup.

In order to not break people’s sessions it is possible to continue using the old session system by using the Flask-OldSessions extension.

Flask also started storing the flask.g object on the application context instead of the request context. This change should be transparent for you but it means that you now can store things on the g object when there is no request context yet but an application context. The old flask.Flask.request_globals_class attribute was renamed toflask.Flask.app_ctx_globals_class.

Version 0.9

The behavior of returning tuples from a function was simplified. If you return a tuple it no longer defines the arguments for the response object you’re creating, it’s now always a tuple in the form (response, status, headers) where at least one item has to be provided. If you depend on the old behavior, you can add it easily by subclassing Flask:

class TraditionalFlask(Flask):
    def make_response(self, rv):
        if isinstance(rv, tuple):
            return self.response_class(*rv)
        return Flask.make_response(self, rv)

If you maintain an extension that was using _request_ctx_stack before, please consider changing to _app_ctx_stack if it makes sense for your extension. For instance, the app context stack makes sense for extensions which connect to databases. Using the app context stack instead of the request stack will make extensions more readily handle use cases outside of requests.

Version 0.8

Flask introduced a new session interface system. We also noticed that there was a naming collision between flask.session the module that implements sessions andflask.session which is the global session object. With that introduction we moved the implementation details for the session system into a new module calledflask.sessions. If you used the previously undocumented session support we urge you to upgrade.

If invalid JSON data was submitted Flask will now raise a BadRequest exception instead of letting the default ValueError bubble up. This has the advantage that you no longer have to handle that error to avoid an internal server error showing up for the user. If you were catching this down explicitly in the past as ValueError you will need to change this.

Due to a bug in the test client Flask 0.7 did not trigger teardown handlers when the test client was used in a with statement. This was since fixed but might require some changes in your testsuites if you relied on this behavior.

Version 0.7

In Flask 0.7 we cleaned up the code base internally a lot and did some backwards incompatible changes that make it easier to implement larger applications with Flask. Because we want to make upgrading as easy as possible we tried to counter the problems arising from these changes by providing a script that can ease the transition.

The script scans your whole application and generates an unified diff with changes it assumes are safe to apply. However as this is an automated tool it won’t be able to find all use cases and it might miss some. We internally spread a lot of deprecation warnings all over the place to make it easy to find pieces of code that it was unable to upgrade.

We strongly recommend that you hand review the generated patchfile and only apply the chunks that look good.

If you are using git as version control system for your project we recommend applying the patch with path -p1 patchfile.diff and then using the interactive commit feature to only apply the chunks that look good.

To apply the upgrade script do the following:

  1. Download the script: flask-07-upgrade.py

  2. Run it in the directory of your application:

    python flask-07-upgrade.py > patchfile.diff
    
  3. Review the generated patchfile.

  4. Apply the patch:

    patch -p1 < patchfile.diff
    
  5. If you were using per-module template folders you need to move some templates around. Previously if you had a folder named templates next to a blueprint namedadmin the implicit template path automatically was admin/index.html for a template file called templates/index.html. This no longer is the case. Now you need to name the template templates/admin/index.html. The tool will not detect this so you will have to do that on your own.

Please note that deprecation warnings are disabled by default starting with Python 2.7. In order to see the deprecation warnings that might be emitted you have to enabled them with the warnings module.

If you are working with windows and you lack the patch command line utility you can get it as part of various Unix runtime environments for windows including cygwin, msysgit or ming32. Also source control systems like svn, hg or git have builtin support for applying unified diffs as generated by the tool. Check the manual of your version control system for more information.

Bug in Request Locals

Due to a bug in earlier implementations the request local proxies now raise aRuntimeError instead of an AttributeError when they are unbound. If you caught these exceptions with AttributeError before, you should catch them withRuntimeError now.

Additionally the send_file() function is now issuing deprecation warnings if you depend on functionality that will be removed in Flask 1.0. Previously it was possible to use etags and mimetypes when file objects were passed. This was unreliable and caused issues for a few setups. If you get a deprecation warning, make sure to update your application to work with either filenames there or disable etag attaching and attach them yourself.

Old code:

return send_file(my_file_object)
return send_file(my_file_object)

New code:

return send_file(my_file_object, add_etags=False)
Upgrading to new Teardown Handling

We streamlined the behavior of the callbacks for request handling. For things that modify the response the after_request() decorators continue to work as expected, but for things that absolutely must happen at the end of request we introduced the newteardown_request() decorator. Unfortunately that change also made after-request work differently under error conditions. It’s not consistently skipped if exceptions happen whereas previously it might have been called twice to ensure it is executed at the end of the request.

If you have database connection code that looks like this:

@app.after_request
def after_request(response):
    g.db.close()
    return response

You are now encouraged to use this instead:

@app.teardown_request
def after_request(exception):
    if hasattr(g, 'db'):
        g.db.close()

On the upside this change greatly improves the internal code flow and makes it easier to customize the dispatching and error handling. This makes it now a lot easier to write unit tests as you can prevent closing down of database connections for a while. You can take advantage of the fact that the teardown callbacks are called when the response context is removed from the stack so a test can query the database after request handling:

with app.test_client() as client:
    resp = client.get('/')
    # g.db is still bound if there is such a thing

# and here it's gone
Manual Error Handler Attaching

While it is still possible to attach error handlers to Flask.error_handlers it’s discouraged to do so and in fact deprecated. In general we no longer recommend custom error handler attaching via assignments to the underlying dictionary due to the more complex internal handling to support arbitrary exception classes and blueprints. See Flask.errorhandler() for more information.

The proper upgrade is to change this:

app.error_handlers[403] = handle_error

Into this:

app.register_error_handler(403, handle_error)

Alternatively you should just attach the function with a decorator:

@app.errorhandler(403)
def handle_error(e):
    ...

(Note that register_error_handler() is new in Flask 0.7)

Blueprint Support

Blueprints replace the previous concept of “Modules” in Flask. They provide better semantics for various features and work better with large applications. The update script provided should be able to upgrade your applications automatically, but there might be some cases where it fails to upgrade. What changed?

  • Blueprints need explicit names. Modules had an automatic name guesssing scheme where the shortname for the module was taken from the last part of the import module. The upgrade script tries to guess that name but it might fail as this information could change at runtime.
  • Blueprints have an inverse behavior for url_for(). Previously .foo toldurl_for() that it should look for the endpoint foo on the application. Now it means “relative to current module”. The script will inverse all calls to url_for()automatically for you. It will do this in a very eager way so you might end up with some unnecessary leading dots in your code if you’re not using modules.
  • Blueprints do not automatically provide static folders. They will also no longer automatically export templates from a folder called templates next to their location however but it can be enabled from the constructor. Same with static files: if you want to continue serving static files you need to tell the constructor explicitly the path to the static folder (which can be relative to the blueprint’s module path).
  • Rendering templates was simplified. Now the blueprints can provide template folders which are added to a general template searchpath. This means that you need to add another subfolder with the blueprint’s name into that folder if you want blueprintname/template.html as the template name.

If you continue to use the Module object which is deprecated, Flask will restore the previous behavior as good as possible. However we strongly recommend upgrading to the new blueprints as they provide a lot of useful improvement such as the ability to attach a blueprint multiple times, blueprint specific error handlers and a lot more.

Version 0.6

Flask 0.6 comes with a backwards incompatible change which affects the order of after-request handlers. Previously they were called in the order of the registration, now they are called in reverse order. This change was made so that Flask behaves more like people expected it to work and how other systems handle request pre- and postprocessing. If you depend on the order of execution of post-request functions, be sure to change the order.

Another change that breaks backwards compatibility is that context processors will no longer override values passed directly to the template rendering function. If for example request is as variable passed directly to the template, the default context processor will not override it with the current request object. This makes it easier to extend context processors later to inject additional variables without breaking existing template not expecting them.

Version 0.5

Flask 0.5 is the first release that comes as a Python package instead of a single module. There were a couple of internal refactoring so if you depend on undocumented internal details you probably have to adapt the imports.

The following changes may be relevant to your application:

  • autoescaping no longer happens for all templates. Instead it is configured to only happen on files ending with .html.htm.xml and .xhtml. If you have templates with different extensions you should override the select_jinja_autoescape()method.
  • Flask no longer supports zipped applications in this release. This functionality might come back in future releases if there is demand for this feature. Removing support for this makes the Flask internal code easier to understand and fixes a couple of small issues that make debugging harder than necessary.
  • The create_jinja_loader function is gone. If you want to customize the Jinja loader now, use the create_jinja_environment() method instead.

Version 0.4

For application developers there are no changes that require changes in your code. In case you are developing on a Flask extension however, and that extension has a unittest-mode you might want to link the activation of that mode to the new TESTING flag.

Version 0.3

Flask 0.3 introduces configuration support and logging as well as categories for flashing messages. All these are features that are 100% backwards compatible but you might want to take advantage of them.

Configuration Support

The configuration support makes it easier to write any kind of application that requires some sort of configuration. (Which most likely is the case for any application out there).

If you previously had code like this:

app.debug = DEBUG
app.secret_key = SECRET_KEY

You no longer have to do that, instead you can just load a configuration into the config object. How this works is outlined in 配置管理.

Logging Integration

Flask now configures a logger for you with some basic and useful defaults. If you run your application in production and want to profit from automatic error logging, you might be interested in attaching a proper log handler. Also you can start logging warnings and errors into the logger when appropriately. For more information on that, read 掌握应用错误.

Categories for Flash Messages

Flash messages can now have categories attached. This makes it possible to render errors, warnings or regular messages differently for example. This is an opt-in feature because it requires some rethinking in the code.

Read all about that in the 消息闪现 pattern.

Flask Changelog

Here you can see the full list of changes between each Flask release.

Version 0.11.1

Bugfix release, released on June 7th 2016.

  • Fixed a bug that prevented FLASK_APP=foobar/__init__.py from working. See pull request #1872.

Version 0.11

Released on May 29th 2016, codename Absinthe.

  • Added support to serializing top-level arrays to flask.jsonify(). This introduces a security risk in ancient browsers. See JSON 安全 for details.
  • Added before_render_template signal.
  • Added **kwargs to flask.Test.test_client() to support passing additional keyword arguments to the constructor of flask.Flask.test_client_class.
  • Added SESSION_REFRESH_EACH_REQUEST config key that controls the set-cookie behavior. If set to True a permanent session will be refreshed each request and get their lifetime extended, if set to False it will only be modified if the session actually modifies. Non permanent sessions are not affected by this and will always expire if the browser window closes.
  • Made Flask support custom JSON mimetypes for incoming data.
  • Added support for returning tuples in the form (response, headers) from a view function.
  • Added flask.Config.from_json().
  • Added flask.Flask.config_class.
  • Added flask.config.Config.get_namespace().
  • Templates are no longer automatically reloaded outside of debug mode. This can be configured with the new TEMPLATES_AUTO_RELOAD config key.
  • Added a workaround for a limitation in Python 3.3’s namespace loader.
  • Added support for explicit root paths when using Python 3.3’s namespace packages.
  • Added flask and the flask.cli module to start the local debug server through the click CLI system. This is recommended over the old flask.run() method as it works faster and more reliable due to a different design and also replaces Flask-Script.
  • Error handlers that match specific classes are now checked first, thereby allowing catching exceptions that are subclasses of HTTP exceptions (inwerkzeug.exceptions). This makes it possible for an extension author to create exceptions that will by default result in the HTTP error of their choosing, but may be caught with a custom error handler if desired.
  • Added flask.Config.from_mapping().
  • Flask will now log by default even if debug is disabled. The log format is now hardcoded but the default log handling can be disabled through theLOGGER_HANDLER_POLICY configuration key.
  • Removed deprecated module functionality.
  • Added the EXPLAIN_TEMPLATE_LOADING config flag which when enabled will instruct Flask to explain how it locates templates. This should help users debug when the wrong templates are loaded.
  • Enforce blueprint handling in the order they were registered for template loading.
  • Ported test suite to py.test.
  • Deprecated request.json in favour of request.get_json().
  • Add “pretty” and “compressed” separators definitions in jsonify() method. Reduces JSON response size when JSONIFY_PRETTYPRINT_REGULAR=False by removing unnecessary white space included by default after separators.
  • JSON responses are now terminated with a newline character, because it is a convention that UNIX text files end with a newline and some clients don’t deal well when this newline is missing. See https://github.com/pallets/flask/pull/1262 – this came up originally as a part ofhttps://github.com/kennethreitz/httpbin/issues/168
  • The automatically provided OPTIONS method is now correctly disabled if the user registered an overriding rule with the lowercase-version options (issue #1288).
  • flask.json.jsonify now supports the datetime.date type (pull request #1326).
  • Don’t leak exception info of already catched exceptions to context teardown handlers (pull request #1393).
  • Allow custom Jinja environment subclasses (pull request #1422).
  • flask.g now has pop() and setdefault methods.
  • Turn on autoescape for flask.templating.render_template_string by default (pull request #1515).
  • flask.ext is now deprecated (pull request #1484).
  • send_from_directory now raises BadRequest if the filename is invalid on the server OS (pull request #1763).
  • Added the JSONIFY_MIMETYPE configuration variable (pull request #1728).
  • Exceptions during teardown handling will no longer leave bad application contexts lingering around.

Version 0.10.2

(bugfix release, release date to be announced)

  • Fixed broken test_appcontext_signals() test case.
  • Raise an AttributeError in flask.helpers.find_package() with a useful message explaining why it is raised when a PEP 302 import hook is used without anis_package() method.
  • Fixed an issue causing exceptions raised before entering a request or app context to be passed to teardown handlers.
  • Fixed an issue with query parameters getting removed from requests in the test client when absolute URLs were requested.
  • Made @before_first_request into a decorator as intended.
  • Fixed an etags bug when sending a file streams with a name.
  • Fixed send_from_directory not expanding to the application root path correctly.
  • Changed logic of before first request handlers to flip the flag after invoking. This will allow some uses that are potentially dangerous but should probably be permitted.
  • Fixed Python 3 bug when a handler from app.url_build_error_handlers reraises the BuildError.

Version 0.10.1

(bugfix release, released on June 14th 2013)

  • Fixed an issue where |tojson was not quoting single quotes which made the filter not work properly in HTML attributes. Now it’s possible to use that filter in single quoted attributes. This should make using that filter with angular.js easier.
  • Added support for byte strings back to the session system. This broke compatibility with the common case of people putting binary data for token verification into the session.
  • Fixed an issue where registering the same method twice for the same endpoint would trigger an exception incorrectly.

Version 0.10

Released on June 13th 2013, codename Limoncello.

  • Changed default cookie serialization format from pickle to JSON to limit the impact an attacker can do if the secret key leaks. See Version 0.10 for more information.
  • Added template_test methods in addition to the already existingtemplate_filter method family.
  • Added template_global methods in addition to the already existingtemplate_filter method family.
  • Set the content-length header for x-sendfile.
  • tojson filter now does not escape script blocks in HTML5 parsers.
  • tojson used in templates is now safe by default due. This was allowed due to the different escaping behavior.
  • Flask will now raise an error if you attempt to register a new function on an already used endpoint.
  • Added wrapper module around simplejson and added default serialization of datetime objects. This allows much easier customization of how JSON is handled by Flask or any Flask extension.
  • Removed deprecated internal flask.session module alias. Use flask.sessionsinstead to get the session module. This is not to be confused with flask.sessionthe session proxy.
  • Templates can now be rendered without request context. The behavior is slightly different as the requestsession and g objects will not be available and blueprint’s context processors are not called.
  • The config object is now available to the template as a real global and not through a context processor which makes it available even in imported templates by default.
  • Added an option to generate non-ascii encoded JSON which should result in less bytes being transmitted over the network. It’s disabled by default to not cause confusion with existing libraries that might expect flask.json.dumps to return bytestrings by default.
  • flask.g is now stored on the app context instead of the request context.
  • flask.g now gained a get() method for not erroring out on non existing items.
  • flask.g now can be used with the in operator to see what’s defined and it now is iterable and will yield all attributes stored.
  • flask.Flask.request_globals_class got renamed toflask.Flask.app_ctx_globals_class which is a better name to what it does since 0.10.
  • requestsession and g are now also added as proxies to the template context which makes them available in imported templates. One has to be very careful with those though because usage outside of macros might cause caching.
  • Flask will no longer invoke the wrong error handlers if a proxy exception is passed through.
  • Added a workaround for chrome’s cookies in localhost not working as intended with domain names.
  • Changed logic for picking defaults for cookie values from sessions to work better with Google Chrome.
  • Added message_flashed signal that simplifies flashing testing.
  • Added support for copying of request contexts for better working with greenlets.
  • Removed custom JSON HTTP exception subclasses. If you were relying on them you can reintroduce them again yourself trivially. Using them however is strongly discouraged as the interface was flawed.
  • Python requirements changed: requiring Python 2.6 or 2.7 now to prepare for Python 3.3 port.
  • Changed how the teardown system is informed about exceptions. This is now more reliable in case something handles an exception halfway through the error handling process.
  • Request context preservation in debug mode now keeps the exception information around which means that teardown handlers are able to distinguish error from success cases.
  • Added the JSONIFY_PRETTYPRINT_REGULAR configuration variable.
  • Flask now orders JSON keys by default to not trash HTTP caches due to different hash seeds between different workers.
  • Added appcontext_pushed and appcontext_popped signals.
  • The builtin run method now takes the SERVER_NAME into account when picking the default port to run on.
  • Added flask.request.get_json() as a replacement for the old flask.request.jsonproperty.

Version 0.9

Released on July 1st 2012, codename Campari.

  • The flask.Request.on_json_loading_failed() now returns a JSON formatted response by default.
  • The flask.url_for() function now can generate anchors to the generated links.
  • The flask.url_for() function now can also explicitly generate URL rules specific to a given HTTP method.
  • Logger now only returns the debug log setting if it was not set explicitly.
  • Unregister a circular dependency between the WSGI environment and the request object when shutting down the request. This means that environwerkzeug.request will be None after the response was returned to the WSGI server but has the advantage that the garbage collector is not needed on CPython to tear down the request unless the user created circular dependencies themselves.
  • Session is now stored after callbacks so that if the session payload is stored in the session you can still modify it in an after request callback.
  • The flask.Flask class will avoid importing the provided import name if it can (the required first parameter), to benefit tools which build Flask instances programmatically. The Flask class will fall back to using import on systems with custom module hooks, e.g. Google App Engine, or when the import name is inside a zip archive (usually a .egg) prior to Python 2.7.
  • Blueprints now have a decorator to add custom template filters application wide,flask.Blueprint.app_template_filter().
  • The Flask and Blueprint classes now have a non-decorator method for adding custom template filters application wide,flask.Flask.add_template_filter() andflask.Blueprint.add_app_template_filter().
  • The flask.get_flashed_messages() function now allows rendering flashed message categories in separate blocks, through a category_filter argument.
  • The flask.Flask.run() method now accepts None for host and port arguments, using default values when None. This allows for calling run using configuration values, e.g. app.run(app.config.get('MYHOST'), app.config.get('MYPORT')), with proper behavior whether or not a config file is provided.
  • The flask.render_template() method now accepts a either an iterable of template names or a single template name. Previously, it only accepted a single template name. On an iterable, the first template found is rendered.
  • Added flask.Flask.app_context() which works very similar to the request context but only provides access to the current application. This also adds support for URL generation without an active request context.
  • View functions can now return a tuple with the first instance being an instance offlask.Response. This allows for returning jsonify(error="error msg"), 400from a view function.
  • Flask and Blueprint now provide a get_send_file_max_age() hook for subclasses to override behavior of serving static files from Flask when usingflask.Flask.send_static_file() (used for the default static file handler) and send_file(). This hook is provided a filename, which for example allows changing cache controls by file extension. The default max-age for send_file and static files can be configured through a new SEND_FILE_MAX_AGE_DEFAULTconfiguration variable, which is used in the default get_send_file_max_ageimplementation.
  • Fixed an assumption in sessions implementation which could break message flashing on sessions implementations which use external storage.
  • Changed the behavior of tuple return values from functions. They are no longer arguments to the response object, they now have a defined meaning.
  • Added flask.Flask.request_globals_class to allow a specific class to be used on creation of the g instance of each request.
  • Added required_methods attribute to view functions to force-add methods on registration.
  • Added flask.after_this_request().
  • Added flask.stream_with_context() and the ability to push contexts multiple times without producing unexpected behavior.

Version 0.8.1

Bugfix release, released on July 1st 2012

  • Fixed an issue with the undocumented flask.session module to not work properly on Python 2.5. It should not be used but did cause some problems for package managers.

Version 0.8

Released on September 29th 2011, codename Rakija

  • Refactored session support into a session interface so that the implementation of the sessions can be changed without having to override the Flask class.
  • Empty session cookies are now deleted properly automatically.
  • View functions can now opt out of getting the automatic OPTIONS implementation.
  • HTTP exceptions and Bad Request errors can now be trapped so that they show up normally in the traceback.
  • Flask in debug mode is now detecting some common problems and tries to warn you about them.
  • Flask in debug mode will now complain with an assertion error if a view was attached after the first request was handled. This gives earlier feedback when users forget to import view code ahead of time.
  • Added the ability to register callbacks that are only triggered once at the beginning of the first request. (Flask.before_first_request())
  • Malformed JSON data will now trigger a bad request HTTP exception instead of a value error which usually would result in a 500 internal server error if not handled. This is a backwards incompatible change.
  • Applications now not only have a root path where the resources and modules are located but also an instance path which is the designated place to drop files that are modified at runtime (uploads etc.). Also this is conceptionally only instance depending and outside version control so it’s the perfect place to put configuration files etc. For more information see 实例文件夹.
  • Added the APPLICATION_ROOT configuration variable.
  • Implemented session_transaction() to easily modify sessions from the test environment.
  • Refactored test client internally. The APPLICATION_ROOT configuration variable as well as SERVER_NAME are now properly used by the test client as defaults.
  • Added flask.views.View.decorators to support simpler decorating of pluggable (class-based) views.
  • Fixed an issue where the test client if used with the “with” statement did not trigger the execution of the teardown handlers.
  • Added finer control over the session cookie parameters.
  • HEAD requests to a method view now automatically dispatch to the get method if no handler was implemented.
  • Implemented the virtual flask.ext package to import extensions from.
  • The context preservation on exceptions is now an integral component of Flask itself and no longer of the test client. This cleaned up some internal logic and lowers the odds of runaway request contexts in unittests.

Version 0.7.3

Bugfix release, release date to be decided

  • Fixed the Jinja2 environment’s list_templates method not returning the correct names when blueprints or modules were involved.

Version 0.7.2

Bugfix release, released on July 6th 2011

  • Fixed an issue with URL processors not properly working on blueprints.

Version 0.7.1

Bugfix release, released on June 29th 2011

  • Added missing future import that broke 2.5 compatibility.
  • Fixed an infinite redirect issue with blueprints.

Version 0.7

Released on June 28th 2011, codename Grappa

  • Added make_default_options_response() which can be used by subclasses to alter the default behavior for OPTIONS responses.
  • Unbound locals now raise a proper RuntimeError instead of anAttributeError.
  • Mimetype guessing and etag support based on file objects is now deprecated forflask.send_file() because it was unreliable. Pass filenames instead or attach your own etags and provide a proper mimetype by hand.
  • Static file handling for modules now requires the name of the static folder to be supplied explicitly. The previous autodetection was not reliable and caused issues on Google’s App Engine. Until 1.0 the old behavior will continue to work but issue dependency warnings.
  • fixed a problem for Flask to run on jython.
  • added a PROPAGATE_EXCEPTIONS configuration variable that can be used to flip the setting of exception propagation which previously was linked to DEBUG alone and is now linked to either DEBUG or TESTING.
  • Flask no longer internally depends on rules being added through the add_url_rulefunction and can now also accept regular werkzeug rules added to the url map.
  • Added an endpoint method to the flask application object which allows one to register a callback to an arbitrary endpoint with a decorator.
  • Use Last-Modified for static file sending instead of Date which was incorrectly introduced in 0.6.
  • Added create_jinja_loader to override the loader creation process.
  • Implemented a silent flag for config.from_pyfile.
  • Added teardown_request decorator, for functions that should run at the end of a request regardless of whether an exception occurred. Also the behavior forafter_request was changed. It’s now no longer executed when an exception is raised. See Upgrading to new Teardown Handling
  • Implemented flask.has_request_context()
  • Deprecated init_jinja_globals. Override the create_jinja_environment()method instead to achieve the same functionality.
  • Added flask.safe_join()
  • The automatic JSON request data unpacking now looks at the charset mimetype parameter.
  • Don’t modify the session on flask.get_flashed_messages() if there are no messages in the session.
  • before_request handlers are now able to abort requests with errors.
  • it is not possible to define user exception handlers. That way you can provide custom error messages from a central hub for certain errors that might occur during request processing (for instance database connection errors, timeouts from remote resources etc.).
  • Blueprints can provide blueprint specific error handlers.
  • Implemented generic 可插拨视图 (class-based views).

Version 0.6.1

Bugfix release, released on December 31st 2010

  • Fixed an issue where the default OPTIONS response was not exposing all valid methods in the Allow header.
  • Jinja2 template loading syntax now allows ”./” in front of a template load path. Previously this caused issues with module setups.
  • Fixed an issue where the subdomain setting for modules was ignored for the static folder.
  • Fixed a security problem that allowed clients to download arbitrary files if the host server was a windows based operating system and the client uses backslashes to escape the directory the files where exposed from.

Version 0.6

Released on July 27th 2010, codename Whisky

  • after request functions are now called in reverse order of registration.
  • OPTIONS is now automatically implemented by Flask unless the application explicitly adds ‘OPTIONS’ as method to the URL rule. In this case no automatic OPTIONS handling kicks in.
  • static rules are now even in place if there is no static folder for the module. This was implemented to aid GAE which will remove the static folder if it’s part of a mapping in the .yml file.
  • the config is now available in the templates as config.
  • context processors will no longer override values passed directly to the render function.
  • added the ability to limit the incoming request data with the newMAX_CONTENT_LENGTH configuration value.
  • the endpoint for the flask.Module.add_url_rule() method is now optional to be consistent with the function of the same name on the application object.
  • added a flask.make_response() function that simplifies creating response object instances in views.
  • added signalling support based on blinker. This feature is currently optional and supposed to be used by extensions and applications. If you want to use it, make sure to have blinker installed.
  • refactored the way URL adapters are created. This process is now fully customizable with the create_url_adapter() method.
  • modules can now register for a subdomain instead of just an URL prefix. This makes it possible to bind a whole module to a configurable subdomain.

Version 0.5.2

Bugfix Release, released on July 15th 2010

  • fixed another issue with loading templates from directories when modules were used.

Version 0.5.1

Bugfix Release, released on July 6th 2010

  • fixes an issue with template loading from directories when modules where used.

Version 0.5

Released on July 6th 2010, codename Calvados

  • fixed a bug with subdomains that was caused by the inability to specify the server name. The server name can now be set with the SERVER_NAME config key. This key is now also used to set the session cookie cross-subdomain wide.
  • autoescaping is no longer active for all templates. Instead it is only active for .html,.htm.xml and .xhtml. Inside templates this behavior can be changed with theautoescape tag.
  • refactored Flask internally. It now consists of more than a single file.
  • flask.send_file() now emits etags and has the ability to do conditional responses builtin.
  • (temporarily) dropped support for zipped applications. This was a rarely used feature and led to some confusing behavior.
  • added support for per-package template and static-file directories.
  • removed support for create_jinja_loader which is no longer used in 0.5 due to the improved module support.
  • added a helper function to expose files from any directory.

Version 0.4

Released on June 18th 2010, codename Rakia

  • added the ability to register application wide error handlers from modules.
  • after_request() handlers are now also invoked if the request dies with an exception and an error handling page kicks in.
  • test client has not the ability to preserve the request context for a little longer. This can also be used to trigger custom requests that do not pop the request stack for testing.
  • because the Python standard library caches loggers, the name of the logger is configurable now to better support unittests.
  • added TESTING switch that can activate unittesting helpers.
  • the logger switches to DEBUG mode now if debug is enabled.

Version 0.3.1

Bugfix release, released on May 28th 2010

  • fixed a error reporting bug with flask.Config.from_envvar()
  • removed some unused code from flask
  • release does no longer include development leftover files (.git folder for themes, built documentation in zip and pdf file and some .pyc files)

Version 0.3

Released on May 28th 2010, codename Schnaps

  • added support for categories for flashed messages.
  • the application now configures a logging.Handler and will log request handling exceptions to that logger when not in debug mode. This makes it possible to receive mails on server errors for example.
  • added support for context binding that does not require the use of the with statement for playing in the console.
  • the request context is now available within the with statement making it possible to further push the request context or pop it.
  • added support for configurations.

Version 0.2

Released on May 12th 2010, codename Jägermeister

  • various bugfixes
  • integrated JSON support
  • added get_template_attribute() helper function.
  • add_url_rule() can now also register a view function.
  • refactored internal request dispatching.
  • server listens on 127.0.0.1 by default now to fix issues with chrome.
  • added external URL support.
  • added support for send_file()
  • module support and internal request handling refactoring to better support pluggable applications.
  • sessions can be set to be permanent now on a per-session basis.
  • better error reporting on missing secret keys.
  • added support for Google Appengine.

Version 0.1

First public preview release.

License

Flask is licensed under a three clause BSD License. It basically means: do whatever you want with it as long as the copyright in Flask sticks around, the conditions are not modified and the disclaimer is present. Furthermore you must not use the names of the authors to promote derivatives of the software without written consent.

The full license text can be found below (Flask License). For the documentation and artwork different licenses apply.

Authors

Flask is written and maintained by Armin Ronacher and various contributors:

Development Lead
Patches and Suggestions
  • Adam Zapletal
  • Ali Afshar
  • Chris Edgemon
  • Chris Grindstaff
  • Christopher Grebs
  • Daniel Neuhäuser
  • David Lord @davidism
  • Edmond Burnett
  • Florent Xicluna
  • Georg Brandl
  • Jeff Widman @jeffwidman
  • Justin Quick
  • Kenneth Reitz
  • Keyan Pishdadian
  • Marian Sigler
  • Martijn Pieters
  • Matt Campell
  • Matthew Frazier
  • Michael van Tellingen
  • Ron DuPlain
  • Sebastien Estienne
  • Simon Sapin
  • Stephane Wirtel
  • Thomas Schranz
  • Zhao Xiaohong

General License Definitions

The following section contains the full license texts for Flask and the documentation.

  • “AUTHORS” hereby refers to all the authors listed in the Authors section.
  • The “Flask License” applies to all the sourcecode shipped as part of Flask (Flask itself as well as the examples and the unittests) as well as documentation.
  • The “Flask Artwork License” applies to the project’s Horn-Logo.

Flask License

Copyright (c) 2015 by Armin Ronacher and contributors. See AUTHORS for more details.

Some rights reserved.

Redistribution and use in source and binary forms of the software as well as documentation, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Flask Artwork License

Copyright (c) 2010 by Armin Ronacher.

Some righ/Users/dormouse/project/Flask_Docs_ZhCn/license.rstts reserved.

This logo or a modified version may be used by anyone to refer to the Flask project, but does not indicate endorsement by the project.

Redistribution and use in source (the SVG file) and binary forms (rendered PNG files etc.) of the image, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice and this list of conditions.
  • The names of the contributors to the Flask software (see AUTHORS) may not be used to endorse or promote products derived from this software without specific prior written permission.

Note: we would appreciate that you make the image a link to http://flask.pocoo.org/ if you use it on a web page.

关于 Flask

Flask 是一个用于 Python 的微型网络开发框架。您现在看到的是开发版本文档。

其他格式文档

您可以下载其他格式的文档:

实用链接

中文译本

这是 Flask 0.10 版文档的中文翻译,由 Dormouse Young 翻译,如果对翻译有任何意见 或建议,请与 译者联系,或者直接写在这里

 

posted on 2016-09-18 17:09  Poer李  阅读(1927)  评论(0编辑  收藏  举报

导航