【问题标题】:Error uploading file to google cloud storage将文件上传到谷歌云存储时出错
【发布时间】:2022-08-20 19:24:36
【问题描述】:

我的服务器上的文件应该如何上传到谷歌云存储?

我尝试过的代码如下所示,但是,它会引发类型错误,说预期的类型不是字节:

the expected type is not byte for:
blob.upload_from_file(file.file.read()).

虽然 upload_from_file 需要二进制类型。

@app.post(\"/file/\")
async def create_upload_file(files: List[UploadFile] = File(...)):
    storage_client = storage.Client.from_service_account_json(path.json)
    bucket_name = \'data\'
    try:
        bucket = storage_client.create_bucket(bucket_name)
    except Exception:
        bucket = storage_client.get_bucket(bucket_name)
    for file in files:        
        destination_file_name = f\'{file.filename}\'
        new_data = models.Data(
            path=destination_file_name
        )
        try:
            blob = bucket.blob(destination_file_name)
        blob.upload_from_file(file.file.read())
        except Exception:
            raise HTTPException(
                status_code=500,
                detail=\"File upload failed\"
            )
  • 利用blob.upload_from_filename(file.filename)
  • @JohnHanley 谢谢,但是当我这样做时,它说找不到给定的文件名,可能是什么原因?
  • 文件名是什么?它存在吗?
  • 它取自 fastapi UploadFile: file = File(...)。 file.filename 存在。但是当传递给 blob.upload_from_filename(file.filename) 时,它会抛出“没有这样的文件或目录”的错误。

标签: python google-cloud-platform google-cloud-storage fastapi


【解决方案1】:

选项1

根据文档,upload_from_file() 支持 file-like 对象;因此,您可以使用UploadFile.file 属性(代表SpooledTemporaryFile 实例)。例如:

blob.upload_from_file(file.file)  

选项 2

您可以读取file 的内容并将它们传递给upload_from_string(),它支持bytesstring 格式的data。例如:

blob.upload_from_string(file.file.read())

或者,由于您使用async def 定义了端点(请参阅this answer 以了解defasync def):

contents = await file.read()
blob.upload_from_string(contents)

选项 3

为了完整起见,upload_from_filename() 期望 filename 代表小路file。因此,当您通过 file.filename(如您的评论中所述)时,会引发 No such file or directory 错误,因为这不是小路到文件。要使用该方法(作为最后的手段),您应该将 file 内容保存到 NamedTemporaryFile,它“在文件系统中有一个可见的名称”,“可用于打开文件”,并且一旦您完成了,删除它。例子:

from tempfile import NamedTemporaryFile
import os

contents = file.file.read()
temp = NamedTemporaryFile(delete=False)
try:
    with temp as f:
        f.write(contents);
    blob.upload_from_filename(temp.name)
except Exception:
    return {"message": "There was an error uploading the file"}
finally:
    #temp.close()  # the `with` statement above takes care of closing the file
    os.remove(temp.name)

注1:

如果您将一个相当大的文件上传到 Google Cloud Storage,可能需要一些时间才能完全上传,并且遇到了 timeout 错误,请考虑增加等待服务器响应的时间,通过更改timeout 值,如upload_from_file() 文档中所示,以及前面描述的所有其他方法,默认设置为timeout=60 秒。要更改它,请使用例如blob.upload_from_file(file.file, timeout=180),或者您也可以设置timeout=None(这意味着它将等到连接关闭)。

笔记2:

由于google-cloud-storage 包中的所有上述方法都执行阻塞 I/O 操作——如源代码 hereherehere 中所见——如果您决定使用async def 而不是 def(请查看 this answer 以了解有关 defasync def 的更多详细信息),您应该在单独的线程中运行“上传文件”功能以确保主线程(协程运行的地方)不会被阻塞。您可以使用 Starlette 的 run_in_threadpool 来做到这一点,FastAPI 在内部也使用它(也请参阅 here)。例如:

await run_in_threadpool(blob.upload_from_file, file.file)

或者,您可以使用asyncioloop.run_in_executor,如this answer 中所述并在this sample snippet 中演示。

至于选项3,如果你需要打开一个NamedTemporaryFile并将内容写入其中,你可以使用aiofiles库来完成,如this answer的选项2所示,即使用:

async with aiofiles.tempfile.NamedTemporaryFile("wb", delete=False) as temp:
    contents = await file.read()
    await temp.write(contents)
    #...

再次,在外部线程池中运行“上传文件”功能:

await run_in_threadpool(blob.upload_from_filename, temp.name)

【讨论】:

  • 非常感谢您提供所有提到的选项以及您的时间。但是,当我尝试使用提供的解决方案上传大文件(最大 1 GB)时,仍然出现“内部服务器错误”,您认为我应该将它们作为块发送吗?还是原因是别的?
  • 这与upload_from_file() 的默认超时限制有关。将其初始化为 None 解决了这个问题。非常感谢您的再次回复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
  • 1970-01-01
  • 2018-08-01
  • 2018-07-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多