Перейти к содержанию

Загрузка файлов

Используя класс File, мы можем позволить клиентам загружать файлы.

Дополнительная информация

Чтобы получать загруженные файлы, сначала установите python-multipart.

Например: pip install python-multipart.

Это связано с тем, что загружаемые файлы передаются как данные формы.

Импорт File

Импортируйте File и UploadFile из модуля fastapi:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File()):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Определите параметры File

Создайте параметры File так же, как вы это делаете для Body или Form:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File()):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Дополнительная информация

File - это класс, который наследуется непосредственно от Form.

Но помните, что когда вы импортируете Query, Path, File и другие из fastapi, на самом деле это функции, которые возвращают специальные классы.

Подсказка

Для объявления тела файла необходимо использовать File, поскольку в противном случае параметры будут интерпретироваться как параметры запроса или параметры тела (JSON).

Файлы будут загружены как данные формы.

Если вы объявите тип параметра у функции операции пути как bytes, то FastAPI прочитает файл за вас, и вы получите его содержимое в виде bytes.

Следует иметь в виду, что все содержимое будет храниться в памяти. Это хорошо подходит для небольших файлов.

Однако возможны случаи, когда использование UploadFile может оказаться полезным.

Загрузка файла с помощью UploadFile

Определите параметр файла с типом UploadFile:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File()):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}

Использование UploadFile имеет ряд преимуществ перед bytes:

  • Использовать File() в значении параметра по умолчанию не обязательно.
  • При этом используется "буферный" файл:
    • Файл, хранящийся в памяти до максимального предела размера, после преодоления которого он будет храниться на диске.
  • Это означает, что он будет хорошо работать с большими файлами, такими как изображения, видео, большие бинарные файлы и т.д., не потребляя при этом всю память.
  • Из загруженного файла можно получить метаданные.
  • Он реализует file-like async интерфейс.
  • Он предоставляет реальный объект Python SpooledTemporaryFile который вы можете передать непосредственно другим библиотекам, которые ожидают файл в качестве объекта.

UploadFile

UploadFile имеет следующие атрибуты:

  • filename: Строка str с исходным именем файла, который был загружен (например, myimage.jpg).
  • content_type: Строка str с типом содержимого (MIME type / media type) (например, image/jpeg).
  • file: SpooledTemporaryFile (a file-like объект). Это фактический файл Python, который можно передавать непосредственно другим функциям или библиотекам, ожидающим файл в качестве объекта.

UploadFile имеет следующие методы async. Все они вызывают соответствующие файловые методы (используя внутренний SpooledTemporaryFile).

  • write(data): Записать данные data (str или bytes) в файл.
  • read(size): Прочитать количество size (int) байт/символов из файла.
  • seek(offset): Перейти к байту на позиции offset (int) в файле.
    • Наример, await myfile.seek(0) перейдет к началу файла.
    • Это особенно удобно, если вы один раз выполнили команду await myfile.read(), а затем вам нужно прочитать содержимое файла еще раз.
  • close(): Закрыть файл.

Поскольку все эти методы являются async методами, вам следует использовать "await" вместе с ними.

Например, внутри async функции операции пути можно получить содержимое с помощью:

contents = await myfile.read()

Если вы находитесь внутри обычной def функции операции пути, можно получить прямой доступ к файлу UploadFile.file, например:

contents = myfile.file.read()

Технические детали async

При использовании методов async FastAPI запускает файловые методы в пуле потоков и ожидает их.

Технические детали Starlette

FastAPI наследует UploadFile непосредственно из Starlette, но добавляет некоторые детали для совместимости с Pydantic и другими частями FastAPI.

Про данные формы ("Form Data")

Способ, которым HTML-формы (<form></form>) отправляют данные на сервер, обычно использует "специальную" кодировку для этих данных, отличную от JSON.

FastAPI позаботится о том, чтобы считать эти данные из нужного места, а не из JSON.

Технические детали

Данные из форм обычно кодируются с использованием "media type" application/x-www-form-urlencoded когда он не включает файлы.

Но когда форма включает файлы, она кодируется как multipart/form-data. Если вы используете File, FastAPI будет знать, что ему нужно получить файлы из нужной части тела.

Если вы хотите узнать больше об этих кодировках и полях форм, перейдите по ссылке MDN web docs for POST.

Внимание

В операции функции операции пути можно объявить несколько параметров File и Form, но нельзя также объявлять поля Body, которые предполагается получить в виде JSON, поскольку тело запроса будет закодировано с помощью multipart/form-data, а не application/json.

Это не является ограничением FastAPI, это часть протокола HTTP.

Необязательная загрузка файлов

Вы можете сделать загрузку файла необязательной, используя стандартные аннотации типов и установив значение по умолчанию None:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes | None, File()] = None):
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}
from typing import Annotated, Union

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[Union[bytes, None], File()] = None):
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: Union[UploadFile, None] = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}
from typing import Union

from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[Union[bytes, None], File()] = None):
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: Union[UploadFile, None] = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes | None = File(default=None)):
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile | None = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from typing import Union

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Union[bytes, None] = File(default=None)):
    if not file:
        return {"message": "No file sent"}
    else:
        return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: Union[UploadFile, None] = None):
    if not file:
        return {"message": "No upload file sent"}
    else:
        return {"filename": file.filename}

UploadFile с дополнительными метаданными

Вы также можете использовать File() вместе с UploadFile, например, для установки дополнительных метаданных:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(
    file: Annotated[UploadFile, File(description="A file read as UploadFile")],
):
    return {"filename": file.filename}
from fastapi import FastAPI, File, UploadFile
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_file(file: Annotated[bytes, File(description="A file read as bytes")]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(
    file: Annotated[UploadFile, File(description="A file read as UploadFile")],
):
    return {"filename": file.filename}

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File(description="A file read as bytes")):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(
    file: UploadFile = File(description="A file read as UploadFile"),
):
    return {"filename": file.filename}

Загрузка нескольких файлов

Можно одновременно загружать несколько файлов.

Они будут связаны с одним и тем же "полем формы", отправляемым с помощью данных формы.

Для этого необходимо объявить список bytes или UploadFile:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(files: Annotated[list[bytes], File()]):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)
from typing import List

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_files(files: Annotated[List[bytes], File()]):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(files: list[bytes] = File()):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: list[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from typing import List

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(files: List[bytes] = File()):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(files: List[UploadFile]):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Вы получите, как и было объявлено, список list из bytes или UploadFile.

Technical Details

Можно также использовать from starlette.responses import HTMLResponse.

FastAPI предоставляет тот же starlette.responses, что и fastapi.responses, просто для удобства разработчика. Однако большинство доступных ответов поступает непосредственно из Starlette.

Загрузка нескольких файлов с дополнительными метаданными

Так же, как и раньше, вы можете использовать File() для задания дополнительных параметров, даже для UploadFile:

from typing import Annotated

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(
    files: Annotated[list[bytes], File(description="Multiple files as bytes")],
):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(
    files: Annotated[
        list[UploadFile], File(description="Multiple files as UploadFile")
    ],
):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)
from typing import List

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse
from typing_extensions import Annotated

app = FastAPI()


@app.post("/files/")
async def create_files(
    files: Annotated[List[bytes], File(description="Multiple files as bytes")],
):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(
    files: Annotated[
        List[UploadFile], File(description="Multiple files as UploadFile")
    ],
):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(
    files: list[bytes] = File(description="Multiple files as bytes"),
):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(
    files: list[UploadFile] = File(description="Multiple files as UploadFile"),
):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Подсказка

Предпочтительнее использовать версию с аннотацией, если это возможно.

from typing import List

from fastapi import FastAPI, File, UploadFile
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.post("/files/")
async def create_files(
    files: List[bytes] = File(description="Multiple files as bytes"),
):
    return {"file_sizes": [len(file) for file in files]}


@app.post("/uploadfiles/")
async def create_upload_files(
    files: List[UploadFile] = File(description="Multiple files as UploadFile"),
):
    return {"filenames": [file.filename for file in files]}


@app.get("/")
async def main():
    content = """
<body>
<form action="/files/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
<form action="/uploadfiles/" enctype="multipart/form-data" method="post">
<input name="files" type="file" multiple>
<input type="submit">
</form>
</body>
    """
    return HTMLResponse(content=content)

Резюме

Используйте File, bytes и UploadFile для работы с файлами, которые будут загружаться и передаваться в виде данных формы.