23、Flask实战第23天:Flask-Restful

Restful API规范

restful api是用于前端和后台进行通信的一套规范。使用这个规范可以让前后端开发变得更加轻松。

协议

  采用http或者https

数据传输格式

  数据之间传输的格式应该都使用json,而不是xml

url链接

  url链接中,不能有动词,只能有名词。并且对于一些名词,如果出现复数,那么应该在后面加s

  比如: 获取文章列表,应该使用/articles/, 而不应该使用/get_article/

HTTP请求方法

  GET: 从服务器上获取资源  (常用)

  POST:在服务器上新创建一个资源  (常用

  PUT:在服务器上更新资源。(客户端提供所有改变后的数据)

  PATCH:在服务器上更新资源。(客户端只提供需要改变的属性)

  DELETE: 从服务器上删除资源

状态码

状态码 原生描述 描述
200 OK 服务器成功响应客户端的请求
400 INVALID REQUEST 用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized 用户没有权限访问这个请求
403 Forbidden 因为某些原因禁止访问这个请求
404 NOT FOUND 用户发送的请求的url不存在
406 NOT Accept 用户请求不被服务器接收(比如服务器期望客户端发送某个字段,但没有发送)
500 Internal server error 服务器内部错误,比如出现了bug

Flask-Restful插件的基本使用

安装flask-restful插件

pip install flask-restful

定义Restful视图

如果使用flask-restful,那么定义视图函数的时候,就要继承自flask_restful.Resource类,然后再根据当前请求的method来定义相应的方法。比如期望客户端使用get方法发送请求,那么就定义一个get方法,期望客户端使用post方法发送请求,就定义一个post方法。类似于MethodView.

因为我们只定义了post方法,如果直接用浏览器去访问http://127.0.0.1:5000/login/(get方法),得不到我们想要的结果

这个时候我们就可以用一些测试工具,我这里使用的是Insomnia,同类的软件还有postman

url中也可以传递参数

url也可以是多个

 

参数验证

Flask-Restful插件提供了类似WTForm来验证提交的数据是否合法的包,叫做reqparse。以下是基本用法

parser = reqparse.RequestParser()
        parser.add_argument('username', type=str, required=True, help='用户名验证错误')
        args = parser.parse_args()

add_argument可以指定这个字段的名字,这个字段的数据类型等

default:默认值,如果这个参数没有值,那么将使用这个参数指定的值。
required:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。
type:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
choices:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
help:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
trim:是否要去掉前后的空格。

 其中的type,可以使用python自带的一些数据类型,也可以使用flask_restful.inputs下的一些特定的数据类型来强制转换。比如一些常用的:

url:会判断这个参数的值是否是一个url,如果不是,那么就会抛出异常。
regex:正则表达式。
date:将这个字符串转换为datetime.date数据类型。如果转换不成功,则会抛出一个异常。

 

操作演示

因为username字段添加了参数required=True,为必填项,因此如果客户端不传此字段,就会提示错误,这个错误提示就是我们自定的help--->"用户名验证错误"

 

...
from flask_restful import Api, Resource, reqparse, inputs

app = Flask(__name__)
app.config.from_object(config)
api = Api(app)

class RegisterView(Resource):
    def post(self):
        parser = reqparse.RequestParser()
        parser.add_argument('username', type=str, required=True, help='用户名字段验证错误')
        parser.add_argument('birthday', type=inputs.date, required=True, help='生日字段验证错误')
        parser.add_argument('gender', type=str, choices=('male', 'female'), help='性别字段验证错误')
        parser.add_argument('money', type=int, trim=True, default=0, help='金额字段验证错误')
        parser.add_argument('phone', type=inputs.regex(r'1[3458]\d{9}'), help='手机字段验证错误')
        parser.add_argument('blog', type=inputs.url, help='博客地址字段验证错误')
        args = parser.parse_args()
        print(args)
        return 'success'

api.add_resource(RegisterView, '/register/', endpoint='register')
...

 

Flask-Restful标准化返回参数

对于一个视图函数,我们可以指定好一些参数用于返回,在规范中要求:即使这个参数没有值也应该返回,返回一个None回去

...
from flask_restful import Api, Resource, fields, marshal_with

api = Api(app)


class ArticleView(Resource):
    resource_field = {     #先定义好返回哪些参数
        'name': fields.String,     #参数的数据类型
        'age': fields.String,
        'school': fields.String
    }

    @marshal_with(resource_field)  #利用marshal_with装饰器传入定义好的返回参数
    def get(self):
        return {}   就算这里返回个空字典,也会把定义好的参数返回

api.add_resource(ArticleView, '/article/', endpoint='article')

...
class ArticleView(Resource):
    resource_field = {
        'name': fields.String,
        'age': fields.String,
        'school': fields.String
    }

    @marshal_with(resource_field)
    def get(self):
        return {'name': 'heboan', 'age': 18}

...

当使用ORM模型或者自定义的的模型的时候,它会自动的获取模型中相应的字段,生成json数据,然后返回给客户端

class ProfileView(Resource):
    resource_fields = {
        'username': fields.String,
        'age': fields.Integer,
        'school': fields.String
    }

    @marshal_with(resource_fields)
    def get(self,user_id):
        user = User.query.get(user_id)
        return user

在get方法中,返回user的时候,flask_restful会自动的读取user模型上的username, age以及school属性。组装成一个json格式的字符串返回给客户端

 

重名属性

如果我们想把面向公众的字段名称不用与内部的属性名。使用attribute可以配置这种属性,比如现在想要返回user.school中的值,但是在返回给外面的时候,想以education返回回去,那么可以这样写:

resource_fields = {
    'education': fields.String(attribute='school')
}

 

默认值

在返回一些字段的时候,有时候可能没有值,那么这时候可以在指定fields的时候给定一个默认值,示例代码如下:

resource_fields = {
    'age': fields.Integer(default=18)
}

 

复杂的结构

有时候想要在返回的数据格式中,形成比较复杂的结构。那么可以使用一些特殊的字段来实现。比如要在一个字段中放置一个列表,那么可以使用fields.List,比如在一个字段下面又是一个字典,那么可以使用fields.Nested。以下将讲解下复杂结构的用法:

定义数据库结构:

user表,article表,tag表, 因为article与tag是多对多的关系,因此需要一个中间表来关联article_tag

from exts import db

class User(db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), nullable=False)


class Article(db.Model):
    __tablename__ = 'article'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

    author = db.relationship('User', backref='articles')
    tags = db.relationship('Tag', secondary='article_tag')


class Tag(db.Model):
    __tablename__ = 'tag'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    name = db.Column(db.String(50), nullable=False)


article_tag = db.Table(
    'article_tag',
    db.Column('article_id', db.Integer, db.ForeignKey('article.id')),
    db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)
models

插入测试数据

...
from models import User, Article, Tag

@app.route('/')
def index():
    user = User(username='heboan')
    tag_1 = Tag(name='Linux')
    tag_2 = Tag(name='Python')
    article = Article(title='python自动化运维', content='Life is short, I use python...')

    article.tags.append(tag_1)
    article.tags.append(tag_2)
    article.author = user

    db.session.add(article)
    db.session.commit()
    return '数据插入成功!'
视图

 当我们全部使用field.String

class ArticleView(Resource):
    resource_fields = {
        'title':fields.String,
        'content': fields.String,
        'author':fields.String,
        'tags':fields.String
    }

    @marshal_with(resource_fields)
    def get(self):
        article = db.session.query(Article).get(1)
        return article

api.add_resource(ArticleView, '/article/', endpoint='article')

使用field.Nested、field.List

class ArticleView(Resource):
    resource_fields = {
        'title':fields.String,
        'content': fields.String,
        'author':fields.Nested({   #字典里面嵌套字典使用field.Nested
           'username': fields.String
        }),
        'tags':fields.List(fields.Nested({  #tags是列表,使用field_List类型然后嵌套field.Nested
            'id': fields.Integer,
            'name': fields.String
        }))
    }

    @marshal_with(resource_fields)
    def get(self):
        article = db.session.query(Article).get(1)
        print(article.title)
        return article

api.add_resource(ArticleView, '/article/', endpoint='article')

在蓝图使用使用Flask-Restful

新建一个articles.py

from flask import Blueprint
from flask_restful import Api, Resource, fields, marshal_with
from exts import db
from models import Article

article_bp = Blueprint('article', __name__, url_prefix='/article')
api = Api(article_bp)   #这里使用article_bp了


class ArticleView(Resource):
    resource_fields = {
        'title':fields.String,
        'content': fields.String,
        'author':fields.Nested({
           'username': fields.String
        }),
        'tags':fields.List(fields.Nested({
            'id': fields.Integer,
            'name': fields.String
        }))
    }

    @marshal_with(resource_fields)
    def get(self):
        article = db.session.query(Article).get(1)
        print(article.title)
        return article

api.add_resource(ArticleView, '/1/', endpoint='article')

主程序注册此蓝图

from article import article_bp
...

app.register_blueprint(article_bp)

 

Flask-Restful渲染模板

先看看直接渲染模板是什么效果

class HelloView(Resource):
    def get(self):
        return render_template('hello.html')

api.add_resource(HelloView, '/hello/', endpoint='hello')

可以发现这样渲染出来的页面并不是我们想要的。要想使用Flask-Restful渲染页面还需要定义一个out_html函数

@api.representation('text/html')
def out_html(data, code, headers):
    resp = make_response(data)
    return resp


class HelloView(Resource):
    def get(self):
        return render_template('hello.html')

api.add_resource(HelloView, '/hello/', endpoint='hello')

其中那个data就是页面的内容

 

posted @ 2018-07-29 16:23  sellsa  阅读(2125)  评论(0编辑  收藏  举报