Flask是Python微框架,基于WerkzeuyJinja2实现。

Flask不包含数据库层,也不会包含表单库或是这个方向的其他东西。Flask建立在Werkezug和Jinja2的桥梁,前者实现一个合适的WSGI应用,后者处理模板。Flask也绑定了一些通用的标准库包,比如logging。其他所有一切取决于扩展。

Flask的思想是为所有应用建立一个良好的基础,其余的一切都取决于你和扩展。

Hello World

创建一个hello.py文件

1
2
3
4
5
6
7
 
from flask import Flask
app = Flask(__name__)

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

安装Flask并执行hello.py文件

1
2
3
4
5
 
$ pip install Flask
$ export FLASK_APP=hello.py
$ flask run
* Running on http://localhost:5000/

If you are on Windows you need to use set instead of export.
Flask最新版本0.12.2。建议在Python3.x环境下运行。
注意: 上面的服务只能在你本地电脑才能访问,因为默认的debugging 模式下,应用的代码可以在你的电脑上运行。
如果你想让其他人电脑也可以访问你的这个服务,你只需要执行flask run --host=0.0.0.0
上面操作告诉系统允许所有公网IP访问。

Debug Mode

当开发时每当改变你的代码,Flask就需要重启一次。Flask提供了debug模式支持当代码变动时自动重启应用。

开启debug模式,你需要在允许服务器前定义FLASK_DEBUG环境变量。

1
2
3
$ export FLASK_APP=hello.py
$ export FLASK_DEBUG=1
$ flask run

(在windows环境下export需替换成set)
上面代码完成了三个操作:

  • 激活了debugger
  • 激活了自动加载
  • 开启了debug模式

Flask0.11开始你有多种构建方法运行开发服务。推荐使用flask命令行模式,也可以继续使用Flask.run()方法。

Command Line

1
2
3
4
 
$ export FLASK_APP=my_application
$ export FLASK_DEBUG=1
$ flask run

这推荐使用的运行开发服务模式。你也可以通过传递参数给run操作符。例如禁止重新加载:

1
2
 
$ flask run --no-reload

如果不指定设置FLASK_APP环境变量,可以通过flask run --reload实现文件变动自动刷新

In Code

可以通过Flask.run()方法启动服务。

1
2
3
 
if __name__ == '__main__':
app.run()

上面如果在Debug模式下会导致一些奇怪的事情发生(一段代码执行两次,意外崩溃,死机当语法或导入错误)。所以在flask0.11后推荐使用命令行模式启动服务。

注意:

1
尽管交互式调试器在允许 fork 的环境中无法正常使用(也即在生产服务器上正常使用几乎是不可能的),但它依然允许执行任意代码。这使它成为一个巨大的安全隐患,因此它 绝对不能用于生产环境 。

路由

Flask路由系统继承于Werkzeug路由系统进行了封住。

路由例子:

1
2
3
4
5
6
7
8
9
10
from flask import Flask
app = Flask(__name__)

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

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

@app.route(rule, **options)装饰器,将url规则于视图函数进行绑定。当url规则匹配到将触发对应的方法。

也可以通过app.add_url_rule(rule, endpoint=None, view_func=None, **options)方法手动将路由规则和视图函数进行绑定。

1
2
3
4
def cat ():
return 'Cat Page'
app.add_url_rule('/cat','cat',cat)
# app.add_url_rule('/cat',view_func=cat)

变量规则

URL上可以添加变量部分,你只需要标记为<variable_name>
变量部分将通过定义的变量关键字通过参数传递给视图函数。

1
2
3
@app.route('/user/<username>')
def user_name(username):
return 'Hello %s'% username

一个视图函数可以绑定多个路由装饰器,如果路由装饰器有变量部分,可以在视图函数指定默认变量值。

1
2
3
4
@app.route('/user/')
@app.route('/user/<username>')
def user_name(username='world'):
return 'Hello %s'% username

可以将变量部分转换为指定的类型,只需将变量部分定义成<converter:name>

1
2
3
4
@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

支持的转换类型有这些:

类型 描述
string 接收任何类型 除了斜杠
int 接收整数
float 接收浮点数
path 像默认,但也接受斜杠
any 匹配提供的项目之一
uuid 接收 uuid字符串

HTTP请求类型

可以指定视图函数触发的请求类型,这样可以根据不同请求类型进行不同处理或者触发不同视图函数。

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

支持的请求类型有:

  • GET - 浏览器告诉服务器想获取一些信息。
  • HEAD - 浏览器告诉服务器:欲获取信息,但是只关心 消息头 。应用应像处理 GET 请求一样来处理它,但是不分发实际内容。在 Flask 中你完全无需 人工 干预,底层的 Werkzeug 库已经替你打点好了。
  • POST - 浏览器告诉服务器:想在 URL 上 发布 新信息。并且,服务器必须确保 数据已存储且仅存储一次。这是 HTML 表单通常发送数据到服务器的方法。
  • PUT - 类似 POST 但是服务器可能触发了存储过程多次,多次覆盖掉旧值。你可 能会问这有什么用,当然这是有原因的。考虑到传输中连接可能会丢失,在 这种 情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而 不破坏其它东西。因为 POST 它只触发一次,所以用 POST 是不可能的。
  • DELETE - 删除给定位置的信息。
  • OPTIONS - 给客户端提供一个敏捷的途径来弄清这个 URL 支持哪些 HTTP 方法。 从 Flask 0.6 开始,实现了自动处理。

构造URL

如果 Flask 能匹配 URL,那么 Flask 可以生成它们吗?当然可以。你可以用 url_for() 来给指定的函数构造 URL。它接受函数名作为第一个参数,也接受对应 URL 规则的变量部分的命名参数。未知变量部分会添加到 URL 末尾作为查询参数。例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> 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

唯一URL/重定向行为

Flask 的 URL 规则基于 Werkzeug 的路由模块。这个模块背后的思想是基于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。

1
2
3
4
5
6
7
@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 结尾不带斜线,类似 UNIX-like 系统下的文件的路径名。访问结尾带斜线的 URL 会产生一个 404 “Not Found” 错误。

这个行为使得在遗忘尾斜线时,允许关联的 URL 接任工作,与 Apache 和其它的服务器的行为并无二异。此外,也保证了 URL 的唯一,有助于避免搜索引擎索引同一个页面两次。

请求

Flask中http请求会绑定在全局变量request上。
在一个视图函数通过request对象获取请求数据。

1
2
3
4
5
6
7
8
9
10
from flask import request

@app.route('/login',methods=['GET','POST'])
def login ():
error = None
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
return '登录中'
return '请登录~'
  • request.form['xxx']获取表单提交的数据(POST或PUT请求)
  • request.method 获取请求方法
  • request.path 获取请求地址
  • request.args.get('key','')获取URL上参数
  • request.cookies.get('username')获取usernamecookie值

响应

从视图函数返回的值会自动转换成响应对象。如果返回值是一个字符串,它会转换成响应对象并且字符串作为响应主体。

Flask转换返回值为响应对象的逻辑如下:

  • 如果返回是正确类型的响应对象,则直接从视图函数返回。
  • 如果是个字符串,使用这个数据和默认参数创建响应对象。
  • 如果是个元组,这个元组提供额外的信息。这个元组形式必须是(response,status,headers)(response,headers)最少有一项是在元组中。status会覆盖状态码。headers能够是个列表或一个字典补充头部信息。
  • 如果上面条件都没满足,Flask会假定返回一个合肥的WSGI应用值,然后将它转换响应对象。

返回视图

1
2
3
4
5
6
from flask import render_template

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

render_template(template,data)渲染模板函数。

Flask会去templates 目录查找模板(hello.html)。如果你的应用是一个模块,这个模板文件夹应该和这个模块在一个层级上。:

1
2
3
/application.py
/templates
/hello.html

如果你的应用是个包,则这个模板文件夹应该在包里面:

1
2
3
4
/application
/__init__.py
/templates
/hello.html

返回JSON

如果在视图函数直接返回一个dict对象,Flask会报错(TypeError: 'dict' object is not callable)。
可以通过jsonify方法对dict对象进行包装再返回。

1
2
3
4
5
6
7
8
9
10
11
12
from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/')
def root():
t = {
'a': 1,
'b': 2,
'c': [3, 4, 5]
}
return jsonify(t)

添加headers信息 flask.make_response

有时我们需要在视图头部添加一些信息。视图不需要返回一个响应对象,因为Flask会自动将它转换成响应对象。可以调用flask.make_response()方法而不是返回,你获取一个响应对象,这样你就可以添加头部信息:

1
2
3
4
def index():
response = make_response(render_template('index.html', foo=42))
response.headers['X-Parachutes'] = 'parachutes are cool'
return response

通过Response返回JSON数据

1
2
3
4
5
6
7
8
9
from flask import Response
import json

@app.route('/')
def root ():
t = {'success': True, 'msg': 'ok!'}
res = Response(json.dumps(t), mimetype='application/json')
res.set_cookie(key="logined", value='true')
return res

蓝图(Blueprint)

Flask用于实现模块化组织程序结构的方法。

在开发一个web项目时,通常使用两种方式组织架构:

  • 功能式架构 - 在功能式架构中,按照每部分代码的功能来组织你的应用。所有模板放到同一个文件夹中,静态文件放在另一个文件夹中,而视图放在第三个文件夹中。
1
2
3
4
5
6
7
8
9
10
11
12
13
yourapp/
__init__.py
static/
templates/
home/
control_panel/
admin/
views/
__init__.py
home.py
control_panel.py
admin.py
models.py
  • 分区式架构 - 在分区式架构中,按照每一部分所属的蓝图来组织你的应用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
yourapp/
__init__.py
admin/
__init__.py
views.py
static/
templates/
home/
__init__.py
views.py
static/
templates/
control_panel/
__init__.py
views.py
static/
templates/
models.py

将相关联的部分作为一个blueprint,这样遍历管理和多人协同开发。最后只需要将blueprints汇集起来注册到Flask

创建一个Blueprint

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
from flask import Blueprint

admin = Blueprint('admin', __name__)

@admin.route('/login/')
def admin_login():
pass

@admin.route('/login_out/')
def admin_login_out():
pass

Blueprint(name, import_name)类接受参数nameimport_name进行初始化。通常用__name__表示当前模块的特殊Python变量,作为import_name的取值。

如果你使用分区式架构,你得告诉Flask某个蓝图有自己得模板和静态文件夹你需要另外传两个参数template_folderstatic_folder:

1
2
3
admin = Blueprint('admin', __name__,
template_folder='templates',
static_folder='static')

注册Blueprint

1
2
3
4
5
from flask import Flask
from application.admin import admin

app = Flask(__name__)
app.register_blueprint(admin,url_prefix='/admin')

register_blueprint(blueprint,url_prefix)将blueprint注册到Flask应用中,并给blueprint定义一个公用得url前缀。

即插视图(Pluggable Views)

将视图函数转为成为类,以便于扩展和通用。例如:

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

上面视图函数简单而灵活,但如果需要同样适应其他模型或模板得方式,你需要更大得灵活性。你需要基于类得即插视图。

1
2
3
4
5
6
7
8
9
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/', ShowUsers.as_view('show_users'))

你需要创建一个 flask.views.View的子类,并且实现dispatch_request()。然后需要通过as_view()类方法将类转换到一个实际的视图函数。你传的这个函数的字符串是视图之后的终极名称,在Flask内部将通过这个endpoint找到对应的view function。

方法提示

即插视图可以像常规函数用route()add_url_rule()附加到应用上。你可以在类定义methods=[]指定视图函数接收的请求类型。

1
2
3
4
5
6
7
8
9
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'))

flask.views.MethodView

通过MethodView类可以方便的在处理相同路径下不同请求方式情况。每个 HTTP 方法映射到同名函数 (只有名称为小写的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from flask.views import MethodView

class UserAPI(MethodView):

def get(self, user_id):
if user_id is None:
# return a list of users
pass
else:
# expose a single user
pass

def post(self):
# create a new user
pass

def delete(self, user_id):
# delete a single user
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'])

配置处理

在实际开发中一般会面对不同的环境,每个环境都会自己的配置项。
Flask提供了app.config.from_object(),app.config.from_pyfile(),app.config.from_envvar()方法用于不同环境加载不同配置项。

app.config.from_object()

app.config.from_object(object)接收一个python对象。实际开发中推荐为不同环境创建不同的配置文件:

1
2
3
4
5
6
|config
|__init__.py
| default.py
| development.py
| producation.py
| test.py

在启动文件可以这样调用

1
2
3
from flask import Flask
app = Flask(__name__)
app.config.from_object('config.default')
config.default
1
DEBUG=True

app.config.from_pyfile()

有时你需要定义一些不能为人所知的配置变量。为此,你会想要把它们从config.py中的其他变量分离出来,并保持在版本控制之外。 你可能要隐藏类似数据库密码和API密钥的秘密,或定义特定于当前机器的参数。 为了让这更加轻松,Flask提供了一个叫instance文件夹的特性。 instance文件夹是根目录的一个子文件夹,包括了一个特定于当前应用实例的配置文件。我们不要把它提交到版本控制中。

这是一个使用了instance文件夹的简单Flask应用的结构:

1
2
3
4
5
6
7
8
9
10
11
config.py
requirements.txt
run.py
instance/
config.py
yourapp/
__init__.py
models.py
views.py
templates/
static/

要想加载定义在instance文件夹中的配置变量,你可以使用app.config.from_pyfile()。 如果在调用Flask()创建应用时设置了instance_relative_config=Trueapp.config.from_pyfile()将查看在instance文件夹的特殊文件。

1
2
3
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('config')
app.config.from_pyfile('config.py')

现在,你可以在instance/config.py中定义变量,一如在config.py。 你也应该将instance文件夹加入到版本控制系统的忽略名单中。比如假设你用的是git,你需要在gitignore中新开一行,写下instance/。

app.config.from_envvar()

你可以在命令行模式设定环境变量,这个环境变量应该是配置文件的绝对路径

1
2
3
4
5
from flask import Flask
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('config.default')
app.config.from_pyfile('config.py') # 从instance文件夹中加载配置
app.config.from_envvar('APP_CONFIG_FILE')
1
2
$ APP_CONFIG_FILE=/var/www/yourapp/config/production.py
$ python run.py

插件

Flask-Migrate

Flask-Migrate用于对SQLAlchemy数据迁移进行操作的Flask插件。

安装

1
pip install Flask-Migrate

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'

db = SQLAlchemy(app)
migrate = Migrate(app, db) # 将应用和数据关联起来

class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128))

对上面的应用,可以使用下面命令创建迁移仓库:

1
$ flask db init

执行完后在应用目录下会多个migrations 文件夹。文件夹里面的内容需要和其它源文件一起添加进你的版本控制。

你可以生成初始迁移:

1
$ flask db migrate

Flask-Migrate是基于Alembic。它有一些限制:表名变化,字段名变化,匿名命名约束它是无法检测到。

通过下面命令可以将迁移应用到数据:

1
flask db upgrade

每次迁移操作都应该添加进行版本控制中。

每次数据模型有变化,重复上面migrateupgrade命令。

Flask-Login

Flask-Login为Flask提供用户会话管理,处理用户的登录,登出,登录状态判断。

它可以处理:

  • 会话中存储激活了的用户ID,让你登录或登出更简单。
  • 让你限制登入(或登出)用户可以访问的视图。
  • 处理棘手的记住我功能。
  • 保护用户会话避免被盗。
  • 可以于Flask-Principal或其它认证扩展集成。

它无法处理:

  • 限制你使用特定的数据库或其它存储方法。如何加载用户完全由你决定。
  • 限制你使用用户名和密码,OpenIDs,或者其它的认证方法。
  • 处理超越 “登入或者登出” 之外的权限。
  • 处理用户注册或者账号恢复。

安装

1
$ pip install flask-login

基础用法

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
login_manager.login_message = "You must be logged in to access this page." #登录提示文案
login_manager.login_view = "auth.login" #登录视图函数
login_manager = LoginManager() #配置你的应用
login_manager.init_app(app) #将Flask-Login于你的应用绑定起来
login_manager.session_protection = "strong"
# 获取存储的用户对象通过用户ID
@login_manager.user_loader
def load_user(user_id):
return exampleModel.query.get(int(user_id))

保护视图 flask.ext.login.login_required(func)[source]

1
2
3
4
@app.route('/post')
@login_required
def post():
pass

上面方法只有用户登录后才能调用。

定制登录 Request Loader

在调用接口时,我们需要将请求验证密钥,这密码一般放在请求头部。

通过request_loader方法我们可以创建一个解析请求头部信息方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@login_manager.request_loader
def load_user_from_request(request):

# first, try to login using the api_key url arg
api_key = request.args.get('api_key')
if api_key:
user = User.query.filter_by(api_key=api_key).first()
if user:
return user

# next, try to login using Basic Auth
api_key = request.headers.get('Authorization')
if api_key:
api_key = api_key.replace('Basic ', '', 1)
try:
api_key = base64.b64decode(api_key)
except TypeError:
pass
user = User.query.filter_by(api_key=api_key).first()
if user:
return user

# finally, return None if both methods did not login the user
return None

记住我

当用户关闭浏览器后,会话就当结束。当打开网页用户需要重新登录才能获取到用户信息。通过login_user(remeber= True)[http://www.pythondoc.com/flask-login/#flask.ext.login.login_user]设置,当用户登录过一次后,会在用户计算机中存储一个cookie。当用户在打开网页时会读取cookie信息恢复用户ID。cookie是防篡改的,当用户改过它,它就会被拒绝。

新鲜登录

如果用户通过登录获取用户信息Flask-Login会将用户标记为fresh。如果是通过cookie获取到用户信息会被标记为non-fresh

login-requiredfreshnon-fresh一样的效果。当如果有些操作比如:修改用户信息则应该只允许fresh操作,如果是non-fresh用户应该让他登录。

fresh_login_required装饰的方法只有fresh用户才能调用。
login_manager.refresh_view='auth.login'指定non-fresh登录视图方法。
login_manager.needs_refresh_message = ( u"To protect your account, please reauthenticate to access this page." )重新登录文案提示。

会话保护

你可以在 LoginManager 上和应用配置中配置会话保护。如果它被启用,它可以在 basic 或 strong 两种模式中运行。要在 LoginManager 上设置它,设置 session_protection 属性为 “basic” 或 “strong”:

1
login_manager.session_protection = "strong" #None 、basic

默认,它被激活为 “basic” 模式。它可以在应用配置中设定 SESSION_PROTECTION 为 None 、 “basic” 或 “strong” 来禁用。

当启用了会话保护,每个请求,它生成一个用户电脑的标识(基本上是 IP 地址和 User Agent 的 MD5 hash 值)。如果会话不包含相关的标识,则存储生成的。如果存在标识,则匹配生成的,之后请求可用。

在 basic 模式下或会话是永久的,如果该标识未匹配,会话会简单地被标记为非活 跃的,且任何需要活跃登入的东西会强制用户重新验证。(当然,你必须已经使用了活跃登入机制才能奏效。)

在 strong 模式下的非永久会话,如果该标识未匹配,整个会话(记住的令牌如果存在,则同样)被删除。

Flask-Session

原生flask实用cookie签名保存session,而该插件支持将session保存到多个地方,如:redis、memcached、mongodb。

安装

1
$ pip install flask-session

保存到redis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# -*- coding:utf-8 -
import redis
from flask import Flask, session
from flask_session import Session

app = Flask(__name__)
app.debug = True
app.secret_key = 'xxxx'

app.config['SESSION_TYPE'] = 'redis' # session类型为redis
app.config['SESSION_PERMANENT'] = False # 如果设置为True,则关闭浏览器session就失效。
app.config['SESSION_USE_SIGNER'] = False # 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_KEY_PREFIX'] = 'sessionid' # 保存到session中的值的前缀
app.config['SESSION_REDIS'] = redis.Redis(host='127.0.0.1', port='6379', password='123') # 用于连接redis的配置

Session(app)


@app.route('/index')
def index():
session['k1'] = 'v1'
return 'xx'