如何使用 FastAPI 以 JSON 格式返回数据?

2024-12-13 08:37:00
admin
原创
100
摘要:问题描述:我在FastAPI和Flask中编写了具有相同功能的相同 API 应用程序。但是,在返回 JSON 时,两个框架之间的数据格式不同。两者都使用相同的json库,甚至完全相同的代码:import json from google.cloud import bigquery bigquery_clien...

问题描述:

我在FastAPIFlask中编写了具有相同功能的相同 API 应用程序。但是,在返回 JSON 时,两个框架之间的数据格式不同。两者都使用相同的json库,甚至完全相同的代码:

import json
from google.cloud import bigquery
bigquery_client = bigquery.Client()

@router.get('/report')
async def report(request: Request):
    response = get_clicks_impression(bigquery_client, source_id)
    return response

def get_user(client, source_id):
    try:
        query = """ SELECT * FROM ....."""
        job_config = bigquery.QueryJobConfig(
            query_parameters=[
                bigquery.ScalarQueryParameter("source_id", "STRING", source_id),
            ]
        )
        query_job = client.query(query, job_config=job_config)  # Wait for the job to complete.
        result = []
        for row in query_job:
            result.append(dict(row))
        json_obj = json.dumps(result, indent=4, sort_keys=True, default=str)

    except Exception as e:
        return str(e)

    return json_obj

Flask中返回的数据是字典:


  {
    "User": "fasdf",
    "date": "2022-09-21",
    "count": 205
  },
  {
    "User": "abd",
    "date": "2022-09-27",
    "count": 100
  }
]

而在FastAPI中则是字符串:

"[
    {
        \"User\": \"aaa\",
        \"date\": \"2022-09-26\",
        \"count\": 840,
]"

我使用这个的原因json.dumps()date无法令人发笑。


解决方案 1:

错误的方法

如果在返回对象之前对其进行序列化,则使用json.dumps()(如示例所示),例如:

import json

@app.get('/user')
async def get_user():
    return json.dumps(some_dict, indent=4, default=str)

返回的 JSON 对象最终将被序列化两次,因为在这种情况下,FastAPI 也会在后台自动序列化返回值。因此,最终得到输出字符串的原因是:

"[
    {
        \"User\": \"aaa\",
        \"date\": \"2022-09-26\",
 ... 

解决方案

查看可用的解决方案,以及下面有关 FastAPI/Starlette 如何在底层工作的解释。

选项 1

第一个选项是照常返回数据(例如dictlist等)—即,使用例如return some_dict—并且 FastAPI 会在后台使用将数据转换为 JSON 兼容数据后,自动将该返回值转换为 JSONjsonable_encoder 。确保jsonable_encoder 不可序列化的对象(例如datetime对象)转换为str。然后,FastAPI 会将 JSON 兼容数据放入里面JSONResponse,它将application/json向客户端返回编码的响应(这也在本答案的选项 1 中进行了解释)。从这里的JSONResponseStarlette 的源代码中可以看出,将使用 Python 标准来序列化(有关替代/更快的 JSON 编码器,请参阅此答案和此答案)。json.dumps()`dict`

例子

from datetime import date


d = [
    {"User": "a", "date": date.today(), "count": 1},
    {"User": "b", "date": date.today(), "count": 2},
]


@app.get('/')
async def main():
    return d

以上相当于

from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get('/')
async def main():
    return JSONResponse(content=jsonable_encoder(d))

输出:

[{"User":"a","date":"2022-10-21","count":1},{"User":"b","date":"2022-10-21","count":2}]

改变status_code

要更改status_code返回dict对象的时间,您可以使用Response对象,如文档中所述,如下所示:

from fastapi import Response, status

@app.get('/')
async def main(response: Response):
    response.status_code = status.HTTP_201_CREATED  # or simply = 201
    return d

还可以在返回时指定自定义 或直接指定自定义(在下面的选项 2 中演示),以及从中继承的任何其他响应类(请参阅此处的FastAPI 文档,以及此处的Starlette 文档和此处的响应实现)。 FastAPI/Starlette 类的实现可在此处找到,以及可以使用的 HTTP 状态代码列表(而不是直接将HTTP 响应状态代码作为传递)可在此处看到。 例如:status_code`JSONResponseResponseResponseJSONResponseint`

from fastapi import status
from fastapi.responses import JSONResponse
from fastapi.encoders import jsonable_encoder

@app.get('/')
async def main():
    return JSONResponse(content=jsonable_encoder(d), status_code=status.HTTP_201_CREATED)

选项 2

如果出于任何原因(例如,尝试强制使用某种自定义 JSON 格式),您必须在返回对象之前对其进行序列化,则您可以直接返回自定义对象,如此Response答案中所述。 根据文档:

当您直接返回时,Response其数据不会被验证、转换(序列化)或自动记录。

此外,如下所述:

FastAPI(实际上是 Starlette)将自动包含一个 Content-Length 标头。它还将包含一个 Content-Type 标头,media_type并根据该标头附加文本类型的字符集。

因此,您还可以将 设置media_type为您期望数据的任何类型;在本例中,即application/json。示例如下。要选择性地更改对象status_codeResponse,您可以在返回 时使用选项 1 中描述的相同方法JSONResponse

注 1:此答案中发布的 JSON 输出(在选项 1 和 2 中)是通过浏览器直接访问 API 端点的结果(即,在浏览器的地址栏中键入 URL,然后按 Enter 键)。如果您通过 Swagger UI 测试端点/docs,则会看到缩进不同(在两个选项中)。这是由于 Swagger UI 格式化application/json响应的方式造成的。如果您也需要在 Swagger UI 上强制自定义缩进,则可以避免在下面的示例中media_type为指定Response。这将导致将内容显示为文本,因为Content-Type响应中缺少标题,因此 Swagger UI 无法识别数据的类型,以便自定义格式化它们(在响应的情况下application/json)。

注 2:将default参数设置为strinjson.dumps()使得可以序列化date对象,否则如果未设置,您将得到:TypeError: Object of type date is not JSON serializabledefault是一个针对无法序列化的对象调用的函数。它应该返回对象的 JSON 编码版本。在这种情况下它是str,这意味着每个不可序列化的对象都会转换为字符串。如果您想以自定义方式序列化对象,也可以使用自定义函数或JSONEncoder子类,如此处演示的。此外,如前面选项 1 中提到的,人们可以使用其他 JSON 编码器,例如orjson,与标准库相比,它可能会提高应用程序的性能json(请参阅此答案和此答案)。

注意 3:FastAPI/StarletteResponse接受contentastr或object 作为参数。如此处bytes的实现所示,如果您不传递对象,Starlette 将尝试使用 对其进行编码。因此,例如,如果您传递了 ,您将获得:。在下面的示例中,传递了一个 JSON,稍后将对其进行编码(您也可以在将其传递给对象之前自己对其进行编码)。bytes`content.encode(self.charset)dictAttributeError: 'dict' object has no attribute 'encode'strbytes`Response

例子

from fastapi import Response
from datetime import date
import json


d = [
    {"User": "a", "date": date.today(), "count": 1},
    {"User": "b", "date": date.today(), "count": 2},
]


@app.get('/')
async def main():
    json_str = json.dumps(d, indent=4, default=str)
    return Response(content=json_str, media_type='application/json')

输出:

[
    {
        "User": "a",
        "date": "2022-10-21",
        "count": 1
    },
    {
        "User": "b",
        "date": "2022-10-21",
        "count": 2
    }
]

解决方案 2:

主要基于并建立在@Chris 的回答之上:

总结:

对于第一个选项,如果您首先使用 pandas,请执行以下操作:

JSONResponse(df.fillna(np.nan).replace([np.nan], [None]).to_dict())

对于第二个答案,
不要使用缩进发送多余的空格,而是这样做:

Response(content=json_str, media_type='application/json')

原因:

如果您尝试发送任何 nan 值,即使它是长表或 pandas 上的一个值,第一个选项也会失败,因此它现在可能适合您的尝试,但将来可能会失败(Murphy -> 将失败)。修复从这里开始。

对于第二部分,任何非 0 的缩进都是供人类使用的,不会帮助您的代码运行得更快。考虑到现代软件包甚至经常删除页面的 javascript 缩进。如果需要调试,任何计算机都会很乐意为您缩进消息,并且您最喜欢的代码段也会很乐意缩进您(观察者,而不是编写代码的人)感到舒适的空格数。

[根据评论更改了答案。另请查看评论以了解更多信息。尤其是流媒体。]

解决方案 3:

使用response_class=JSONResponse

from fastapi.responses import JSONResponse
@app.get("/db/{jobid}", include_in_schema=True, response_class=JSONResponse)
async def dbget(jobid:int):
    """ read data stored on the jobid """
    return db.read(f"jobid=={jobid}")

证明这有效

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1043  
  IPD(Integrated Product Development,集成产品开发)是一种系统化的产品开发方法论,旨在通过跨职能团队的协作,优化产品开发的效率和质量。IPD流程强调从市场需求出发,通过并行工程、跨部门协作和阶段性评审,确保产品从概念到上市的每个环节都高效且可控。随着敏捷开发方法的普及,越来越多的企业开始...
华为IPD流程   41  
  随着企业产品开发复杂度的提升以及市场需求的快速变化,传统的产品开发模式逐渐显现出局限性。集成产品开发(IPD)流程与敏捷开发(Agile Development)作为两种主流的开发方法论,分别从系统化管理和快速响应需求的角度为企业提供了解决方案。然而,单独使用其中一种方法往往无法完全满足企业在效率、质量和创新上的多重需...
华为IPD流程   35  
  华为IPD(Integrated Product Development,集成产品开发)流程是华为公司成功的关键因素之一。它不仅帮助华为在技术上实现了快速创新,还通过市场导向确保了产品的商业成功。IPD流程通过整合技术与市场双驱动,实现了从需求定义到产品交付的全生命周期管理。这种模式不仅提高了产品的开发效率,还降低了市...
IPD流程中PDCP是什么意思   32  
  在研发领域,集成产品开发(IPD)流程已经成为企业提升创新效率和市场竞争力的重要手段。然而,资源分配的不合理往往是制约IPD流程效率的关键因素之一。无论是人力资源、财务资源还是技术资源,如何高效分配直接关系到项目的成功与否。优化资源分配不仅能够缩短产品开发周期,还能降低研发成本,提升产品的市场竞争力。因此,掌握资源分配...
IPD流程中CDCP   34  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用