我的服务器上的文件应该如何上传到谷歌云存储?
我尝试过的代码如下所示,但是,它会抛出类型错误,指出预期的类型不是字节:
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"
)
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)
您可以读取
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)
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)
如果您要向 Google Cloud Storage 上传相当大的文件,可能需要一些时间才能完全上传,并且遇到
timeout
错误,请考虑增加 等待服务器响应的时间,方法是更改timeout
值,如 upload_from_file()
文档以及之前描述的所有其他方法所示,默认设置为timeout=60
秒。要更改它,请使用例如 blob.upload_from_file(file.file, timeout=180)
,或者您也可以设置 timeout=None
(意味着它将等待直到连接关闭)。
由于 google-cloud-storage 包中的所有上述方法都会执行阻塞 I/O 操作(如源代码 here、here 和 here 中所示),如果您决定定义自己的
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)
try-except-finally
块中的答案,以便您可以捕获任何可能的异常,以及关闭UploadFile
正确地反对。 UploadFile
是一个临时文件,关闭时会从文件系统中删除。要了解系统保存临时文件的位置,请参阅此答案。注意:Starlette,如此处所述,使用 SpooledTemporaryFile
和 1MB max_size
,这意味着数据将在内存中进行假脱机处理,直到文件大小超过 1MB,此时内容将写入临时目录。因此,如果上传的文件大于 1MB 并且尚未调用 temp
,您只会在 .close()
目录中看到您上传的文件。