Web后端学习笔记 Flask (12)Restful

Restful API规范

restful API是用于在前端与后台进行通信的一套规范,使用这个规范可以让前后端开发变得更加轻松:

1. 协议:http或者https

2. 数据传输格式:json

3. url链接:url链接中,不能有动词,只能有名词,并且对于一些名词,如果出现复数,就用复数的形式

4. http请求方法:

GET:在服务器上获取资源

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

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

PATCH:在服务器上更新资源(只需提供需要改变的信息)

DELETE:从服务器上删除资源

举例:
GET/users/ :获取所有用户

POST/user/:创建一个用户

GET/user/id/:根据id获取一个用户

PUT/user/id/:更新某个id的用户信息(需要提供用户的所=所有信息)

PATCH/user/id/:更新某个id的用户信息(只需提供需要修改的数据)

DELETE/user/id/:删除一个用户

状态码:

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

Restful插件:

通过 pip install flask-restful安装flask-restful插件

定义restful视图函数:如果使用flask-restful,那么定义视图函数的时候,需要继承自flask-restful.Resource这个类,然后再根据当前请求的method来定义响应的方法。类似于类视图的功能,可以分别定义get方法与post方法。

工具:postman做接口测试

from flask import Flask, render_template, request
from flask_restful import Api, Resource
import config

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

api = Api(app=app)


class LoginView(Resource):
    def post(self):
        return {"username": "tom"}


api.add_resource(LoginView, '/login/', endpoint='login')


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

注意事项:

1. endpoint是用来给url_for反转url的时候使用的。如果不写endpoint,那么将会使用视图的名字的小写来作为endpoint。

2. add_resource的第二个参数是访问这个视图函数的url,这个url和之前的route一样,也可以传递参数,并且这个方法还可以传递多个url来指定这个视图函数。

class LoginView(Resource):
    # 如果add_resource绑定了多个url,如果没有传参,可以将视图函数中的参数设置为None
    # 此时即使url中没有传递参数,也不会报错
    def post(self, username=None):    
        return {"username": username}


api.add_resource(LoginView, '/login/<username>/', '/register/', endpoint='login')

注意事项:
1. 如果向返回json数据,那么就使用flask-restful,如果向渲染模板,那么好是采用视图函数或者类视图的方法。

2. url还是和之前的一样,可以传递参数。不同的是视图函数的是,可以绑定多个视图函数。

flask-restful参数

flask-restful插件提供了类似于WTForms来验证提交的数据是否合法,叫做reparse

class LoginView(Resource):
    # 如果add_resource绑定了多个url,如果没有传参,可以将视图函数中的参数设置为None
    # 此时即使url中没有传递参数,也不会报错
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument("username", type=str, help="用户名验证错误")
        parse.add_argument("password", type=str, help="密码验证错误")
        args = parse.parse_args()
        return {"username": args.get("username"), "password": args.get("password")}


api.add_resource(LoginView, '/login/', endpoint='login')

在不传参数的时候,返回的结果:

在post方法中传递参数之后,可以看到返回的结果:

add_argument可以指定的字段:
1. default: 当没有传递相应参数的时候,设置这个参数的默认值

2. required:是否必须传递这一参数, 布尔值

3. type:指定提交的参数的类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。

4. choices:选项,提交上来的值只有满足这个选项中的值才能验证通过

5. help:错误信息,如果验证失败,将会使用这个参数指定的值作为错误信息

6. trim:是否要取出前后的空格

type字段的值,可以使用python内置的数据类型,也可以使用flask-restful.input中的类型,例如:

url:判断传入的参数是否为url

regex:正则表达式

date:将这个字符串转换为datetime.date,如果转换失败,则会抛出异常

from flask import Flask, render_template, request
from flask_restful import Api, Resource, reqparse, inputs
import config

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

api = Api(app=app)


class LoginView(Resource):
    # 如果add_resource绑定了多个url,如果没有传参,可以将视图函数中的参数设置为None
    # 此时即使url中没有传递参数,也不会报错
    def post(self):
        parse = reqparse.RequestParser()
        parse.add_argument("username", type=str, help="用户名验证错误", trim=True)
        parse.add_argument("password", type=str, help="密码验证错误", trim=True)
        parse.add_argument("age", type=int, help="年龄验证错误", trim=True)
        parse.add_argument("render", type=str, choices=["female", "male"], trim=True, help="性别填写错误")
        parse.add_argument("link", type=inputs.url, trim=True, help="url填写错误")
        parse.add_argument("phone", type=inputs.regex(r"1[3578]\d{9}"), trim=True, help="电话号码格式错误")
        args = parse.parse_args()
        return {"username": args.get("username"),
                "password": args.get("password"),
                "age": args.get("age"),
                "render": args.get("render")}


api.add_resource(LoginView, '/login/', endpoint='login')


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

Flask-restful标准:
输出字段: 

对于一个视图函数,可以指定好一些字段用于返回。在可以使用ORM模型或者自定义模型的时候,它会自动获取模型中的相应字段,生成json数据,然后再返回给客户端。这其中需要导入flask_restful.marshal_with装饰器,并且需要写一个字典,来指示需要返回的字段,以及该字段的数据类型。 

from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config

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

api = Api(app=app)


class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return {"title": "xxx", "content": "yyy"}


api.add_resource(ArticleView, '/articles/', endpoint="articles")


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

有的字段没有值,在return的字典中没有写,也会返回为null值:

class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return {"title": "xxx"}    # 即使此时content字段没有值,也会返回为null,这样就保证了接口比较规范


api.add_resource(ArticleView, '/articles/', endpoint="articles")

返回的结果:

这种方式还有一个好处,就是如果相应的数据是一个模型,则可以直接返回模型的对象即可,而不用构建字典

from flask import Flask, render_template, request
from flask_restful import Api, Resource, fields, marshal_with
import config

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

api = Api(app=app)


class Article(object):
    def __init__(self, title, content):
        self.title = title
        self.content = content


article = Article(title="gone with wind", content="xsddkkjsdv")


class ArticleView(Resource):
    resource_fields = {
        "title": fields.String,
        "content": fields.String
    }    # 指定需要返回的字段

    @marshal_with(resource_fields)
    def get(self):
        return article    # 可以根据article对象中的属性自动构造字典,返回字典


api.add_resource(ArticleView, '/articles/', endpoint="articles")


@app.route('/')
def index():
    return render_template("html/index.html")


if __name__ == '__main__':
    app.run()

复杂结构:

返回的字段的值任然是一个模型,则需要使用Nested()方法再定义一个字典,返回相应的字段

# 定义一个restful视图
class ArticleView(Resource):
    resource_field = {
        "title": fields.String,
        "content": fields.String,
        "author": fields.Nested({     # User类型
            "username": fields.String,
            "email": fields.String
        }),
        "tags": fields.Nested({
            "name": fields.String
        })
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

使用field.Nested()之后,返回的的数据如下所示:

重命名属性:

在读取模型中的某个字段之后,需要用一个新的名字将其返回,可以使用attribute属性实现:

例如,需要将article中的title属性返回,并赋予一个新的属性名:

# 定义一个restful视图
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改变属性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User类型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags")
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

返回结果:

默认值:

在resource_field中定义的一些字段,如果模型中没有与之对应的字段,则该字段的值会被设置为null返回。除此之外,还可以给没有的字段设置默认值,最后返回的该字段的值就是默认值,而不是null.

class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改变属性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User类型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, '/articles/<article_id>/', endpoint="article")

此时返回的read_cnt字段的值就是默认值100:

Flask-restful细节:

1. flask-restful结合蓝图使用

在flask-restful中使用蓝图,需要将蓝图定义到一个单独的文件即可,此时在定义api的时候,以蓝图为参数,而不用再以app作为定义api的参数,在主app主注册蓝图:

例如,定义蓝图的文件,articleViews.py

# -*- coding: utf-8 -*-

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

article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp)      # api


# 定义一个restful视图
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改变属性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User类型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


api.add_resource(ArticleView, "/<article_id>/", endpoint="article")

在主app.py文件中注册蓝图:

from flask import Flask, render_template, request
import config
from exts import db
from models import User, Article, Tag
from articleViews import article_bp

app = Flask(__name__)
app.config.from_object(config)
db.init_app(app=app)

app.register_blueprint(article_bp)   # 注册蓝图


@app.route('/')
def index():
    user = User(username="kong", email="123tg@gmail.com")
    article = Article(title="gone", content="hover")
    article.author = user
    tag1 = Tag(name="java")
    tag2 = Tag(name="python")
    article.tags.extend([tag1, tag2])
    db.session.add(article)
    db.session.commit()
    return "Hello"


if __name__ == '__main__':
    app.run()

其中models文件为定义ORM模型的文件:models.py

# -*- coding: utf-8 -*-
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)
    email = db.Column(db.String(50), nullable=False)


# tag和article之间是多对多的关系
article_tag = db.Table("article_tag",
                       db.Column("article_id", db.Integer, db.ForeignKey("article.id"), primary_key=True),
                       db.Column("tag_id", db.Integer, db.ForeignKey("tag.id"), primary_key=True),
                       )


class Article(db.Model):
    __tablename__ = "article"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text)

    author_id = db.Column(db.Integer, db.ForeignKey("user.id"))

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


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





2. flask-restful渲染模板

falsk-restful规定的数据交互的形式是JSON,所以即使返回渲染的模板(及render_templlate(html文件)),此时在浏览器中显示的还是字符串形式的,而不会当作html代码被浏览器解析:此时需要借助一个装饰器,@api.representation("text/html")来定义一个函数,在这个函数中对html代码进行封装,再进行返回

# -*- coding: utf-8 -*-

from flask import Blueprint, render_template, make_response
from flask_restful import Resource, fields, marshal_with, Api
from models import User, Article, Tag
from exts import db

article_bp = Blueprint("article", __name__, url_prefix="/article")
api = Api(article_bp)      # api


@api.representation("text/html")      # 保证restful也能够通过render_template渲染模板
def output_html(data, code, headers):
    # data是字符串形式的html文本
    resp = make_response(data)    # 构造Response对象
    return resp


# 定义一个restful视图
class ArticleView(Resource):
    resource_field = {
        "article_title": fields.String(attribute="title"),    # 改变属性的名字
        "article_content": fields.String(attribute="content"),
        "article_author": fields.Nested({     # User类型
            "username": fields.String,
            "email": fields.String
        }, attribute="author"),
        "article_tags": fields.Nested({
            "name": fields.String
        }, attribute="tags"),
        "read_cnt": fields.Integer(default=100)
    }

    @marshal_with(resource_field)
    def get(self, article_id):
        article = db.session.query(Article).filter(Article.id == article_id).first()
        return article


class ArticleListView(Resource):
    def get(self):
        return render_template("html/list.html")
        # 此时,restful返回的依然是字符串形式的html文本,不能被浏览器解析


api.add_resource(ArticleView, "/<article_id>/", endpoint="article")
api.add_resource(ArticleListView, '/list/', endpoint="list")

 

posted @ 2020-04-24 12:36  Alpha205  阅读(53)  评论(0编辑  收藏  举报