Я использую FastAPI для загрузки файла в соответствии с официальной документацией, как показано ниже:
@app.post("/create_file") asyncdefcreate_file(file: UploadFile = File(...)): file2store = await file.read() # some code to store the BytesIO(file2store) to the other database
Когда я отправляю запрос с использованием библиотеки запросов Python, как показано ниже:
переменная file2store всегда пуста. Иногда (редко встречается) она может получать байты файла, но почти все время она пуста, поэтому я не могу восстановить файл в другой базе данных.
Я также пробовал bytes вместо UploadFile, но получаю те же результаты. Что-то не так с моим кодом, или я неправильно использую FastAPI для загрузки файла?
Переведено автоматически
Ответ 1
Сначала, согласно документации FastAPI, вам необходимо установить python-multipart — если вы еще этого не сделали — поскольку загруженные файлы отправляются как "данные формы". Например:
У SpooledTemporaryFile, используемого FastAPI / Starlette, есть max_size атрибут, равный 1 МБ, означающий, что данные сохраняются в памяти до тех пор, пока размер файла не превысит 1 МБ, после чего данные записываются во временный файл на диске, в каталоге temp операционной системы. Следовательно, если вы загрузили файл размером более 1 МБ, он не будет сохранен в памяти, и вызов file.file.read() фактически считал бы данные с диска в память. Таким образом, если файл слишком велик, чтобы поместиться в оперативной памяти вашего сервера, вам лучше читать файл порциями и обрабатывать по одному фрагменту за раз, как описано в разделе "Чтение файла порциями" ниже. Вы также можете ознакомиться с этим ответом, который демонстрирует другой подход к загрузке большого файла порциями, используя .stream() метод Starlette и streaming-form-data пакет, который позволяет анализировать потоковые multipart/form-data фрагменты, что приводит к значительному сокращению времени, необходимого для загрузки файлов.
Если вам нужно определить свою конечную точку с помощью async def— как вам может понадобиться await для некоторых других сопрограмм внутри вашего маршрута — тогда вам лучше использовать асинхронное чтение и запись содержимого, как показано в этом ответе. Более того, если вам нужно отправить дополнительные данные (например, JSON данные) вместе с загрузкой файла (ов), пожалуйста, взгляните на этот ответ. Я бы также предложил вам взглянуть на этот ответ, который объясняет разницу между def и async def конечными точками.
Загрузить один файл
app.py
from fastapi import File, UploadFile
@app.post("/upload") defupload(file: UploadFile = File(...)): try: contents = file.file.read() withopen(file.filename, 'wb') as f: f.write(contents) except Exception: return {"message": "There was an error uploading the file"} finally: file.file.close()
Как описано ранее и в этом ответе, если файл слишком велик, чтобы поместиться в память — например, если у вас 8 ГБ оперативной памяти, вы не сможете загрузить файл объемом 50 ГБ (не говоря уже о том, что доступная оперативная память всегда будет меньше общего объема, установленного на вашем компьютере, поскольку другие приложения будут использовать часть оперативной памяти) — вам следует загружать файл в память порциями и обрабатывать данные по одной порции за раз. Однако выполнение этого метода может занять больше времени, в зависимости от выбранного вами размера фрагмента — в примере ниже размер фрагмента составляет 1024 * 1024 байт (т.е. 1 МБ). Вы можете настроить размер фрагмента по желанию.
from fastapi import File, UploadFile
@app.post("/upload") defupload(file: UploadFile = File(...)): try: withopen(file.filename, 'wb') as f: while contents := file.file.read(1024 * 1024): f.write(contents) except Exception: return {"message": "There was an error uploading the file"} finally: file.file.close()
Другим вариантом было бы использовать shutil.copyfileobj(), который используется для копирования содержимого file-like объекта в другой file-like объект (также взгляните на этот ответ). По умолчанию данные считываются порциями, при этом размер буфера (чанка) по умолчанию равен 1 МБ (т.Е. 1024 * 1024 Байтам) для Windows и 64 КБ для других платформ, как показано в исходном коде здесь. Вы можете указать размер буфера, передав необязательный параметр length. Примечание: Если передано отрицательное length значение, вместо него будет прочитано все содержимое файла — смотрите f.read() также, который .copyfileobj() использует under the hoodle (как можно видеть в исходном коде здесь).
from fastapi import File, UploadFile import shutil
@app.post("/upload") defupload(file: UploadFile = File(...)): try: withopen(file.filename, 'wb') as f: shutil.copyfileobj(file.file, f) except Exception: return {"message": "There was an error uploading the file"} finally: file.file.close()
from fastapi import File, UploadFile from typing importList
@app.post("/upload") defupload(files: List[UploadFile] = File(...)): for file in files: try: contents = file.file.read() withopen(file.filename, 'wb') as f: f.write(contents) except Exception: return {"message": "There was an error uploading the file(s)"} finally: file.file.close()
return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}
Считывать файлы по частям
Как описано ранее в этом ответе, если вы ожидаете получить довольно большой файл (ы) и у вас недостаточно оперативной памяти для размещения всех данных от начала до конца, вам лучше загружать файл в память порциями, таким образом обрабатывая данные по одному блоку за раз (Примечание: отрегулируйте размер блока по желанию, ниже это 1024 * 1024 байты).
from fastapi import File, UploadFile from typing importList
@app.post("/upload") defupload(files: List[UploadFile] = File(...)): for file in files: try: withopen(file.filename, 'wb') as f: while contents := file.file.read(1024 * 1024): f.write(contents) except Exception: return {"message": "There was an error uploading the file(s)"} finally: file.file.close()
return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}
или, используя shutil.copyfileobj():
from fastapi import File, UploadFile from typing importList import shutil
@app.post("/upload") defupload(files: List[UploadFile] = File(...)): for file in files: try: withopen(file.filename, 'wb') as f: shutil.copyfileobj(file.file, f) except Exception: return {"message": "There was an error uploading the file(s)"} finally: file.file.close()
return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}