Перейти до змісту

Запит файлів

Ви можете визначити файли, які будуть завантажуватися клієнтом, використовуючи 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}
🤓 Other versions and variants
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}

Tip

Prefer to use the Annotated version if possible.

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

Створіть параметри файлів так само як Ви б створювали 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}
🤓 Other versions and variants
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}

Tip

Prefer to use the Annotated version if possible.

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}
🤓 Other versions and variants
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}

Tip

Prefer to use the Annotated version if possible.

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 асинхронний файловий інтерфейс interface.
  • Він надає фактичний об'єкт Python SpooledTemporaryFile, який можна передавати безпосередньо іншим бібліотекам.

UploadFile

UploadFile має такі атрибути:

  • filename: Рядок str з оригінальною назвою файлу, який був завантажений (наприклад, myimage.jpg).
  • content_type: Рядок str з MIME-типом (наприклад, image/jpeg).
  • file: Об'єкт SpooledTemporaryFile (файлоподібний об'єкт). Це фактичний файловий об'єкт Python, який можна безпосередньо передавати іншим функціям або бібліотекам, що очікують "файлоподібний" об'єкт.

UploadFile має такі асинхронні async методи. Вони викликають відповідні методи файлу під капотом (використовуючи внутрішній SpooledTemporaryFile).

  • write(data): Записує data (str або bytes) у файл.
  • read(size): Читає size (int) байтів/символів з файлу.
  • seek(offset): Переміщується до позиції offset (int) у файлі.
    • Наприклад, await myfile.seek(0) поверне курсор на початок файлу.
    • This is especially useful if you run await myfile.read() once and then need to read the contents again. Це особливо корисно, якщо Ви виконуєте await await myfile.read() один раз, а потім потрібно знову прочитати вміст.
  • close(): Закриває файл.

Оскільки всі ці методи є асинхронними async, Вам потрібно використовувати "await":

Наприклад, всередині async функції обробки шляху Ви можете отримати вміст за допомогою:

contents = await myfile.read()
Якщо Ви знаходитесь у звичайній def функції обробки шляху, Ви можете отримати доступ до UploadFile.file безпосередньо, наприклад:

contents = myfile.file.read()

Технічні деталі async

Коли Ви використовуєте async методи, FastAPI виконує файлові операції у пулі потоків та очікує їх завершення.

Технічні деталі Starlette

UploadFile у 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 щодо 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}
🤓 Other versions and variants
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}

Tip

Prefer to use the Annotated version if possible.

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}

Tip

Prefer to use the Annotated version if possible.

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}
🤓 Other versions and variants
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}

Tip

Prefer to use the Annotated version if possible.

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}

Завантаження Кількох Файлів

Можна завантажувати кілька файлів одночасно.

Вони будуть пов’язані з одним і тим самим "form field", який передається у вигляді "form data".

Щоб це реалізувати, потрібно оголосити список 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)
🤓 Other versions and variants
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)

Tip

Prefer to use the Annotated version if possible.

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)

Tip

Prefer to use the Annotated version if possible.

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.

Технічні деталі

Ви також можете використати 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)
🤓 Other versions and variants
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)

Tip

Prefer to use the Annotated version if possible.

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)

Tip

Prefer to use the Annotated version if possible.

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, щоб оголошувати файли для завантаження у запитах, які надсилаються у вигляді form data.