将文件上传到谷歌云存储时出错

问题描述 投票:0回答:1

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

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

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"
            )
python google-cloud-platform google-cloud-storage fastapi
1个回答
1
投票

选项1

根据文档,

upload_from_file()
(另请参阅流式上传的文档)支持类文件对象;因此,您可以使用
.file
UploadFile
属性(代表
SpooledTemporaryFile
实例)。例如:

# Rewind the stream to the beginning. This step can be omitted if the input
# stream will always be at a correct position.
file_obj.seek(0)

# Upload data from the stream to your bucket
blob.upload_from_file(file.file)  

选项2

您可以读取

file
的内容并将其传递给
upload_from_string()
(另请参阅从内存上传对象的文档),它支持
data
bytes
格式的
string
。例如:

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

或者,由于您使用

async def
定义了端点(有关 def
async def
的信息,请参阅
此答案
):

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

选项3

为了完整起见,

upload_from_filename()
(另请参阅有关从文件系统上传对象的文档)需要一个
filename
,它表示到 file
path
。因此,当您传递
No such file or directory
(如您的评论中所述)时,会引发
file.filename
错误,因为这不是文件的 path 。要使用该方法(作为最后的手段),您应该将
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 中所示),如果您决定定义自己的

create_upload_file
端点用
async def
而不是
def
(看看这个答案有关
def
async def
的更多详细信息),您应该在单独的线程中运行“上传文件”函数,以确保主线程(运行协程的地方)不会被阻塞。您可以使用 Starlette 的
run_in_threadpool()
来完成此操作,FastAPI 内部也使用它(也请参见 here)。例如:

await run_in_threadpool(blob.upload_from_file, file.file)

或者,您可以使用

asyncio
loop.run_in_executor()
,如此答案中所述,并在此示例片段中演示。

对于选项3,如果您需要打开一个

NamedTemporaryFile
并将内容写入其中,您可以使用
aiofiles
库来做到这一点,如thisanswer的选项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)

最后,看看这里这里关于如何将I/O操作封装在

try-except-finally
块中的答案,以便您可以捕获任何可能的异常,以及关闭
UploadFile
正确地反对。
UploadFile
是一个临时文件,关闭时会从文件系统中删除。要了解系统保存临时文件的位置,请参阅此答案。注意:Starlette,如此处所述,使用
SpooledTemporaryFile
和 1MB
max_size
,这意味着数据将在内存中进行假脱机处理,直到文件大小超过 1MB,此时内容将写入临时目录。因此,如果上传的文件大于 1MB 并且尚未调用
temp
,您只会在
.close()
目录中看到您上传的文件。

© www.soinside.com 2019 - 2024. All rights reserved.