前言

这几年一直在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

http://[hostname]/todo/api/v1.0/tasks

创建新任务

PUT

http://[hostname]/todo/api/v1.0/tasks/[task_id]

更新任务

DELETE

http://[hostname]/todo/api/v1.0/tasks/[task_id]

删除任务

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可以指定这个字段的名字,这个字段的数据类型等。以下是这个方法的一些参数的详细讲解

  1. default:默认值,如果这个参数没有值,那么将使用这个参数指定的值。
  2. required:是否必须。默认为False,如果设置为True,那么这个参数就必须提交上来。
  3. type:这个参数的数据类型,如果指定,那么将使用指定的数据类型来强制转换提交上来的值。
  4. choices:选项。提交上来的值只有满足这个选项中的值才符合验证通过,否则验证不通过。
  5. help:错误信息。如果验证失败后,将会使用这个参数指定的值作为错误信息。
  6. 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')