Flask文档
1. 快速入门(Quickstart)
1.1 最小的Flask应用
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
1.2 路由(Routing)
应用route()装饰器建立函数对地址(URL)的映射。
@app.route('/')
def index():
return 'Index Page'
@app.route('/hello')
def hello():
return 'Hello, World'
1.3 变量规则(Variable Rules)
You can add variable sections to a URL by marking sections with <variable_name>. Your function then receives the <variable_name> as a keyword argument.
Optionally, you can use a converter to specify the type of the argument like <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
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# show the subpath after /path/
return 'Subpath %s' % subpath
1.4 唯一URLs/重定向行为(Unique URLs / Redirection Behavior)
@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
The canonical URL for the projects endpoint has a trailing slash. It’s similar to a folder in a file system. If you access the URL without a trailing slash, Flask redirects you to the canonical URL with the trailing slash.
The canonical URL for the about endpoint does not have a trailing slash. It’s similar to the pathname of a file. Accessing the URL with a trailing slash produces a 404 “Not Found” error. This helps keep URLs unique for these resources, which helps search engines avoid indexing the same page twice.
1.5 构建URL(URL Building)
To build a URL to a specific function, use the url_for() function. It accepts the name of the function as its first argument and any number of keyword arguments.
from flask import Flask, url_for
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
@app.route('/login')
def login():
return 'login'
@app.route('/user/<username>')
def profile(username):
return '{}\'s profile'.format(username)
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'))
1.6 HTTP方法
from flask import request
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
return do_the_login()
else:
return show_the_login_form()
1.7 静态文件(Static Files)
Just create a folder called static in your package or next to your module and it will be available at /static on the application.
To generate URLs for static files, use the special 'static' endpoint name:
url_for('static', filename='style.css')
The file has to be stored on the filesystem as static/style.css.
1.8 渲染模板
Flask应用Jinja2来自动创建模板。
To render a template you can use the render_template() method.
from flask import render_template
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
Flask will look for templates in the templates folder. So if your application is a module, this folder is next to that module, if it’s a package it’s actually inside your package:
# Case 1: a module
/application.py
/templates
/hello.html
# Case 2: a package
/application
/__init__.py
/templates
/hello.html
Here is an example template
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}
1.9 请求对象
当前请求的方法可以用 method 属性来访问。你可以用 form 属性来访问表单数据 (数据在 POST 或者 PUT 中传输)。
@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'
# the code below this is executed if the request method
# was GET or the credentials were invalid
return render_template('login.html', error=error)
你可以用 args 属性来接收在 URL ( ?key=value ) 中提交的参数
searchword = request.args.get('key', '')
1.10 Cookies
请求对象中的 cookies 属性是一个客户端发送所有的 cookies 的字典。
读取 cookies
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a
# KeyError if the cookie is missing.
存储 cookies
from flask import make_response
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
1.11 重定向和错误
你能够用 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()
默认情况下,每个错误代码会显示一个黑白错误页面。如果你想定制错误页面,可以使用 errorhandler() 装饰器
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
1.12 关于响应
如果你想要获取在视图中得到的响应对象,你可以用函数 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
1.13 会话
除了请求对象,还有第二个称为 session 对象允许你在不同请求间存储特定用户的信息。这是在 cookies 的基础上实现的,并且在 cookies 中使用加密的签名。这意味着用户可以查看 cookie 的内容, 但是不能修改它,除非它知道签名的密钥。
Flask REST API
快速入门
一个最小的 API
一个最小的 Flask-RESTful API 像这样
from flask import Flask
from flask.ext import restful
app = Flask(__name__)
api = restful.Api(app)
class HelloWorld(restful.Resource):
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
现在打开一个新的命令行窗口使用 curl 测试你的 API
$ curl http://127.0.0.1:5000/
{"hello": "world"}
资源丰富的路由(Resourceful Routing)
Flask-RESTful 提供的最主要的基础就是资源(resources)。资源(Resources)是构建在 Flask 可拔插视图 之上,只要在你的资源(resource)上定义方法就能够容易地访问多个 HTTP 方法。
from flask import Flask, request
from flask.ext.restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(debug=True)
你可以尝试这样
$ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo1
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT
{"todo2": "Change my brakepads"}
$ curl http://localhost:5000/todo2
{"todo2": "Change my brakepads"}
Flask-RESTful 支持视图方法多种类型的返回值。同 Flask 一样,你可以返回任一迭代器,它将会被转换成一个包含原始 Flask 响应对象的响应。Flask-RESTful 也支持使用多个返回值来设置响应代码和响应头。
class Todo1(Resource):
def get(self):
# Default to 200 OK
return {'task': 'Hello world'}
class Todo2(Resource):
def get(self):
# Set the response code to 201
return {'task': 'Hello world'}, 201
class Todo3(Resource):
def get(self):
# Set the response code to 201 and return custom headers
return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}
端点(Endpoints)
很多时候在一个 API 中,你的资源可以通过多个 URLs 访问。你可以把多个 URLs 传给 Api 对象的 Api.add_resource() 方法。每一个 URL 都能访问到你的 Resource。
api.add_resource(HelloWorld, '/', '/hello')
api.add_resource(Todo, '/todo/<int:todo_id>', endpoint='todo_ep')
参数解析
尽管 Flask 能够简单地访问请求数据(比如查询字符串或者 POST 表单编码的数据),验证表单数据仍然很痛苦。Flask-RESTful 内置了支持验证请求数据,它使用了一个类似 argparse 的库。
from flask.ext.restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()
使用 reqparse 模块同样可以自由地提供聪明的错误信息。如果参数没有通过验证,Flask-RESTful 将会以一个 400 错误请求以及高亮的错误信息回应。
$ curl -d 'rate=foo' http://127.0.0.1:5000/
{'status': 400, 'message': 'foo cannot be converted to int'}
数据格式化
默认情况下,在你的返回迭代中所有字段将会原样呈现。尽管当你刚刚处理 Python 数据结构的时候,觉得这是一个伟大的工作,但是当实际处理它们的时候,会觉得十分沮丧和枯燥。为了解决这个问题,Flask-RESTful 提供了 fields 模块和 marshal_with() 装饰器。类似 Django ORM 和 WTForm,你可以使用 fields 模块来在你的响应中格式化结构。
rom collections import OrderedDict
from flask.ext.restful import fields, marshal_with
resource_fields = {
'task': fields.String,
'uri': fields.Url('todo_ep')
}
class TodoDao(object):
def __init__(self, todo_id, task):
self.todo_id = todo_id
self.task = task
# This field will not be sent in the response
self.status = 'active'
class Todo(Resource):
@marshal_with(resource_fields)
def get(self, **kwargs):
return TodoDao(todo_id='my_todo', task='Remember the milk')
上面的例子接受一个 python 对象并准备将其序列化。marshal_with() 装饰器将会应用到由 resource_fields 描述的转换。
flask.ext.restful.marshal(data, fields, envelope=None)
Takes raw data (in the form of a dict, list, object) and a dict of fields to output and filters the data based on those fields.
Parameters:
- data – the actual object(s) from which the fields are taken from
- fields – a dict of whose keys will make up the final serialized response output
- envelope – optional key that will be used to envelop the serialized response
>>> from flask.ext.restful import fields, marshal
>>> data = { 'a': 100, 'b': 'foo' }
>>> mfields = { 'a': fields.Raw }
>>> marshal(data, mfields)
OrderedDict([('a', 100)])
>>> marshal(data, mfields, envelope='data')
OrderedDict([('data', OrderedDict([('a', 100)]))])
flask.ext.restful.marshal_with(fields, envelope=None)
A decorator that apply marshalling to the return values of your methods.
>>> from flask.ext.restful import fields, marshal_with
>>> mfields = { 'a': fields.Raw }
>>> @marshal_with(mfields)
... def get():
... return { 'a': 100, 'b': 'foo' }
...
...
>>> get()
OrderedDict([('a', 100)])
>>> @marshal_with(mfields, envelope='data')
... def get():
... return { 'a': 100, 'b': 'foo' }
...
...
>>> get()
OrderedDict([('data', OrderedDict([('a', 100)]))])
flask.ext.restful.marshal_with_field(field)
A decorator that formats the return values of your methods with a single field.
>>> from flask.ext.restful import marshal_with_field, fields
>>> @marshal_with_field(fields.List(fields.Integer))
... def get():
... return ['1', 2, 3.0]
...
>>> get()
[1, 2, 3]
完整的例子
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
app = Flask(__name__)
api = Api(app)
TODOS = {
'todo1': {'task': 'build an API'},
'todo2': {'task': '?????'},
'todo3': {'task': 'profit!'},
}
def abort_if_todo_doesnt_exist(todo_id):
if todo_id not in TODOS:
abort(404, message="Todo {} doesn't exist".format(todo_id))
parser = reqparse.RequestParser()
parser.add_argument('task')
# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
def get(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
return TODOS[todo_id]
def delete(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
del TODOS[todo_id]
return '', 204
def put(self, todo_id):
args = parser.parse_args()
task = {'task': args['task']}
TODOS[todo_id] = task
return task, 201
# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
def get(self):
return TODOS
def post(self):
args = parser.parse_args()
todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
todo_id = 'todo%i' % todo_id
TODOS[todo_id] = {'task': args['task']}
return TODOS[todo_id], 201
##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')
if __name__ == '__main__':
app.run(debug=True)
获取列表
$ curl http://localhost:5000/todos
{"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}}
$ curl http://localhost:5000/todos/todo3
{"task": "profit!"}
删除一个任务
$ curl http://localhost:5000/todos/todo2 -X DELETE -v
> DELETE /todos/todo2 HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 204 NO CONTENT
< Content-Type: application/json
< Content-Length: 0
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:10:32 GMT
增加一个新的任务
$ curl http://localhost:5000/todos -d "task=something new" -X POST -v
> POST /todos HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
> Content-Length: 18
> Content-Type: application/x-www-form-urlencoded
>
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 25
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:12:58 GMT
<
* Closing connection #0
{"task": "something new"}
更新一个任务
$ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v
> PUT /todos/todo3 HTTP/1.1
> Host: localhost:5000
> Accept: */*
> Content-Length: 20
> Content-Type: application/x-www-form-urlencoded
>
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 27
< Server: Werkzeug/0.8.3 Python/2.7.3
< Date: Mon, 01 Oct 2012 22:13:00 GMT
<
* Closing connection #0
{"task": "something different"}
输出字段
Flask-RESTful 提供了一个简单的方式来控制在你的响应中实际呈现什么数据。使用 fields 模块,你可以使用在你的资源里的任意对象(ORM 模型、定制的类等等)并且 fields 让你格式化和过滤响应,因此您不必担心暴露内部数据结构。
基本用法
你可以定义一个字典或者 fields 的 OrderedDict 类型,OrderedDict 类型是指键名是要呈现的对象的属性或键的名称,键值是一个类,该类格式化和返回的该字段的值。这个例子有三个字段,两个是字符串(Strings)以及一个是日期时间(DateTime),格式为 RFC 822 日期字符串(同样也支持 ISO 8601)。
from flask.ext.restful import Resource, fields, marshal_with
resource_fields = {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}
class Todo(Resource):
@marshal_with(resource_fields, envelope='resource')
def get(self, **kwargs):
return db_get_todo() # Some function that queries the db
这个例子假设你有一个自定义的数据库对象(todo),它具有属性:name, address, 以及 date_updated。该对象上任何其它的属性可以被认为是私有的不会在输出中呈现出来。一个可选的 envelope 关键字参数被指定为封装结果输出。
装饰器 marshal_with 是真正接受你的数据对象并且过滤字段。marshal_with 能够在单个对象,字典,或者列表对象上工作。
重命名属性
很多时候你面向公众的字段名称是不同于内部的属性名。使用 attribute 可以配置这种映射。
fields = {
'name': fields.String(attribute='private_name'),
'address': fields.String,
}
lambda 也能在 attribute 中使用。
fields = {
'name': fields.String(attribute=lambda x: x._private_name),
'address': fields.String,
}
默认值
fields = {
'name': fields.String(default='Anonymous User'),
'address': fields.String,
}
自定义字段&多个值
有时候你有你自己定义格式的需求。你可以继承 fields.Raw 类并且实现格式化函数。当一个属性存储多条信息的时候是特别有用的。例如,一个位域(bit-field)各位代表不同的值。你可以使用 fields 复用一个单一的属性到多个输出值(一个属性在不同情况下输出不同的结果)。
lass UrgentItem(fields.Raw):
def format(self, value):
return "Urgent" if value & 0x01 else "Normal"
class UnreadItem(fields.Raw):
def format(self, value):
return "Unread" if value & 0x02 else "Read"
fields = {
'name': fields.String,
'priority': UrgentItem(attribute='flags'),
'status': UnreadItem(attribute='flags'),
}
Url & 其它具体字段
Flask-RESTful 包含一个特别的字段,fields.Url,即为所请求的资源合成一个 uri。这也是一个好示例,它展示了如何添加并不真正在你的数据对象中存在的数据到你的响应中。
class RandomNumber(fields.Raw):
def output(self, key, obj):
return random.random()
fields = {
'name': fields.String,
# todo_resource is the endpoint name when you called api.add_resource()
'uri': fields.Url('todo_resource'),
'random': RandomNumber,
}
默认情况下,fields.Url 返回一个相对的 uri。为了生成包含协议(scheme),主机名以及端口的绝对 uri,需要在字段声明的时候传入 absolute=True。传入 scheme 关键字参数可以覆盖默认的协议(scheme)。
fields = {
'uri': fields.Url('todo_resource', absolute=True)
'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}
复杂结构
你可以有一个扁平的结构,marshal_with 将会把它转变为一个嵌套结构。
>>> from flask.ext.restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['zip'] = fields.String
>>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'
注意:address 字段并不真正地存在于数据对象中,但是任何一个子字段(sub-fields)可以直接地访问对象的属性,就像没有嵌套一样。
列表字段
你也可以把字段解组(unmarshal)成列表
>>> from flask.ext.restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'
高级:嵌套字段
尽管使用字典套入字段能够使得一个扁平的数据对象变成一个嵌套的响应,你可以使用 Nested 解组(unmarshal)嵌套数据结构并且合适地呈现它们。
>>> from flask.ext.restful import fields, marshal
>>> import json
>>>
>>> address_fields = {}
>>> address_fields['line 1'] = fields.String(attribute='addr1')
>>> address_fields['line 2'] = fields.String(attribute='addr2')
>>> address_fields['city'] = fields.String(attribute='city')
>>> address_fields['state'] = fields.String(attribute='state')
>>> address_fields['zip'] = fields.String(attribute='zip')
>>>
>>> resource_fields = {}
>>> resource_fields['name'] = fields.String
>>> resource_fields['billing_address'] = fields.Nested(address_fields)
>>> resource_fields['shipping_address'] = fields.Nested(address_fields)
>>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> data = { 'name': 'bob', 'billing_address': address1, 'shipping_address': address2}
>>>
>>> json.dumps(marshal_with(data, resource_fields))
'{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'
此示例使用两个嵌套字段。Nested 构造函数把字段的字典作为子字段(sub-fields)来呈现。使用 Nested 和之前例子中的嵌套字典之间的重要区别就是属性的上下文。在本例中 “billing_address” 是一个具有自己字段的复杂的对象,传递给嵌套字段的上下文是子对象(sub-object),而不是原来的“数据”对象。
扩展 Flask-RESTful
内容协商
开箱即用,Flask-RESTful 仅配置为支持 JSON。我们做出这个决定是为了给 API 维护者完全控制 API 格式支持,因此一年来的路上,你不必支持那些使用 API 且用 CSV 表示的人们,甚至你都不知道他们的存在。要添加其它的 mediatypes 到你的 API 中,你需要在 Api 对象中声明你支持的表示。
app = Flask(__name__)
api = restful.Api(app)
@api.representation('application/json')
def output_json(data, code, headers=None):
resp = make_response(json.dumps(data), code)
resp.headers.extend(headers or {})
return resp
自定义字段 & 输入
一种最常见的 Flask-RESTful 附件功能就是基于你自己数据类型的数据来定义自定义的类型或者字段。
字段
自定义输出字段让你无需直接修改内部对象执行自己的输出格式。所有你必须做的就是继承 Raw 并且实现 format() 方法
class AllCapsString(fields.Raw):
def format(self, value):
return value.upper()
# example usage
fields = {
'name': fields.String,
'all_caps_name': AllCapsString(attribute=name),
}
输入
对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。
def odd_number(value):
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
请求解析器在你想要在错误消息中引用名称的情况下将也会允许你访问参数的名称。
def odd_number(value, name):
if value % 2 == 0:
raise ValueError("The parameter '{}' is not odd. You gave us the value: {}".format(name, value))
return value
你还可以将公开的参数转换为内部表示
# maps the strings to their internal integer representation
# 'init' => 0
# 'in-progress' => 1
# 'completed' => 2
def task_status(value):
statuses = [u"init", u"in-progress", u"completed"]
return statuses.index(value)
然后你可以在你的 RequestParser 中使用这些自定义输入类型
parser = reqparse.RequestParser()
parser.add_argument('OddNumber', type=odd_number)
parser.add_argument('Status', type=task_status)
args = parser.parse_args()
响应格式
为了支持其它的表示(像 XML,CSV,HTML),你可以使用 representation() 装饰器。你需要在你的 API 中引用它。