【问题标题】:Can I make a FastAPI endpoint receive json OR a file我可以让 FastAPI 端点接收 json 或文件吗
【发布时间】:2023-03-19 09:47:01
【问题描述】:

我正在尝试创建一个 FastAPI 端点,用户可以在其中上传 json 格式或 gzip 文件格式的文档。我可以让端点单独/单独地从这两种方法接收数据,但不能在一个端点/函数中一起接收数据。有没有办法让同一个 FastAPI 端点接收 json 或文件?

json 示例:

from fastapi import FastAPI
from pydantic import BaseModel


class Document(BaseModel):
    words: str


app = FastAPI()


@app.post("/document/")
async def create_item(document_json: Document):
    return document_json

文件示例:

from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.gzip import GZipMiddleware


app = FastAPI()
app.add_middleware(GZipMiddleware)


@app.post("/document/")
async def create_item(document_gzip: UploadFile = File(...)):
    return document_gzip

非此即彼的不工作示例:

from typing import Optional
from fastapi import FastAPI, File, UploadFile
from fastapi.middleware.gzip import GZipMiddleware
from pydantic import BaseModel


class Document(BaseModel):
    words: Optional[str] = None


app = FastAPI()
app.add_middleware(GZipMiddleware)


@app.post("/document/")
async def create_item(
    document_json: Document, document_gzip: Optional[UploadFile] = File(None)
):
    return document_json, document_gzip

【问题讨论】:

    标签: python fastapi


    【解决方案1】:

    在您使用这两个选项的“不工作示例”中,将 json 文档设为强制性。也就是说,必须提供参数。我猜想发布 json 文档而不是文件有效,但反之则失败。我说的对吗?

    无论如何,正确的代码应该如下所示(注意:我没有测试过):

    @app.post("/document/")
    async def create_item(
        document_json: Document = None, document_gzip: Optional[UploadFile] = File(None)
    ):
        # Check that either are not none
        return document_json, document_gzip
    

    根据您的评论进行编辑:

    这可能是由于Fastapi 为您处理请求的方式。由于您从正​​文中同时指定了JSONFile,因此Fastapi 可能只使用了最后一个(这只是我的假设,探索它可能会很有趣)。因此它会始终将所有参数(GET 除外)视为文件对象,并在请求正文中检查文件。

    您可以尝试使用原始的Request 对象来检查正文中的任何JSON

    下面是一个潜在的例子,它可能是什么样子。由于Fastapi 是基于Starlette 的,因此两者之间共享了一些功能。 Request 对象就是其中之一。这是包含更多信息的文档(以备不时之需)

    https://www.starlette.io/requests/

    在更新的示例下方,我没有测试,但应该可以工作

    @app.post("/document/")
        async def create_item(
            req: Request, document_gzip: Optional[UploadFile] = File(None)
        ):
            if document_gzip is None:
                # Maybe use a try except (i.e. try-catch) block... just in case
                document_json = await req.json()
    
            # Check that either are not none
            return document_json, document_gzip
    

    【讨论】:

    • 实际上在我的“不工作示例”中都不起作用,返回正文显示类似{"detail":[{"loc":["body","documents_json"],"msg":"field required","type":"value_error.missing"}]}。如果我进行您建议的更改,则两者都返回状态代码 204,就好像它们都可以工作一样,并且 gzip 文件可以工作,但 json 实际上并不接受 json,我只是返回 null(这与工作“示例”不同with json" 我实际上收到了 json 数据。
    • 为我工作。谢谢
    【解决方案2】:

    我的工作样本基于previous response:

    async def invocations(input: Request, input_file: Optional[UploadFile] = File(None)):
    
        if input_file is None:
            input_json = await input.json()
    
        if input_file is not None:
            input_df = pd.read_csv(BytesIO(input_file.file.read()))
            return models.get("model").batch_predict(input_df=input_df)
    

    使用 json 请求:

    curl --location --request POST 'http://0.0.0.0:8080/invocations' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "first_attr": 10,
        "second_attr": "all good"
    }'
    

    请求文件:

    curl --location --request POST 'http://0.0.0.0:8080/invocations' \
    --form 'input_file=@"/absolute-path-to-file/some-json-file.csv"'
    

    缺点是生成的文档没有反映这种行为(尚未尝试修复此问题):

    【讨论】:

      【解决方案3】:

      这是做不到的。

      你可以在一个路径操作中声明多个 File 和 Form 参数, 但您也不能将您希望收到的 Body 字段声明为 JSON,因为请求将使用编码的正文 multipart/form-data 而不是 application/json。

      这不是 FastAPI 的限制,它是 HTTP 协议的一部分。

      但是,您可以使用 Form(None) 作为解决方法来附加额外的 List 或类似的类型作为 form-data

      这是我在这个用例中使用的 sn-p

      @extract_operations_router.post("/runcompletepipe")
      async def runcompletepipeline(urls: Optional[List[HttpUrl]] = Form(None),files : Optional[List[UploadFile]] = File(None)):
          print(urls,files)
          # for file in files:
          #     print(file.filename)
          return('qapairslist')
      

      【讨论】:

        猜你喜欢
        • 2022-10-15
        • 2021-05-02
        • 1970-01-01
        • 1970-01-01
        • 2014-12-15
        • 2023-04-03
        • 1970-01-01
        • 1970-01-01
        • 2019-10-09
        相关资源
        最近更新 更多