您的位置:首页 > 其它

用 Flask 来写个轻博客 (33) — 使用 Flask-RESTful 来构建 RESTful API 之二

2017-01-02 15:34 721 查看

目录

目录

前文列表

扩展阅读

构建 RESTful Flask API
定义资源路由

格式化输出

前文列表

用 Flask 来写个轻博客 (1) — 创建项目

用 Flask 来写个轻博客 (2) — Hello World!

用 Flask 来写个轻博客 (3) — (M)VC_连接 MySQL 和 SQLAlchemy

用 Flask 来写个轻博客 (4) — (M)VC_创建数据模型和表

用 Flask 来写个轻博客 (5) — (M)VC_SQLAlchemy 的 CRUD 详解

用 Flask 来写个轻博客 (6) — (M)VC_models 的关系(one to many)

用 Flask 来写个轻博客 (7) — (M)VC_models 的关系(many to many)

用 Flask 来写个轻博客 (8) — (M)VC_Alembic 管理数据库结构的升级和降级

用 Flask 来写个轻博客 (9) — M(V)C_Jinja 语法基础快速概览

用 Flask 来写个轻博客 (10) — M(V)C_Jinja 常用过滤器与 Flask 特殊变量及方法

用 Flask 来写个轻博客 (11) — M(V)C_创建视图函数

用 Flask 来写个轻博客 (12) — M(V)C_编写和继承 Jinja 模板

用 Flask 来写个轻博客 (13) — M(V)C_WTForms 服务端表单检验

用 Flask 来写个轻博客 (14) — M(V)C_实现项目首页的模板

用 Flask 来写个轻博客 (15) — M(V)C_实现博文页面评论表单

用 Flask 来写个轻博客 (16) — MV(C)_Flask Blueprint 蓝图

用 Flask 来写个轻博客 (17) — MV(C)_应用蓝图来重构项目

用 Flask 来写个轻博客 (18) — 使用工厂模式来生成应用对象

用 Flask 来写个轻博客 (19) — 以 Bcrypt 密文存储账户信息与实现用户登陆表单

用 Flask 来写个轻博客 (20) — 实现注册表单与应用 reCAPTCHA 来实现验证码

用 Flask 来写个轻博客 (21) — 结合 reCAPTCHA 验证码实现用户注册与登录

用 Flask 来写个轻博客 (22) — 实现博客文章的添加和编辑页面

用 Flask 来写个轻博客 (23) — 应用 OAuth 来实现 Facebook 第三方登录

用 Flask 来写个轻博客 (24) — 使用 Flask-Login 来保护应用安全

用 Flask 来写个轻博客 (25) — 使用 Flask-Principal 实现角色权限功能

用 Flask 来写个轻博客 (26) — 使用 Flask-Celery-Helper 实现异步任务

用 Flask 来写个轻博客 (27) — 使用 Flask-Cache 实现网页缓存加速

用 Flask 来写个轻博客 (29) — 使用 Flask-Admin 实现后台管理 SQLAlchemy

用 Flask 来写个轻博客 (30) — 使用 Flask-Admin 增强文章管理功能

用 Flask 来写个轻博客 (31) — 使用 Flask-Admin 实现 FileSystem 管理

用 Flask 来写个轻博客 (32) — 使用 Flask-RESTful 来构建 RESTful API 之一

扩展阅读

使用 Flask 设计 RESTful APIs

快速入门 — Flask-RESTful 0.3.1 documentation

Python爬虫常用之HtmlParser

构建 RESTful Flask API

为什么要构建 RESTful API ?

对于一个 blog application 而言, 其实完全可以不用到 restful api 也能满足日常所需. 加入 restful api 的唯一目标就是加强该项目的可扩展性, 为后期所要实现的诸如: 博客迁移/数据备份/功能扩展 提供统一且可靠的接口.

定义资源路由

首先我们要有一个大概的需求, 如果希望通过 HTTP 请求来完成对服务端资源的操作, 我们需要解决那些问题?

1. 首先要定位到该资源

2. 告诉服务端我要对该资源做那种操作

3. 前提还可能需要满足身份鉴权(这个需求, 我们后期再实现)

安装 Flask-RESTful

pip install Flask-Restful
pip freeze > requirements.txt


初始化 restful_api 对象

vim jmilkfansblog/extensions.py

from flask.ext.restful import Api
...

#### Create the Flask-Restful's instance
restful_api = Api()


实现 PostApi 资源类

我们将 posts 博客文章定义为一类资源, 只有定义了资源并且对外公开后, 才能被外部所调用.

vim jmilkfansblog/controllers/flask_restful/posts.py

from flask.ext.restful import Resource

class PostApi(Resource):
"""Restful API of posts resource."""

def get(self, post_id=None):
"""Can be execute when receive HTTP Method `GET`.
Will be return the Dict object as post_fields.
"""
return {'hello': 'world'}


NOTE 1: jmilkfansblog/controllers/flask_restful 会作为一个包, 所以要记得创建 __init__.py 文件, 否则无法作为导入路径.

NOTE 2: 每个 REST 资源类都需要继承 flask_restful 的 Resource 类. 其所有的子类都可以通过定义同名实例函数来将该函数绑定到 HTTP Methods 中. EG. GET <==> get(), 放接受定位到资源的 HTTP GET 方法时, 就会执行该资源类的实例函数 get() .

将 restful_api 对象注册到 app 对象中

vim jmilkfansblog/__init__.py

from jmilkfansblog.extensions import restful_api
from jmilkfansblog.controllers.flask_restful.posts import PostApi
...

def create_app(object_name):
...
#### Init the Flask-Restful via app object
# Define the route of restful_api
restful_api.add_resource(
PostApi,
'/api/posts')
restful_api.init_app(app)


NOTE 1: 在
restful_api.add_resource()
指定了资源类 PostApi 所对应的资源名称为 posts, 访问路由为 /api/posts, 这样才完成了对一个资源的完整定义.

NOTE 2: 同时再结合 PostApi 中的 get() 会自动的适配到 HTTP GET 方法, 这样就解决了我们之前所提出的 2 个问题.

现在我们引入一个新的问题, 通过上述定义的 get() 方法我们基本可以获取到数据库 posts 表中的所有记录(当然现在还没有连接数据库操作), 那么如果我只需要获取其中的某一条指定的记录呢?

这里需要在请求中指定 id 来完成单一的定位, 或者也可以传递一个 filters 来过滤若干条满足要求的数据记录.

为资源 posts 添加多条路由

vim jmilkfansblog/__init__.py

def create_app(object_name):
...
#### Init the Flask-Restful via app object
# Define the route of restful_api
restful_api.add_resource(
PostApi,
'/api/posts',
'/api/posts/<string:post_id>',
endpoint='restful_api_post')


NOTE: add_resource() 允许为同一个资源类绑定多条路由,
'/api/posts/<string:post_id>'
表示可以访问 posts 这一类资源中某一个 post_id 一致的资源对象.

为 get() 方法添加 post_id 形参数

vim jmilkfansblog/controllers/flask_restful/posts.py

class PostApi(Resource):
"""Restful API of posts resource."""

def get(self, post_id=None):
"""Can be execute when receive HTTP Method `GET`.
Will be return the Dict object as post_fields.
"""

if post_id:
return {'post_id': post_id}
return {'hello': 'world'}


格式化输出

在上一篇博文中提到, REST 约束要求我们使用一致的数据包装形式来进行响应, 所以我们需要实现一致的格式化功能. 本项目使用最常见的 JSON 格式.

Flask-Restful 的格式化输出, 首先需要定义出一个类似模板的 Dict 类型对象

其 keys 是资源对应的 Model 对象所拥有且需要输出的字段名, values 则声明了该字段的值以何种类型转换并输出. 然后把该字典模板传给装饰器
@marshal_with
并装饰到所有资源类中需要返回数据到客户端的实例方法中. 如此之后,实例方法在返回数据之前都会按照该模板将数据进行格式化转换.

注意: 字典模板的 keys 最好与 models 模块中定义的字段名相同, 否则无法自动完成字典模板与 Model 对象的匹配.

vim jmilkfansblog/controllers/flask_restful/posts.py

from flask.ext.restful import Resource, fields, marshal_with
from jmilkfansblog.controllers.flask_restful import fields as jf_fields

...

# String format output of tag
nested_tag_fields = {
'id': fields.String(),
'name': fields.String()}

# String format output of post
post_fields = {
'author': fields.String(attribute=lambda x: x.user.username),
'title': fields.String(),
'text': jf_fields.HTMLField(),
'tags': fields.List(fields.Nested(nested_tag_fields)),
'publish_date': fields.DateTime(dt_format='iso8601')}

class PostApi(Resource):
"""Restful API of posts resource."""

@marshal_with(post_fields)
def get(self, post_id=None):
"""Can be execute when receive HTTP Method `GET`.
Will be return the Dict object as post_fields.
"""

if post_id:
return {'post_id': post_id}
return {'hello': 'world'}


NOTE 1: 这里需要使用到 flask_restful.fields, 其提供了绝大多数常用的格式类型定义, 具体格式类型列表可以查看官方文档. 当然, 我们也可以自定义一些格式类型, 例如
jf_fields.HTMLField()


NOTE 2: tags 和 author 字段并不存在与 posts 表中, 返回该字段是为了遵守 REST 的约束之一, RESTful API 返回的数据应该尽量满足客户端的需求. 所以我们一般会将表与表之前含有关联关系的字段都一同返回. 格式类型
List
可以接受另外一个格式化输出字典模板对象. 类似于 字典内嵌套字典的格式.

自定义 fields 类型

因为 posts 表中的 text 字段内容是一系列的 HTML 字符串(由 CKEditor 产生), 这些 HTML 字符串是不允许被 RESTful API 返回的, 因为要满足 REST 的约束之一, 服务端不参与用户界面表现层的业务逻辑(即 HTML 代码), 所以我们需要将该字段值中的 HTML 标签过滤掉.

vim jmilkfansblog/controllers/flask_restful/fields.py

from HTMLParser import HTMLParser
from flask.ext.restful import fields

class HTMLField(fields.Raw):
"""Define a new fields for filter the HTML tags string."""

def format(self, value):
return strip_tags(str(value))

class HTMLStripper(HTMLParser):
"""HTML Parser of Stripper."""

def __init__(self):
self.reset()
self.fed = []

def handle_data(self, data_object):
self.fed.append(data_object)

def get_data(self):
return ''.join(self.fed)

def strip_tags(html):
"""Filter the tags string of HTML for data object of Restful api."""

stripper = HTMLStripper()
stripper.feed(html)

return stripper.get_data()


NOTE 1: 在 fields 模块中通过继承了 flask_restful.fields.Raw 类, 实现了新的格式类型 HTMLField .

NOTE 2: 使用 HTTPParser 来实现 HTML 解析, 重载 handle_data 方法用于将 HTML 标签之间的文本内容合并.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: