APIFlask的”亿“点问题

最近在使用apiflask框架,总体来说这个框架十分的赞!:+1:
在使用的过程中遇到了一下的一些问题或者说是疑问,有的问题已经自行解决但不知道是否有更好的方法。
因此发个贴看看有没有更好的、更优雅的解决方法。
(表达能力不太好,见谅 and 由于写作的时间跨度比较大,因此个别方法已在官方文档中贴出,但下文并未提及,有的也可能是我眼瞎没看到)

自定义通用响应体

个人感觉这个问题是比较重要的,目前的解决方法过于丑陋。
当前的解决办法(Flask通用),是先定义一个格式化响应体的函数,然后将内容格式化成字典然后返回,但这样就不知道应该如何与@output装饰器配合使用了,感觉这个问题应该是有成熟的解决方案才对。

其中这个自定义的响应体应该是全局通用的,包括错误处理之类(框架自动处理 or 开发者手动处理的错误)

def make_res(code:int=200, message:str="ok", data: Any=None) -> dict:
    return {
        "code": code,
        "message": message,
        "data": data,
        "timestamp": get_timestamp()
    }

@app.get('/')
def index():
    ...
    return make_res( ... )

Abort函数

  • abort函数应该如何搭配上面的自定义响应体一起使用?
  • 有的时候错误会有状态码需要返回出去,但abort好像只可以输入http协议范围内的错误状态码(好像是因为会和http的status code绑定在一起)

@docs装饰器的使用

  1. apiflask直接生成api文档这个功能十分的cool~,但随之而来的问题是我应该如何指定文档中参数的类型,比如form表单中文件上传。
  2. api文档中的Example Value应该如何修改

Example Value目前的解决方法是在Schema模型中指定missingdefault字段

@input装饰器的使用

官网中给出了input可以location到files、‘form’、‘cookies’、‘headers’和’query’
其中files不太清楚如何获取.

关于用户认证

用户认证是极其重要的一块,一般前后端交互中使用token实现。查看apiflask官网发现到目前位置仍然没有找到相关部分的栗子。所幸apiflask使用了flask-httpauth模块蹭使用过,一下是我的解决方案,同样的个人对这个实现方法不太满意,感觉比较丑陋。代码如下:
问题:

  1. 当使用类的方法(MethodView)来组织时,如何添加认证
    • 如何为整个类添加认证 → 未知
    • 如何为类中的个别方法添加 → 为这个类的方法添加装饰器@auth_required
  2. 当使用普通视图函数时应该如何添加 → 直接添加@auth_required

extend.py

from apiflask import HTTPTokenAuth

@auth.verify_token
def verify_token(token):
    """验证token
        验证函数,验证token是否有效

    Args:
        token (str): 前端传来的token

    Returns:
        Bool: True表示token验证有效 否则False
    """
    g.user = None
    try:
        data = Serializer(current_app.config['SECRET_KEY']).loads(token)
    except Exception as e:
        return False
    if "username" in data:
        g.user = data["username"]
        return True
    return False

utils.py

def generate_auth_token(payload:dict, expiration=3600 * 24 * 7):
    """生成token
    Args:
        payload (dict): token载体
        expiration (int, optional): token有效时间. Defaults to 3600*24*7.

    Returns:
        str: token令牌
    """
    s = Serializer(current_app.config["SECRET_KEY"], expires_in=expiration)
    return s.dumps(payload).decode()

views.py

from apiflask.decorators import auth_required
...
@bp.get("/")
@auth_required(auth)
def index():
    user = {"msg": "not found"}
    for item in users:
        if item["username"] == g.user:
            user = item
    return user

ps: 上面的代码均已分类push到GitHub,新手上路 小心高血压!

1 个赞

你好,对于@input装饰器中location=files的使用可以参考我自己写的网站中的
https://github.com/z-t-y/Flog/blob/master/flog/api/v3/views.py 351-365行
对其进行单元测试可以参考
https://github.com/z-t-y/Flog/blob/master/tests/test_api_v3.py 304-316行

对于这个问题debug了很久,希望能帮助到你。

2 个赞

Thx!之前被这个问题折磨了很久,最终无奈选择用Flask的方法来解决。
现在发现原来是需要在schemaRaw指定type为file,看了你的代码瞬间明白了:+1:

我精简了一下,方便有需要的直接上手

from marshmallow.fields import Raw
...
@app.post("/icon")
@input({"icon": Raw(type="file", required=True)}, location="files")
@doc(description="上传头像")
def icon(data):
    print(data.get("icon"))
    return {"msg": 'ok'}

除此之外,我还发现一个apispec的bug – 无法上传文件。
在测试上述代码的时候我通过apiflask内置的apispec进行调试,发现后端无法获取到文件,转而使用postman。由此发现的这个bug(apispec无法上传,postman正常)

1 个赞

哇,终于收到一份详细反馈,感谢!我周末再来写详细回复。

感谢李大!贴中的问题由于时间问题,还有许多问题项目起来,希望可以开个贴(或者直接在本贴)以便及时反馈(一点小建议) :smiley:

此外,这两天想起来不少问题(具体开发问题,非框架问题)希望新书能多介绍相关方面,内容如下:

  1. 异步任务
  2. 获取异步任务的内容(共享线程内容?)
  3. 消息队列

具体情景:
如写一个端口扫描器,由于扫描65535个端口耗时往往需要几分钟。此时接口应该向前端返回一个任务号(如celery的task id) 前端定时(或使用ws)获取任务进程、内容直到任务完成。

1 个赞

自定义通用响应体

如何自定义通用响应体并和 output 装饰器搭配使用

在 0.9.0 版本之后,你可以通过配置 BASE_RESPONSE_SCHEMA 来指定通用响应的模式:

from apiflask import APIFlask, Schema
from apiflask.fields import String, Integer, Field

app = APIFlask(__name__)

class BaseResponseSchema(Schema):
    message = String()
    code = Integer()
    data = Field()

app.config['BASE_RESPONSE_SCHEMA'] = BaseResponseSchema

数据键默认是 data,你可以通过配置 BASE_RESPONSE_DATA_KEY 来自定义。

现在只需要在视图函数返回对应通用响应的字典即可,output 装饰器指定数据模式。APIFlask 自动序列化基础响应模式,以及嵌套的数据模式,并在 OpenAPI 中生成对应的 Schema:

@app.get('/pets')
@output(PetOutSchema(many=True))
def get_pets():
    return make_resp('Success!', 200, pets)

更多介绍参考示例和文档:

Abort 函数

abort 函数目前还不支持传递额外的参数,后续会支持(详见 https://github.com/greyli/apiflask/issues/125 )。

@docs装饰器的使用

我应该如何指定文档中参数的类型,比如 form 表单中文件上传。

参数类型都通过数据的 input/output schema 的字段类型来指定。

  1. api文档中的 Example Value 应该如何修改

整个数据模型的示例在 input 和 output 装饰器的 example 和 example 参数来给出。

单个字段的示例在字段定义中给出:

class MySchema(Schema):
    name = String(metadata={'example': 'Mike'})

文档:https://apiflask.com/openapi/#response-and-request-example

@input 文件上传

官网中给出了input可以location到 files 、‘form’、‘cookies’、‘headers’和’query’
其中 files 不太清楚如何获取.

暂不支持上传类型的 OpenAPI 生成,后续会支持,详见 https://github.com/greyli/apiflask/issues/123 。考添加一个 File 字段来简化字段定义。

关于用户认证

当使用类的方法(MethodView)来组织时,如何为整个类添加认证

@app.route('/pets/<int:pet_id>', endpoint='pet')
class Pet(MethodView):

    decorators = [auth_required(auth)] # <--

    @output(PetOutSchema)
    @doc(summary='Get a Pet')
    def get(self, pet_id):
        # ...

    @auth_required(auth)
    @input(PetInSchema)
    @output(PetOutSchema)
    def put(self, pet_id, data):
        # ...

    @input(PetInSchema(partial=True))
    @output(PetOutSchema)
    def patch(self, pet_id, data):
        # ...
1 个赞

不客气~后续有问题写在这里就可以。

希望新书能多介绍相关方面,内容如下……

嗯,新书会考虑写这些,不过也许会放到那本 Flask 书的第二版,考虑到这些和 Web API 开发关系不大。

abort 函数加了一个 extra_data 参数来设置额外的字段(#130):

@app.get('/')
def missing():
    abort(404, message='nothing', extra_data={'code': '123', 'status': 'not_found'})

你觉得怎么样?有没有什么改进想法?

1 个赞

关于Abort函数配合 Base response schema customization

问题描述:我设置了通用响应体,但这个通用响应体貌似不能配合abort函数使用( abort函数返回的响应体和通用响应体不一致)
下面是我相关的代码片段:

响应体设置

class Normal(Schema):
    status_code = Integer(metadata={'example': 200})
    message = String(metadata={'example': 'response message'})
    data = Field()

视图函数

@input(...)
def put(self, item):
    ...
    abort(400, message="出了点小问题")

当前的响应体

{
  "detail": {},
  "message": "出了点小问题"
}

我期望的响应

{
  "data": null,
  "message": "出了点小问题",
  "status_code": 400
}

后话: 我正准备使用apiflask来做一个简单的 todo list 项目,希望能在简单易懂的基础上涉及到该框架的大部分内容,欢迎各位大佬的指导~

因此上述代码也在该仓库中:

在错误响应里,APIFlask 只会自动生成两个字段 detail 和 message,其他字段由用户自己控制。所以你可以手动返回其他数据:

@input(...)
def put(self, item):
    ...
    abort(400, message="出了点小问题", extra_data={"data": None, "status_code": 400})

或是注册一个错误处理函数:

@app.error_processor
def make_error_response(error):
    return {
        'detail': error.detail,
        'status_code': error.status_code,
        'message': error.message,
        'data': {}
    }
1 个赞

使用DateTime优雅的格式化时间

问题来源:微信群一群友想生成'%a, %d %b %Y %H:%M:%S GMT'格式的时间

解决办法:

from apiflask.fields import DateTime
...
class OutSchema(Schema):
        time = DateTime('%a, %d %b %Y %H:%M:%S GMT')
#  DateTime(<自定义格式字符串>)

补充: DateTime其实内置了两种标准化的格式–rfc(for RFC822)iso(for ISO8601),而默认是使用iso标准进行格式化的,因此可以这样来生成rfc标准的时间

class OutSchema(Schema):
        time = DateTime('rfc')
#  DateTime('rfc') or DateTime('rfc822') or DateTime('iso') or DateTime('iso8601')

apiflask官网文档描述问题


link: Basic Usage - APIFlask
在官网如图所示描述,data_key属性貌似是指向ORM模型,phone_number字段是输出给前端的名字。

但实际上data_key貌似才是输出给前端的名字,而schema模型中属性名应该与ORM模型中的属性名一致。

相关验证代码:
orm模型的第19行:https://github.com/Farmer-chong/todo-list/blob/master/app/models.py
验证模型的第26行:https://github.com/Farmer-chong/todo-list/blob/master/app/schemas/output.py

commit id:fd44fae6375cf7b1485582faf71efedaebbb57fc

github discuss #147

看到你这边有关于全局响应的问题,我这边有一种实现方式,可以参考下https://www.yuque.com/docs/share/411feb9c-1a2e-40d6-9450-3455e56dc836?# 《4. 全局响应/状态码》

1 个赞

通用响应体相关

自定义通用响应体在apiflas中有更优雅的方式实现,当然文中的相关内容也很适合flask,如果apiflask可以进行适度的定制,然后配合错误响应就好了. @greyli
如该文中的重写 okerr等方法

文件上传相关

关于文件上传,一开始就支持文件上传,但不支持OpenAPI生成,详见李大的回答: