[译]Flask Framework Cookbook-第七章 构建RESTful API
2018-02-09 10:51
423 查看
第七章 构建RESTful API
API,即应用编程接口,可以概括为应用对开发者的接口。就像用户有一个可以和应用沟通的可视化界面一样,开发者同样需要一个接口和应用交互。REST,即表现层状态转移,它不是一个协议或者标准。它只是一种软件架构风格,或者是为编写应用程序定义的一组约束,旨在简化应用程序内外接口。当web服务API遵循了REST风格进行编写时,它们就可以称为RESTful API。RESTful使得API和应用内部细节分离。这使得扩展很容易,并且使得事情变得简单。统一接口确保每个请求都得文档化。提示
关于REST和SOAP哪个好存在一个争论。它实际上是一个主观问题,因为它取决于需要做什么。每个都有它自己的好处,应该根据应用程序的需要来进行选择。
这一章,我们将包含下面小节:
创建一个基于类的REST接口
创建一个基于扩展的REST接口
创建一个SQLAlchemy-independent REST API
一个完整的REST API例子
介绍
从名字可以看出,表现层状态转移(REST)意味着可以分离API到逻辑资源,这些资源可以通过使用HTTP请求获得和操作,一个HTTP请求由GET,POST,PUT,PATCH,DELETE中的一个(还有其他HTTP方法,但这些是最常使用的)。这些方法中的每一个都有一个特定的意义。REST的关键隐含原则之一是资源的逻辑分组应该是简单容易理解的,提供简单性和可移植性。这本书到这里,我们一直在使用一个资源叫做Product。让我们来看看怎么讲API调用映射到资源分离上:
GET /products/1:获取ID为1的商品
GET /products:获取商品列表
POST /products:创建一个新商品
PUT /products/1:更新ID为1的商品
PATCH /products/1:部分更新ID为1的商品
DELETE /products/1:删除ID为1的商品
创建一个基于类的REST接口
在第四章里我们看到了在Flask里如何使用基于类的视图。我们将使用相同的概念去创建视图,为我们应用提供REST接口。准备
让我们写一个简单的视图来处理Product模型的REST接口。怎么做
需要简单的修改商品视图,来继承MethodView类:from flask.views import MethodView class ProductView(MethodView): def get(self, id=None, page=1): if not id: products = Product.query.paginate(page, 10).items res = {} for product in products: res[product.id] = { 'name': product.name, 'price': product.price, 'category': product.category.name } # 译者注 加上这一句,否则会报错 res = json.dumps(res) else: product = Product.query.filter_by(id=id).first() if not product: abort(404) res = json.dumps({ 'name': product.name, 'price': product.price, 'category': product.category.name }) return res
get()方法搜索product,然后返回JSON结果。
可以用同样的方式完成post(),put(),delete()方法:
def post(self): # Create a new product. # Return the ID/object of newly created product. return def put(self, id): # Update the product corresponding provided id. # Return the JSON corresponding updated product. return def delete(self, id): # Delete the product corresponding provided id. # Return success or error message. return
很多人会想为什么我们没在这里写路由。为了包含路由,我们得像下面这样做:
product_view = ProductView.as_view('product_view') app.add_url_rule('/products/', view_func=product_view, methods=['GET', 'POST']) app.add_url_rule('/products/<int:id>', view_func=product_view, methods=['GET', 'PUT', 'DELETE'])
第一句首先转换类为实际的视图函数,这样才可以用在路由系统中。后面两句是URL规则和其对应的请求方法。
译者注
测试时如果遇到/products/路由已经注册,原因可能是第四章已经定义了一个/products/视图函数,注释掉即可,或者修改这里的路由名称。
原理
MethodView类定义了请求中的HTTP方法,并将名字转为小写。请求到来时,HTTP方法匹配上类中定义的方法,就会调用相应的方法。所以,如果对ProductView进行一个GET调用,它将自动的匹配上get()方法。更多
我们还可以使用一个叫做Flask-Classy的扩展(https://pythonhosted.or/Flask-Classy)。这将在很大程度上自动处理类和路由,并使生活更加美好。我们不会在这里讨论这些,但它是一个值得研究的扩展。
创建基于扩展的REST接口
前面一节中,我们看到如何使用热插拔的视图创建一个REST接口。这里我们将使用一个Flask扩展叫做Flask-Restless。Flask-Restless是完全为了构建REST接口而开发的。它提供了一个简单的为使用SQLAlchemy创建的数据模型构建RESTful APIs的方法。这些生成的api以JSON格式发送和接收消息。准备
首先,需安装Flask-Restless扩展:$ pip install Flask-Restless
我们借用第四章的程序构建我们的应用,以此来包含RESTful API接口。
提示
如果views和handlers的概念不是很清楚,建议在继续阅读之前,先去阅读第四章。
怎么做
通过使用Flask-Restless是非常容易向一个SQLAlchemy模型新增RESTful API接口的。首先,需向应用新增扩展提供的REST API管理器,然后通过使用app对象创建一个实例:from flask_restless import APIManager manager = APIManager(app, flask_sqlalchemy_db=db)
之后,我们需要通过使用manager实例使能模型里的API创建。为此,需向views.py新增下面代码:
from my_app import manager manager.create_api(Product, methods=['GET', 'POST', 'DELETE']) manager.create_api(Category, methods=['GET', 'POST', 'DELETE'])
这将在Product和Category模型里创建GET,POST,DELETE这些RESTful API。通常,如果methods参数缺失的话,只支持GET方法。
原理
为了测试和理解这些是如何工作的,我们通过使用Python requests库发送一些请求:>>> import requests >>> import json >>> res = requests.get("http://127.0.0.1:5000/api/category") >>> res.json() {u'total_pages': 0, u'objects': [], u'num_results': 0, u'page': 1}
译者注
res.json()可能会从出错,可使用res.text
我们发送了一个GET请求去获取类别列表,但是现在没有记录。来看一下商品:
>>> res = requests.get('http://127.0.0.1:5000/api/product') >>> res.json() {u'total_pages': 0, u'objects': [], u'num_results': 0, u'page': 1}
我们发送了一个GE
4000
T请求去获取商品列表,但是没有记录。现在让我们创建一个商品:
>>> d = {'name': u'iPhone', 'price': 549.00, 'category':{'name':'Phones'}} >>> res = requests.post('http://127.0.0.1:5000/api/product', data=json.dumps(d), headers={'Content-Type': 'application/json'}) >>> res.json() {u'category': {u'id': 1, u'name': u'Phones'}, u'name': u'iPhone', u'company': u'', u'price': 549.0, u'category_id': 1, u'id': 2, u'image_path': u''}
我们发送了一个POST请求去创建一个商品。注意看请求里的headers参数。每个发给Flask-Restless的POST请求都应该包含这个头。现在,我们再一次搜索商品列表:
>>> res = requests.get('http://127.0.0.1:5000/api/product') >>> res.json() {u'total_pages': 1, u'objects': [{u'category': {u'id': 1, u'name': u'Phones'}, u'name': u'iPhone', u'company': u'', u'price': 549.0, u'category_id': 1, u'id': 1, u'image_path': u''}], u'num_results': 1, u'page': 1}
我们可以看到新创建的商品已经在数据库中了。
同样需要注意的是,查询结果默认已经分好页了,这是优秀的API的标识之一。
更多
自动创建RESTful API接口非常的酷,但是每个应用都需要一些自定义,验证,处理业务的逻辑。这使得使用preprocessors和postprocessors成为可能。从名字可以看出,preprocessors会在请求被处理前运行,postprocessors会在请求处理完,发送给应用前运行。它们被定义在create_api()中,做为请求类型(GET,POST等)映射,并且作为前处理程序或后处理程序的方法列表,用于处理指定的请求:
manager.create_api( Product, methods=['GET', 'POST', 'DELETE'], preprocessors={ 'GET_SINGLE': ['a_preprocessor_for_single_get'], 'GET_MANY': ['another_preprocessor_for_many_get'], 'POST': ['a_preprocessor_for_post'] }, postprocessors={ 'DELETE': ['a_postprocessor_for_delete'] } )
单个或多个记录都可以调用GET,PUT,PATCH方法;但是它们各有两个变体(variants)。举个例子,前面的代码里,对于GET请求有GET_SINGLE和GET_MANY。preprocessors和postprocessors对于各自请求接收不同的参数,然后执行它们,并且没有返回值。参见
https://flask-restless.readthedocs.org/en/latest/了解更多细节。
译者注
对preprocessor和postprocessors的理解,参见
http://flask-restless.readthedocs.io/en/stable/customizing.html#request-preprocessors-and-postprocessors
创建一个SQLAlchemy-independent REST API
在前一小节中,我们看到了如何使用依赖于SQLAlchemy的扩展创建一个REST API接口。现在我们将使用一个名为Flask-Restful的扩展,它是在Flask可插拔视图上编写的,并且独立于ORM。准备
首先,安装扩展:$ pip install Flask-Restful
我们将修改前面的商品目录应用,通过使用这个扩展增加一个REST接口。
怎么做
通常,首先要修改应用的配置,看起来像这样:from flask_restful import Api api = Api(app)
这里,app是我们应用的对象/实例。
接下来,在views.py里创建API。在这里,我们将尝试理解API的框架,更详细的实现在下一小节里:
from flask_restful import Resource from my_app import api class ProductApi(Resource): def get(self, id=None): # Return product data return 'This is a GET response' def post(self): # Create a new product return 'This is a POST response' def put(self, id): # Update the product with given id return 'This is a PUT response' def delete(self, id): # Delete the product with given id return 'This is a DELETE response'
前面的API结构是很容易理解的。看下面代码:
api.add_resource(ProductApi, '/api/product', '/api/product/<int:id>')
这里,我们为ProductApi创建路由,我们可以根据需要指定多条路由。
原理
我们将使用Python requests库在看这些是如何工作的,就像前一小节那样:>>> import requests >>> res = requests.get('http://127.0.0.1:5000/api/product') >>> res.json() u'This is a GET response' >>> res = requests.post('http://127.0.0.1:5000/api/product') >u'This is a POST response' >>> res = requests.put('http://127.0.0.1:5000/api/product/1') u'This is a PUT response' >>> res = requests.delete('http://127.0.0.1:5000/api/product/1') u'This is a DELETE response'
在前面一小段代码中,我们看到了我们的请求被相应的方法处理了;从回复中可以确认这一点。
其他
确保在继续向下阅读之前先阅读完这一小节一个完整的REST API例子
这一小节,我们将上一小节的API框架改写为一个完整的RESTful API接口。准备
我们将使用上一小节的API框架作为基础,来创建一个完整的SQLAlchemy-independent RESTful API。尽管我们使用SQLAlchemy作为ORM来进行演示,这一小节可以使用任何ORM或者底层数据库进行编写。怎么做
下面的代码是Product模型完整的RESTful API接口。views.py看起来像这样:from flask_restful import reqparse parser = reqparse.RequestParser() parser.add_argument('name', type=str) parser.add_argument('price', type=float) parser.add_argument('category', type=dict)
前面的一小段代码,我们为希望在POST,PUT请求中解析出来的参数创建了parser。请求期待每个参数不是空值。如果任何参数的值是缺失的,则将使用None做为值。看下面代码:
class ProductApi(Resource): def get(self, id=None, page=1): if not id: products = Product.query.paginate(page, 10).items else: products = [Product.query.get(id)] if not products: abort(404) res = {} for product in products: res[product.id] = { 'name': product.name, 'price': product.price, 'category': product.category.name } return json.dumps(res)
前面的get方法对应于GET请求,如果没有传递id,将返回商品分好页的商品列表;否则,返回匹配的商品。看下面POST请求代码:
def post(self): args = parser.parse_args() name = args['name'] price = args['price'] categ_name = args['category']['name'] category = Category.query.filter_by(name=categ_name).first() if not category: category = Category(categ_name) product = Product(name, price, category) db.session.add(product) db.session.commit() res = {} res[product.id] = { 'name': product.name, 'price': product.price, 'category': product.category.name, } return json.dumps(res)
前面post()方法将在POST请求时创建一个新的商品。看下面代码:
def put(self, id): args = parser.parse_args() name = args['name'] price = args['price'] categ_name = args['category']['name'] category = Category.query.filter_by(name=categ_name).first() Product.query.filter_by(id=id).update({ 'name': name, 'price': price, 'category_id': category.id, }) db.session.commit() product = Product.query.get_or_404(id) res = {} res[product.id] = { 'name': product.name, 'price': product.price, 'category': product.category.name, } return json.dumps(res)
前面代码,通过PUT请求更新了一个已经存在的商品。这里,我们应该提供所有的参数,即使我们仅仅想更新一部分。这是因为PUT被定义的工作方式就是这样。如果我们想要一个请求只传递那些我们想要更新的参数,这应该使用PATCH请求。看下面代码:
def delete(self, id): product = Product.query.filter_by(id=id) product.delete() db.session.commit() return json.dumps({'response': 'Success'})
最后同样重要的是,DELETE请求将删除匹配上id的商品。看下面代码:
api.add_resource( ProductApi, '/api/product', '/api/product/<int:id>', '/api/product/<int:id>/<int:page>' )
上一句代码是我们的API可以容纳的所有URL的定义。
提示
REST API的一个重要方面是基于令牌的身份验证,它只允许有限和经过身份验证的用户能够使用和调用API。这将留给你自己探索。我们在第6章Flask认证中介绍的用户身份验证的基础知识,将作为此概念的基础。
代码下载链接:
https://pan.baidu.com/s/1o7GyZUi 密码:x9rw
http://download.csdn.net/download/liusple/10186764
完。
相关文章推荐
- [译]Flask Framework Cookbook-第四章 视图的使用
- [译]Flask Framework Cookbook-第一章 Flask配置
- [译]Flask Framework Cookbook-第二章 使用Jinja2模板
- [译]Flask Framework Cookbook-第三章 Flask中的数据模型
- [译]Flask Framework Cookbook-第五章 使用WTForms处理表单
- [译]Flask Framework Cookbook-第六章 Flask认证
- Play Framework Cookbook (play框架食谱...)3
- 使用flask 构建基本的 restful api
- Play Framework Cookbook (play框架食谱...)2
- Visual Studio .NET: The .NET Framework Black Book
- Python cookbook(数据结构与算法)找到最大或最小的N个元素实现方法示例
- Python cookbook(数据结构与算法)让字典保持有序的方法
- wxPython Cookbook (Chatper1)part 2
- [译]Redis Cookbook(2)
- MySQL Cookbook 学习笔记-02
- 《iOS5 programming cookbook》学习笔记1
- CAF(C++ actor framework)使用随笔(使用类去构建actor和使用的一些思路)
- 网站后端_Flask-第三方库.利用Flask-Socketio扩展构建实时流应用?
- wxPython Cookbook(Chatper2)Part 1
- 使用 Flask 和 AngularJS 构建博客 - 2