前言
这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题
于是乎,我自己开发了一款面试宝典,希望能帮到大家,也希望有更多的Python新人真正加入从事到这个行业里,让python火不只是停留在广告上。
Flask-Restful
Flask-RESTful 是一个可以简化 APIs 的构建的 Flask 扩展,类似django的drf,需要进行安装
pip install flask-restful
请求回顾
方法 | url示例 | 解释 |
GET | http://[hostname]/todo/api/v1.0/taskshttp://[hostname]/todo/api/v1.0/tasks/[task_id] | 检索任务列表 检索某个任务 |
POST | 创建新任务 | |
PUT | 更新任务 | |
DELETE | 删除任务 |
Flask-RESTful 提供了一个 Resource 基础类,它能够定义一个给定 URL 的一个或者多个 HTTP 方法
例如,定义一个可以使用 HTTP 的 GET, PUT 以及 DELETE 方法的 User 资源
基本示例
使用Flask-Restful
,那么定义视图函数的时候,就要继承自flask_restful.Resource
类,然后再根据当前请求的method
来定义相应的方法
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class UserAPI(Resource):
def get(self, id):
return {"data": "OK"}
api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
如果是和蓝图进行结合使用,那么应该Api不在使用flask生成的app对象,而应该是蓝图对象
# 1. 蓝图对象
user_blueprint = Blueprint('user', __name__, url_prefix='/user')
# 2. 初始化蓝图对象Api
user_api = Api(user_blueprint)
class UserAPI(Resource):
def get(self, id):
return {"data": "OK"}
# 3. 类视图路由注册
user_api.add_resource(UserAPI, '/users/<int:id>', endpoint = 'user')
Flask应用注册蓝图对象还是必不可少的
app.register_blueprint(user_blueprint)
路由映射
Flask-rest框架对应的视图通过add_resource
进行路由映射
def add_resource(self, resource, *urls, **kwargs):
if self.app is not None:
self._register_view(self.app, resource, *urls, **kwargs)
else:
self.resources.append((resource, urls, kwargs))
比如
api.add_resource(Index, '/<int:id>', endpoint="index")
请求验证
在之前编写的代码中,对于用户提交参数的验证是非常麻烦的,如果提交数据过多,那么会造成大量的if判断,如下所示
def update_task(task_id):
task = filter(lambda t: t['id'] == task_id, tasks)
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
Flask-RESTful 提供了一个更好的方式来处理数据验证,它叫做 RequestParser 类。这个类工作方式类似命令行解析工具 argparse
parser = reqparse.RequestParser()
parser.add_argument('username',type=str,help='请输入用户名')
args = parser.parse_args()
首先,对于每一个资源需要定义参数以及怎样验证它们
from flask_restful import Resource, reqparse
class Index(Resource): # 视图类
def __init__(self): # 重写init方法
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('account', type = str, required = True, help = '需要提供一个账号', location = 'json')
self.reqparse.add_argument('password', type = str, default = "123456", location = 'json')
super(Index, self).__init__()
在 Index 视图中,使用POST 方法接收参数
参数id
是必须的,因此定义一个缺少id
的错误信息,当客户端缺少这个参数的时候,Flask-RESTful 将会把这个错误信息作为响应发送给客户端
default
字段是可选的,当缺少这个字段的时候,默认的字符串123456
将会被使用
RequestParser 类默认情况下在 request.values 中查找参数,因此 location 可选参数被设置以表明请求参数类型为 request.json 格式的
请求获取
如果是正常的json数据提交,那么数据可以正常拿取
request.json
如果是通过官方文档建议的获取方式过滤参数,那么在正确传递参数的情况下,可以这样获取
args = self.reqparse.parse_args()
args.get("account") # 和字典类似的数据对象
'''
<class 'flask_restful.reqparse.Namespace'>
['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
'''
add_argument
可以指定这个字段的名字,这个字段的数据类型等。以下是这个方法的一些参数的详细讲解
-
default
:默认值,如果这个参数没有值,那么将使用这个参数指定的值。 -
required
:是否必须。默认为False,如果设置为True
,那么这个参数就必须提交上来。 -
type
:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。 -
choices
:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。 -
help
:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。 -
trim
:是否要去掉前后的空格
生成响应
Flask-RESTful 如果返回的是字典格式会自动地处理转换成 JSON 数据格式,原来设计的 REST 服务器使用 Flask 的 jsonify 函数来生成响应
return jsonify( { 'code': 200} )
现在!
return {"code": 200}
序列化
对于一个视图函数,你可以指定好一些字段用于返回json格式数据,进行序列化
以后可以使用ORM模型或者自定义的模型的时候,他会自动的获取模型中的相应的字段,生成json
数据,然后再返回给客户端
比如只包含包如下字段的模型类设计序列化字段
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), index=True, nullable=False)
resource_fields = {
'id': fields.Integer, # 序列化id字段
'name': fields.String, # 序列化name字段
}
- 第一种序列化使用装饰器
那么使用这个序列化字段进行接口数据返回
@marshal_with(resource_fields, envelope="users")
def get(self):
return Users.query.all()
envelope
指定了一个可选的关键字参数来包装结果输出,这个地方经常写data
,代表json
接口返回的整体数据
{
users: [...]
}
- 第二种序列化使用
marshal
return marshal(data, fields, envelope=None)
'''
data: 模型数据
fields: 序列化映射字典
envelope: 包裹数据的KEY
'''
序列化默认值
在返回一些字段的时候,有时候可能没有值,那么这时候可以在指定fields
的时候给定一个默认值,示例代码如下:
resource_fields = {
'name': fields.String(default="blank")
}
重命名属性
很多时候你面向公众的字段名称是不同于内部的属性名
使用 attribute可以配置这种映射。比如现在想要返回user.account
中的值,但是在返回给外面的时候,想以name
作为key返回回去,那么可以这样写:
resource_fields = {
'name': fields.String(attribute='account')
}
复杂序列化
https://flask-restful.readthedocs.io/en/latest/fields.html#list-field
class Follow(db.Model):
__tablename__ = 'follows'
id = db.Column(db.Integer, primary_key=True)
idol = db.Column(db.Integer, db.ForeignKey("users.id")) # 偶像
fans = db.Column(db.Integer, db.ForeignKey("users.id")) # 粉丝
db.UniqueConstraint('idol', 'fans', name="follow_relation")
def __repr__(self): return self.name
class Users(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), index=True, nullable=False)
fans = db.relationship("Users", secondary="follows", backref=db.backref("idols", lazy="dynamic"),
lazy="dynamic", # 由于是多对多,所以生成query对象可以继续查询
primaryjoin=(Follow.idol == id), # 左侧,用于获取 我的粉丝
secondaryjoin=(Follow.fans == id) # 右侧,用于获取 我的偶像
)
比如对之前例子中的多对多关注进行序列化展示,在主表中的fans字段是一个用户的粉丝们,那么序列化返回粉丝的json字典将定义如下
users_resource_fields = {
'id': fields.Integer,
'name': fields.String,
}
resource_fields = {
'id': fields.Integer,
'name': fields.String,
'fans': fields.List(fields.Nested(users_resource_fields)),
}
全部的接口像这样
class Index(Resource):
users_resource_fields = {
'id': fields.Integer,
'name': fields.String,
}
resource_fields = {
'id': fields.Integer,
'name': fields.String,
'fans': fields.List(
fields.Nested(users_resource_fields)
),
}
def get(self):
# return {"code": 200}
# return Users.query.first()
data = Users.query.all()
return marshal(data, self.resource_fields, envelope='data')