如何使用Graph API从FastAPI应用程序上传文件到Facebook页面?

huangapple go评论65阅读模式
英文:

How to upload a file from FastAPI application to a Facebook Page using Graph API?

问题

当我尝试使用Python中的Graph API将视频文件上传到Facebook页面,并使用以下函数时:

def upload_video_file(page_id: str, access_token: str, video_file: UploadFile):
    upload_url = f"https://graph.facebook.com/{page_id}/videos"
    headers = {"Authorization": f"Bearer {access_token}"}
    files = {"file": video_file.file}
    response = requests.post(upload_url, headers=headers, files=files)
    data = response.json()
    if data:
        return data
    else:
        return {"message": "上传视频失败"}

并在以下FastAPI端点中执行上述函数:

@router.post("/upload-video/{page_id}")
async def post_video(page_id: str, video_file: UploadFile = File(...), access_token: str = Header(..., description="Access Token")):
    response = upload_video_file(page_id, access_token, video_file)
    return JSONResponse(response)

我收到了以下错误:

{
  "error": {
    "message": "您选择的视频文件处于不受支持的格式中。",
    "type": "OAuthException",
    "code": 352,
    "error_subcode": 1363024,
    "is_transient": false,
    "error_user_title": "不支持的视频格式",
    "error_user_msg": "您尝试上传的视频格式不受支持。请尝试使用支持的视频格式。",
    "fbtrace_id": "AZNNyQhyPDfi5AhDOBpdA5c"
  }
}

有谁知道如何修复这个问题?

英文:

When I try to upload a video file to a Facebook page using the Graph API in python with this function:

def upload_video_file(page_id: str, access_token: str, video_file: UploadFile):
    upload_url = f"https://graph.facebook.com/{page_id}/videos"
    headers = {"Authorization": f"Bearer {access_token}"}
    files = {"file": video_file.file}
    response = requests.post(upload_url, headers=headers, files=files)
    data = response.json()
    if data:
        return data
    else:
        return {"message": "failed uploud video"}

and execute the above function from within the following FastAPI endpoint:

@router.post("/upload-video/{page_id}")
async def post_video(page_id: str, video_file: UploadFile = File(...), access_token: str = Header(..., description="Access Token")):
    response = upload_video_file(page_id, access_token, video_file)
    return JSONResponse (response)pe here

I get this error:

{
  "error": {
    "message": "The video file you selected is in a format that we don't support.",
    "type": "OAuthException",
    "code": 352,
    "error_subcode": 1363024,
    "is_transient": false,
    "error_user_title": "Format Video Tidak Didukung",
    "error_user_msg": "Format video yang Anda coba unggah tidak didukung. Silakan coba lagi dengan sebuah video dalam format yang didukung.",
    "fbtrace_id": "AZNNyQhyPDfi5AhDOBpdA5c"
  }
}

Does anyone know how to fix this?

答案1

得分: 1

错误是因为发送二进制数据而引起的,使用Starlette的UploadFile对象在底层使用的SpooledTemporaryFile对象,没有指定filename和/或content-type。因此,解决方案是在发送HTTP请求之前在定义files变量时指定这两个属性。您可以在这里这里找到相关的requests文档(查看files参数)。您可能还会发现这个答案有帮助。示例:

files = {'file': ('video.mp4', video_file.file, 'video/mp4')}

或者,如果您想使用用户上传的文件中提供的属性,您可以使用以下方法(确保它们不是None):

files = {'file': (video_file.filename, video_file.file,  video_file.content_type)}

另外,我不建议在像FastAPI这样的async环境中使用requests库来执行HTTP请求。如果您仍然希望使用requests,您至少应该从端点中删除async定义,这将导致FastAPI在外部线程池中运行该端点,然后进行await,以防止端点阻塞事件循环(因此阻塞整个服务器)。请查看这个答案以获取有关在FastAPI中使用async def和普通def的详细解释、详细信息和示例。

或者,您可以使用httpx库,它也提供了一个async API,并且与requests有非常相似的语法。详细信息和示例可以在这里以及这里这里找到。有关如何明确设置filesfilenamecontent-type的相关文档可以在这里找到。此外,您还可以在启动时初始化一个全局的Client对象,并在整个应用程序中重用它,而不是在每次请求到达端点时都创建一个新的Client会话。最后,如果文件上传失败,您还可以通过指定自定义响应status_code来向用户返回自定义的JSONResponse,详细信息请参阅这个答案

英文:

The error is caused due to sending binary data, using the SpooledTemporaryFile object that Starlette's UploadFile object uses under the hood—please have a look at this answer and this answer for more details and examples—without specifying a filename and/or content-type.

Hence, the solution would be to specify those two attributes when defining the files variable before sending the HTTP request. You can find the relevant requests documentation here and here (see the files argument). You might find this answer helpful as well. Example:

files = {'file': ('video.mp4', video_file.file, 'video/mp4')}

or, if you would like to use the ones that come with the file uploaded by the user, you could instead use the below (make sure they are not None):

files = {'file': (video_file.filename, video_file.file,  video_file.content_type)}

On a side note, I would not suggest using the requests library for performing HTTP requests in an async environment such as FastAPI's. If you would still like to use requests, you should at least drop the async definition from your endpoint, which would result in FastAPI running that endpoint in an external threadpool that would be then awaited, in order to prevent the endpoing from blocking the event loop (and hence, the entire server). Please have a look at this answer for a thorough explanation, details and examples around async def and normal def in FastAPI.

Alternatively, you could use the httpx library, which provides an async API as well, and has very similar syntax to requests. Details and examples can be found here, as well as here and here. The relevant documentation on how to explicitly set the filename and content-type for files, can be found here. Not only that, you could initialise a global Client object at startup and reuse it accross the application, instead of creating a new Client session every time a request arrives to that endpoint. Finally, you could also return a custom JSONResponse to the user when the file failed to upload for some reason, by specifying a custom response status_code—see this answer for more details.

Working Example

from fastapi import FastAPI, Request, File, UploadFile, Header, status
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
import httpx


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Initialise the Client on startup and add it to the state
    async with httpx.AsyncClient() as client:
        yield {'client': client}
        # The Client closes on shutdown 


app = FastAPI(lifespan=lifespan)


@app.post('/upload-video/{page_id}')
async def upload_video(
    request: Request,
    page_id: str,
    file: UploadFile = File(...),
    access_token: str = Header(...),
):
    client = request.state.client
    url = f'https://graph.facebook.com/{page_id}/videos'
    files = {'file': (file.filename, file.file, file.content_type)}
    headers = {'Authorization': f'Bearer {access_token}'}
    req = client.build_request(method='POST', url=url, files=files, headers=headers)
    r = await client.send(req)
    if r.status_code == 200:
        return r.json()
    else:
        return JSONResponse(
            content='File failed to upload',
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        )

huangapple
  • 本文由 发表于 2023年6月8日 09:06:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76428002.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定