콘텐츠로 이동

파일 요청

File을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있습니다.

정보

업로드된 파일을 전달받기 위해 먼저 python-multipart를 설치해야합니다.

가상 환경을 생성하고, 활성화한 다음, 예를 들어 다음과 같이 설치하세요:

$ pip install python-multipart

업로드된 파일들은 "폼 데이터"의 형태로 전송되기 때문에 이 작업이 필요합니다.

File 임포트

fastapi 에서 FileUploadFile 을 임포트 합니다:

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

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 매개변수 정의

BodyForm 과 동일한 방식으로 파일의 매개변수를 생성합니다:

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

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}

정보

FileForm 으로부터 직접 상속된 클래스입니다.

하지만 fastapi로부터 Query, Path, File 등을 임포트 할 때, 이것들은 특별한 클래스들을 반환하는 함수라는 것을 기억하기 바랍니다.

File의 본문을 선언할 때, 매개변수가 쿼리 매개변수 또는 본문(JSON) 매개변수로 해석되는 것을 방지하기 위해 File 을 사용해야합니다.

파일들은 "폼 데이터"의 형태로 업로드 됩니다.

경로 처리 함수의 매개변수를 bytes 로 선언하는 경우 FastAPI는 파일을 읽고 bytes 형태의 내용을 전달합니다.

이것은 전체 내용이 메모리에 저장된다는 것을 의미한다는 걸 염두하기 바랍니다. 이는 작은 크기의 파일들에 적합합니다.

어떤 경우에는 UploadFile 을 사용하는 것이 더 유리합니다.

UploadFile을 사용하는 File 매개변수

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()]):
    return {"file_size": len(file)}


@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    return {"filename": file.filename}
🤓 Other versions and variants

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 async 인터페이스를 갖고 있습니다.
  • file-like object를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 파이썬 SpooledTemporaryFile 객체를 반환합니다.

UploadFile

UploadFile 은 다음과 같은 어트리뷰트가 있습니다:

  • filename : 문자열(str)로 된 업로드된 파일의 파일명입니다 (예: myimage.jpg).
  • content_type : 문자열(str)로 된 파일 형식(MIME type / media type)입니다 (예: image/jpeg).
  • file : SpooledTemporaryFile (a file-like object)입니다. 이것은 "file-like" 객체를 필요로하는 다른 함수나 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일 객체입니다.

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 기술 세부사항

FastAPIUploadFileStarletteUploadFile 을 직접적으로 상속받지만, Pydantic 및 FastAPI의 다른 부분들과의 호환성을 위해 필요한 부분들이 추가되었습니다.

"폼 데이터"란

HTML의 폼들(<form></form>)이 서버에 데이터를 전송하는 방식은 대개 데이터에 JSON과는 다른 "특별한" 인코딩을 사용합니다.

FastAPI는 JSON 대신 올바른 위치에서 데이터를 읽을 수 있도록 합니다.

기술 세부사항

폼의 데이터는 파일이 포함되지 않은 경우 일반적으로 "미디어 유형" application/x-www-form-urlencoded 을 사용해 인코딩 됩니다.

하지만 파일이 포함된 경우, multipart/form-data로 인코딩됩니다. File을 사용하였다면, FastAPI는 본문의 적합한 부분에서 파일을 가져와야 한다는 것을 인지합니다.

인코딩과 폼 필드에 대해 더 알고싶다면, MDN web docs for POST를 참고하기 바랍니다.

경고

다수의 FileForm 매개변수를 한 경로 처리에 선언하는 것이 가능하지만, 요청의 본문이 application/json 가 아닌 multipart/form-data 로 인코딩 되기 때문에 JSON으로 받아야하는 Body 필드를 함께 선언할 수는 없습니다.

이는 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}

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

추가 메타데이터를 설정하기 위해 예를 들어 UploadFile과 함께 File()을 사용할 수도 있습니다:

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

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}

다중 파일 업로드

여러 파일을 동시에 업로드 할 수 있습니다.

그들은 "폼 데이터"를 사용하여 전송된 동일한 "폼 필드"에 연결됩니다.

이 기능을 사용하기 위해 , bytesList 또는 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

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)

선언한대로, byteslist 또는 UploadFile 들을 전송받을 것입니다.

기술 세부사항

from starlette.responses import HTMLResponse 역시 사용할 수 있습니다.

FastAPI는 개발자의 편의를 위해 fastapi.responses 와 동일한 starlette.responses 도 제공합니다. 하지만 대부분의 응답들은 Starlette로부터 직접 제공됩니다.

추가 메타데이터를 포함한 다중 파일 업로드

이전과 같은 방식으로 UploadFile에 대해서도 File()을 사용해 추가 매개변수를 설정할 수 있습니다:

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

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)

요약

File, bytes, UploadFile을 사용하여 폼 데이터로 전송되는 요청에서 업로드할 파일을 선언하세요.