Flask中flask-restful的应用(十一)

      REST又被称为表征性状态传输,是指在客户端与服务端之间传输信息的一种方式,在WEB的应用程序

中,一般都是基于HTTP的REST服务,这样的好处是可以使用应用层的协议来方便的实现客户端与服务端

之间的基本通信。REST它与语言无关,它更多的是制定了客户端与服务端之间的交互规则,即使在目前主

流的微服务架构中,也是使用了轻量级的通信方式,也就是基于HTTP的REST的交互方式,在这样的一种

交互方式中,一般的数据格式会使用JSON或者是XML的。在HTTP的协议中,客户端与服务端的之间的交互,

首先不会去太多的关心底层网络传输层的协议,更多关注的是应用层客户端与服务端之间的交互请求。见如下:

客户端向服务端发送Request请求后,客户端会响应回复给客户端,在这样的一个交互模式中,由于HTTP的协议

它是无状态的协议,所以如果服务端需要记住客户端的某些信息的话,需要应用到cookie或者是session的的技术。

但是在这里我们并不需要刻意的去关注这些技术,更多需要关心的是当客户端发送请求后,由于网络超时等异常

情况可能会导致客户端的请求堵塞,基于同步通信的模式始终存在这样的缺陷。当然在REST中,还有如下几点要

求,分别是:

1、接口是基于资源的。

2、服务端返回的数据基本都是JSON格式的数据,客户端需要关键的数据需要对JSON序列化处理后得到自己想要

的信息。

3、API提供的每个资源信息,都必须有统一的格式,比如要么都必须是JSON格式或者是XML格式,不能是这个是

JSON格式另外一个是XML的格式。

       在Flask的应用程序中,首先需要安装flask-restful,安装的命令为:

pip  install flask-restful 

在这里首先简单的写一个 "Hello World"的GET请求,请求地址是/index/,见实现的源码:

#!/usr/bin/env python
# -*-coding:utf-8 -*-


from flask import  Flask,jsonify
from flask_restful import  Api,Resource


app=Flask(__name__)
api=Api(app)

class IndexView(Resource):
    def get(self):
        return {'Hello':'World'}

api.add_resource(IndexView,'/index/',endpoint='index')

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

 执行应用程序,然后在浏览器中访问http://localhost:5000/index/,就会输出基于JSON格式的信息,见如下:

当然作为一个接口来说,它是有请求参数的,比如一个登录的接口,它的请求参数是username和password,下面

继续实现POST的请求方法,见源码:

#!/usr/bin/env python
# -*-coding:utf-8 -*-


from flask import  Flask,jsonify
from flask_restful import  Api,Resource,reqparse


app=Flask(__name__)
api=Api(app)

class IndexView(Resource):
    def get(self):
        return {'Hello':'World'}


class LoginView(Resource):
    def get(self):
        return {'status':0,'msg':'ok','data':'this is a  login page'}

    def post(self):
        parser=reqparse.RequestParser()
        parser.add_argument('username',type=str,help='登录账户不能为空',required=True)
        parser.add_argument('password',type=str,help='账户密码不能为空',required=True)
        return jsonify(parser.parse_args())

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

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

 在postman中模拟POST的请求,见POSTMAN的请求截图:

在如上的代码中,关于add_argument()的方法的形式参数都是有很严格的讲究的,主要为:

type:规定了请求参数的类型

required:如果是True的话,就意味着参数是必须填写的

htlp:指参数验证没有通过后返回的错误信息

choices:对一个请求参数指定它的范围,如性别,只能使用男或者女,如果是其他的信息,就会不允许

这里主要看三个维度,分别是请求参数类型,请求参数是否为空,和chioces的校验,见修改后的源码:

class LoginView(Resource):
    def get(self):
        return {'status':0,'msg':'ok','data':'this is a login page'}

    def post(self):
        parser=reqparse.RequestParser()
        parser.add_argument('username', type=str, required=True, help='您的用户名验证错误')
        parser.add_argument('password',type=str,help='账户密码不能为空')
        parser.add_argument('age',type=int,help='年龄必须为正正数')
        parser.add_argument('sex',type=str,help='性别只能是男或者女',choices=['',''])
        args=parser.parse_args()
        return jsonify(args)

先看用户名为空返回的错误信息:

接着验证参数的数据类型,请求参数年龄为字符串,看后台的判断信息:

最后校验choices,依据代码,性别只能是男或者是女,那么这里把请求参数为false,看程序的验证:

         在flask-restful中,也可以提前定义好接口返回的参数,比如接口返回username,password以及一些想要返回的信息,

这里就会使用到marshall_with,比如这里写一个简单的案例,期望返回业务状态码,msg消息,和data里面的数据,见

实现的源码:

#!/usr/bin/env python
# -*-coding:utf-8 -*-


from flask import  Flask,jsonify,Response,make_response,abort,url_for
from flask_restful import  Api,Resource,reqparse,fields,marshal_with


app=Flask(__name__)
api=Api(app)


class Login(object):
    def __init__(self,username,password,isLogin):
        self.username=username
        self.password=password
        self.isLogin=isLogin

login=Login('wuya','admin',True)

class LoginView(Resource):
    resources={
        'username':fields.String,
        'password':fields.String,
        'isLogin':fields.Boolean
    }

    @marshal_with(resources)
    def get(self):
        return login

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

#请求上下文
with app.test_request_context():
    print(url_for('login'))

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

在浏览器中再次访问 http://127.0.0.1:5000/login/,就会显示Login实例化后的对象的实例,见如下截图:

       下面就结合flask-restful写一个具体的案例,主要写一个图书管理系统的API,主要如下:

显示所有的图书信息    查询某个书的具体信息    修改书的信息   删除书的信息,在一个字典里面初始化部分需要测试的数据,具体实现的代码如下:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

from flask import  *
from flask_httpauth import  HTTPBasicAuth
from flask_restful import  reqparse,Api,Resource

app=Flask(__name__)
api=Api(app)
auth=HTTPBasicAuth()

books=[
    {
        'id':1,
        'author':'无涯',
        'name':'Python自动化测试实战',
        "done":True
    },
    {
        'id': 2,
        "aurhor":"无涯",
        'name': 'Python测试开发实战',
        "done":False
    }
]

@app.route('/api/v1/books',methods=['GET'])
def get_books():
    '''返回所有的书籍'''
    return jsonify({'books':books})


@app.route('/api/v1/books/<int:book_id>',methods=['GET'])
def get_book(book_id):
    '''依据ID返回具体的书的信息'''
    book=list(filter(lambda t: t['id'] == book_id, books))
    if len(book)==0:
        abort(404)
    return jsonify({'book':book[0]})

@app.errorhandler(404)
def not_found(error):
    '''对404情况的处理,希望错误得到的是404的错误'''
    return make_response(jsonify({'error':'Not Found'}),404)



@app.route('/api/v1/books',methods=['POST'])
def create_book():
    if not request.json or not 'author' in request.json:
        abort(400)
    book={
        'id':books[-1]['id']+1,
        # 'author':request.json['author'],
        'author':request.json.get('author'),
        'name':request.json.get('name'),
        'done':False
    }
    books.append(book)
    return jsonify({'book':book}),201

@app.route('/api/v1/books/<int:book_id>',methods=['POST'])
def update_book(book_id):
    book=list(filter(lambda t: t['id'] == book_id, books))
    if len(book)==0:
        abort(404)
    if not request.json:
        abort(400)
    if 'author' not in  request.json and 'name'  not in  request.json:
        abort(400)
    if 'done' in request.json and type(request.json['done']) is not bool:
        abort(400)
    book[0]['author']=request.json.get('author',book[0]['author'])
    book[0]['name']=request.json.get('name',book[0]['name'])
    book[0]['done']=request.json.get('done',book[0]['done'])
    return jsonify({'result':book[0]})


@app.route('/api/v1/books/<int:book_id>',methods=['DELETE'])
def del_book(book_id):
    book=list(filter(lambda t:t['id']==book_id,books))if len(book)==0:
        abort(404)
    books.remove(book[0])
    return jsonify({'result':True})

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

 

在上面的案例代码中并没有增加认证,在认证中主要可以分为基本认证,常规认证,和基本认证,这里主要使用基本认证,也

就是说,需要访问API,必须经过认证才可以访问的。见添加认证后的信息代码:

#!/usr/bin/env python
# -*-coding:utf-8 -*-

from flask import  *
from flask_httpauth import  HTTPBasicAuth
from flask_restful import  reqparse,Api,Resource

app=Flask(__name__)
api=Api(app)
auth=HTTPBasicAuth()

books=[
    {
        'id':1,
        'author':'无涯',
        'name':'Python自动化测试实战',
        "done":True
    },
    {
        'id': 2,
        "aurhor":"无涯",
        'name': 'Python测试开发实战',
        "done":False
    }
]


@auth.get_password
def get_password(username):
    if username=='wuya':
        return 'admin'

@auth.error_handler
def authoriazed():
    return make_response(jsonify({'error':'请进行认证'}),403)

@app.route('/api/v1/books',methods=['GET'])
@auth.login_required
def get_books():
    '''返回所有的书籍'''
    return jsonify({'books':books})


@app.route('/api/v1/books/<int:book_id>',methods=['GET'])
@auth.login_required
def get_book(book_id):
    '''依据ID返回具体的书的信息'''
    book=list(filter(lambda t: t['id'] == book_id, books))
    if len(book)==0:
        abort(404)
    return jsonify({'book':book[0]})

@app.errorhandler(404)
def not_found(error):
    '''对404情况的处理,希望错误得到的是404的错误'''
    return make_response(jsonify({'error':'Not Found'}),404)



@app.route('/api/v1/books',methods=['POST'])
@auth.login_required
def create_book():
    if not request.json or not 'author' in request.json:
        abort(400)
    book={
        'id':books[-1]['id']+1,
        # 'author':request.json['author'],
        'author':request.json.get('author'),
        'name':request.json.get('name'),
        'done':False
    }
    books.append(book)
    return jsonify({'book':book}),201

@app.route('/api/v1/books/<int:book_id>',methods=['POST'])
@auth.login_required
def update_book(book_id):
    book=list(filter(lambda t: t['id'] == book_id, books))
    if len(book)==0:
        abort(404)
    if not request.json:
        abort(400)
    if 'author' not in  request.json and 'name'  not in  request.json:
        abort(400)
    if 'done' in request.json and type(request.json['done']) is not bool:
        abort(400)
    book[0]['author']=request.json.get('author',book[0]['author'])
    book[0]['name']=request.json.get('name',book[0]['name'])
    book[0]['done']=request.json.get('done',book[0]['done'])
    return jsonify({'result':book[0]})


@app.route('/api/v1/books/<int:book_id>',methods=['DELETE'])
@auth.login_required
def del_book(book_id):
    book=list(filter(lambda t:t['id']==book_id,books))
    if len(book)==0:
        abort(404)
    books.remove(book[0])
    return jsonify({'result':True})

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

 下面通过类的方式来修改代码,让API增加完整,而且通过类的方式,API更加好容易管理,具体代码见如下:

#!/usr/bin/env python
# -*-coding:utf-8 -*-


from flask import  Flask,redirect,render_template,url_for,request,jsonify,abort,make_response
from flask_restful import  Resource,Api
from flask_httpauth import  HTTPBasicAuth


app=Flask(__name__)
api=Api(app=app)

auth=HTTPBasicAuth()

@auth.get_password
def get_password(name):
    if name=='wuya':
        return 'admin'

@auth.error_handler
def authorized():
    return make_response(jsonify({'error':'请认证'}),401)


books=[
    {
        'id':1,
        'author':'无涯',
        'name':'Python自动化测试实战',
        "done":True
    },
    {
        'id': 2,
        "aurhor":"无涯",
        'name': 'Python测试开发实战',
        "done":False
    }
]

class BooksApi(Resource):
    decorators=[auth.login_required]

    def get(self):
        return jsonify(books)

class BookApi(Resource):
    def get(self,book_id):
        book=list(filter(lambda t:t['id']==book_id,books))
        print(book)
        if len(book)==0:
            abort(400)
        else:
            return jsonify(book)

    def put(self,book_id):
        book=list(filter(lambda t:t['id']==book_id,books))
        if len(book)==0:
            abort(404)
        elif not request.json:
            abort(400)
        elif 'author' not in request.json:
            abort(400)
        elif 'done' not in request.json and type(request.json['done']) is not bool:
            abort(400)
        book[0]['author']=request.json.get('author',book[0]['author'])
        book[0]['name'] = request.json.get('name', book[0]['name'])
        book[0]['done'] = request.json.get('done', book[0]['done'])
        return jsonify({'status':0,'msg':'ok','datas':book})



    def delete(self,book_id):
        book = list(filter(lambda t: t['id'] == book_id, books))
        if len(book)==0:
            abort(404)
        books.remove(book[0])
        return jsonify({'status':1001,'msg':'删除成功'})



api.add_resource(BooksApi,'/v1/api/books',endpoint='/v1/api/books')
api.add_resource(BookApi,'/v1/api/book/<int:book_id>')

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

 

 通过类的方式修改代码,关于它的API调用add_resource来添加进去,鉴权部分与函数式的编程方式还是有点差异。

 

posted @ 2019-07-07 10:14  无涯(WuYa)  阅读(907)  评论(0)    收藏  举报