APIFlask:一个基于 Flask 的 Web API 框架

经常看到有人把 FastAPI 和 Flask 放到一起比较,但是却没有意识到这完全是两种东西——前者是基于 Web 框架 Starlette 添加了 Web API 功能支持的(框架之上的)框架,而后者是和 Starlette 同类的通用 Web 框架。你怎么能让小明和骑电动车的小军赛跑然后还夸小军好快好强?为了让框架 PK 爱好者们有一个更公平的比较对象,从一份 APIFairy 0.6.2 版本的 fork 开始,我实现了一个基于 Flask 的 Web API 框架——APIFlask

APIFlask 在 Flask 的基础上添加了更多 Web API 相关的功能支持,核心特性包括:

  • 更多方便的装饰器,比如 @app.input()@app.output()@app.get()@app.post() 等等
  • 自动反序列化和验证请求格式,当请求数据不符合模式类要求时,会自动生成包含错误详细信息的错误响应(基于 Webargs
  • 自动格式化和序列化响应数据,在定义好响应模式后,你可以直接在视图函数返回一个模型类对象,或是返回字典(基于 Marshmallow
  • 自动生成 OpenAPI Specification 文件,你可以把这个文件导入到 API 调试工具或是用来生成客户端代码(基于 APISpec
  • 自动生成交互式 API 文档,并自动为蓝本和视图设置对应的标签分类(基于 Swagger UI and Redoc
  • 自动为 HTTP 错误生成 JSON 格式的错误响应

下面是一个最基础的示例程序:

 from apiflask import APIFlask, Schema, abort
 from apiflask.fields import Integer, String
 from apiflask.validators import Length, OneOf
 ​
 app = APIFlask(__name__)  # 可以使用 title 和 version 参数来自定义 API 的名称和版本
 ​
 pets = [
     {
         'id': 0,
         'name': 'Kitty',
         'category': 'cat'
     },
     {
         'id': 1,
         'name': 'Coco',
         'category': 'dog'
     }
 ]
 ​
 # 定义一个请求数据模式类
 class PetInSchema(Schema):
     name = String(required=True, validate=Length(0, 10))  # 可以使用 description 参数添加字段描述
     category = String(required=True, validate=OneOf(['dog', 'cat']))
 ​
 # 定义一个响应数据模式类
 class PetOutSchema(Schema):
     id = Integer()
     name = String()
     category = String()
 ​
 ​
 @app.get('/pets/<int:pet_id>')
 @app.output(PetOutSchema)  # 使用 @output 装饰器标记响应数据模式
 def get_pet(pet_id):
     if pet_id > len(pets) - 1:
         abort(404)
     # 在真实程序里,你可以直接返回 ORM 模型类的实例,比如
     # return Pet.query.get(1)
     return pets[pet_id]
 ​
 ​
@app.patch('/pets/<int:pet_id>')
@app.input(PetInSchema(partial=True))  # 使用 @input 装饰器标记请求数据模式
@app.output(PetOutSchema)
def update_pet(pet_id, json_data):  # 通过验证后的请求数据字典会注入到视图函数,默认参数名为 json_data
    if pet_id > len(pets) - 1:
        abort(404)
    for attr, value in json_data.items():
        pets[pet_id][attr] = value
    return pets[pet_id]

P.S. 你也可以使用类视图(class-based views),具体示例见这里

如果你想在你的电脑上运行这个示例,可以先用下面的命令安装 APIFlask(需要 Python 3.7 及以上版本,Flask 1.1 及以上版本):

Linux 和 macOS:

 $ pip3 install apiflask

Windows:

 > pip install apiflask

安装完成后把上面的代码保存到文件 app.py,然后执行下面的命令运行程序:

 $ flask run

现在你可以在浏览器访问 http://localhost:5000/docs 查看基于 Swagger UI 自动生成的交互式 API 文档:

Swagger UI

或者访问 http://localhost:5000/redoc 查看基于 Redoc 生成的 API 文档:

Redoc

访问 http://localhost:5000/openapi.json 可以获取自动生成的 OpenAPI spec 文件。

这个示例程序的完整版本可以在 https://github.com/greyli/apiflask/tree/master/examples 看到。如果你想了解更多用法,可以阅读文档中的基本用法一章(目前只有英文)。

APIFlask 只在 Flask 之上做了轻量级包装,所以你实际上仍然是在写一个 Flask 程序,所有 Flask 的特性都完全兼容。你只需要记住下面两处不同点:

  • 创建程序实例的时候使用 APIFlask 类(from apiflask import APIFlask)。
  • 创建蓝本实例的时候使用 APIBlueprint 类(from apiflask import APIBlueprint)。
  • 使用 apiflask.abort() 函数返回 JSON 格式的错误响应。

以下面的 Flask 程序为例:

 from flask import Flask, request, escape
 ​
 app = Flask(__name__)
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

迁移到 APIFlask 只需要改动两行代码:

 from apiflask import APIFlask  # 第一行
 from flask import request, escape
 ​
 app = APIFlask(__name__)  # 第二行
 ​
 @app.route('/')
 def hello():
     name = request.args.get('name', 'Human')
     return f'Hello, {escape(name)}'

欢迎提出改进建议,报告 bug 或是分享其他任何相关的想法。你也可以在 GitHub 上创建 Issue,或是提交 PR 来改进它。谢谢!

APIFlask:一个基于 Flask 的 Web API 框架》上有25条评论

  1. 头像meiyujack

    辉神大大牛?,为了flask出了一口恶气,支持apiflask项目!在下几经辗转重写,终于还是决定还是试下apiflask~~,最后会留言的。

    回复
  2. 头像Stone

    事实上,这看起来不是那么flasky,按照Flask插件的开发惯例,只要在 Flask app 创建后调用一下 插件.init()方法就可以了……

    回复
  3. 头像伍个一

    大佬你好,想问一个apiflask1.0什么时候推出?在github上看排期是到12.31日,

    这边是遇到了问题,apiflask.Schema是继承Marshmallow么?但是我这边使用Schema的exclude方法,还有class Meta的时候有报错,希望有空帮忙解答一下疑问,多谢!!!
    “`
    from apiflask import Schema
    from apiflask.fields import Integer, String

    # 用户模板类
    class UserReq(Schema):
    id = Integer(required=True, description=”ID”), # ID
    login_name = Integer(required=True, description=”登录名”), # 登录名
    real_name = String(required=True, description=”所属项目”) # 姓名
    branch = String(required=True, description=”所属模块”) # 部门
    phone = String(required=True) # 手机号
    password = String(required=True) # 密码
    page = Integer(required=True, description=”page”), # page
    pageLimit = Integer(required=True, description=”page”), # pageLimit

    if __name__ == ‘__main__’:
    userData = {‘is_delete’: ‘1’, ‘id’: 1, ‘real_name’: ‘wugeyi’, ‘branch’: ‘测试部’, ‘create_time’: ‘2021-11-06T11:26:28’,
    ‘phone’: ‘123456’, ‘password’: ‘123’, ‘login_name’: ‘wugeyi’, ‘user_status’: ‘0’,
    ‘update_time’: ‘2021-11-09T11:27:22’}
    userReq = UserReq()
    print(userReq.load(userData))

    ——————————————-

    marshmallow.exceptions.ValidationError: {‘id’: [‘Unknown field.’], ‘create_time’: [‘Unknown field.’], ‘is_delete’: [‘Unknown field.’], ‘user_status’: [‘Unknown field.’], ‘login_name’: [‘Unknown field.’], ‘update_time’: [‘Unknown field.’]}

    “`

    回复
  4. 头像伍个一

    rv = f(*args, **kwargs) # for Flask < 2.0
    TypeError: getBugInfo() takes 0 positional arguments but 1 was given

    提示版本不对,但是我切换到2.0版本之后,发现有其他问题
    想问下这边兼容的是Flask哪个版本?

    回复
      1. 头像伍个一

        #是我post方法的形参没有data

        bug = APIBlueprint(__name__, “pop”)

        @bug.post(‘/bug/’)
        @input(BugReq(partial=True))
        @output(BugRes)
        def getBugInfo():
        bugData = {}
        data = request.json
        for key in data.keys():
        print(data[key])
        if data[key] != ‘string’ and data[key] != 0:
        bugData[key] = data[key]

        return jsonify(data)

        回复
      2. 头像伍个一

        另外,很期待这个项目。
        之前用过springboot,然后现在用flask的时候,想用接口文档,尝试过flask_restfuls还有一些其他的扩展,最后不习惯接口用对象来包裹,还有请求模板和响应模板的形式,放弃了。
        最后发现APIFlask提供了用对象来实现请求和响应的模板数据,更适合面向对象,前后端分离的形式。
        另外,用的过程中也遇到一些其他问题和需求,想问下有没有其他的渠道来反馈?

        回复
    1. 头像李辉 文章作者

      个人来看,Marshmallow 生态更好,维护也更稳定。我对 Pydantic 不够熟悉,所以暂时没法给出代码设计和用法上的对比分析,后续有时间会写一篇文章。另外 APIFlask 后续也会考虑添加 Pydantic 支持。

      回复
  5. 头像Mack

    博主您好,关于apiflask有个使用问题向您反馈:
    –使用装饰器@auth_required(auth)验证Token登录,它只能作用到视图函数上,不能作用到继承MethodView的视图类上面,后者会报错,错误消息(TypeError: The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a UsersView.)
    –当前使用版本0.8

    回复
  6. 头像Frost Ming

    有些疑问

    1. 我看到APIFairy最近有更新,也是你在维护,那么它和APIFlask定位上有什么不同
    2. Flask API框架的最大痛点是底层设施(ORM)的自由性,ORM不确定,那么Model Schema的互转就无法固化到框架里(对比DRF而言),APIFlask对这个是如何设计取舍

    谢谢

    回复
    1. 头像李辉 文章作者

      1. 和 APIFairy 的区别可以参考这里:

      https://github.com/greyli/apiflask/discussions/14#discussioncomment-566968

      2. 我不太确定我是否正确理解了你的意思。APIFlask 不限定你使用哪个 ORM:

      Schema -> Model:接收到的请求数据在解析和验证后会以字典的形式传递给视图函数,所以创建数据库 Model 可以直接把这个字典传递给 Model 类:

      user = User(**data)

      Model -> Schema:Marshmallow 可以直接把一个 Model 对象根据 Schema 输出 JSON,所以视图函数可以直接返回一个 Model 实例:

      return User.query.get(1)

      回复

撰写评论

电子邮件地址不会被公开,必填项已用 * 标出。