zl程序教程

您现在的位置是:首页 >  其他

当前栏目

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

博客restfulAPI 构建 Flask 之二 33 使用
2023-09-27 14:28:47 时间
用 Flask 来写个轻博客 (1) — 创建项目 用 Flask 来写个轻博客 (2) — Hello World! 用 Flask ...

使用 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. 前提还可能需要满足身份鉴权(这个需求, 我们后期再实现)


实现 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() .


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 来过滤若干条满足要求的数据记录.


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))
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 标签之间的文本内容合并.