Skip to content

파일 μš”μ²­

File을 μ‚¬μš©ν•˜μ—¬ ν΄λΌμ΄μ–ΈνŠΈκ°€ μ—…λ‘œλ“œν•  νŒŒμΌλ“€μ„ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

정보

μ—…λ‘œλ“œλœ νŒŒμΌμ„ 전달받기 μœ„ν•΄ λ¨Όμ € python-multipartλ₯Ό μ„€μΉ˜ν•΄μ•Όν•©λ‹ˆλ‹€.

μ˜ˆμ‹œ) pip install python-multipart.

μ—…λ‘œλ“œλœ νŒŒμΌλ“€μ€ "폼 데이터"의 ν˜•νƒœλ‘œ μ „μ†‘λ˜κΈ° λ•Œλ¬Έμ— 이 μž‘μ—…μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

File μž„ν¬νŠΈ

fastapi μ—μ„œ File κ³Ό UploadFile 을 μž„ν¬νŠΈ ν•©λ‹ˆλ‹€:

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 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 μœΌλ‘œλΆ€ν„° 직접 μƒμ†λœ ν΄λž˜μŠ€μž…λ‹ˆλ‹€.

ν•˜μ§€λ§Œ fastapiλ‘œλΆ€ν„° Query, Path, File 등을 μž„ν¬νŠΈ ν•  λ•Œ, 이것듀은 νŠΉλ³„ν•œ ν΄λž˜μŠ€λ“€μ„ λ°˜ν™˜ν•˜λŠ” ν•¨μˆ˜λΌλŠ” 것을 κΈ°μ–΅ν•˜κΈ° λ°”λžλ‹ˆλ‹€.

팁

File의 본문을 μ„ μ–Έν•  λ•Œ, λ§€κ°œλ³€μˆ˜κ°€ 쿼리 λ§€κ°œλ³€μˆ˜ λ˜λŠ” λ³Έλ¬Έ(JSON) λ§€κ°œλ³€μˆ˜λ‘œ ν•΄μ„λ˜λŠ” 것을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ File 을 μ‚¬μš©ν•΄μ•Όν•©λ‹ˆλ‹€.

νŒŒμΌλ“€μ€ "폼 데이터"의 ν˜•νƒœλ‘œ μ—…λ‘œλ“œ λ©λ‹ˆλ‹€.

경둜 μž‘λ™ ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜λ₯Ό bytes 둜 μ„ μ–Έν•˜λŠ” 경우 FastAPIλŠ” νŒŒμΌμ„ 읽고 bytes ν˜•νƒœμ˜ λ‚΄μš©μ„ μ „λ‹¬ν•©λ‹ˆλ‹€.

이것은 전체 λ‚΄μš©μ΄ λ©”λͺ¨λ¦¬μ— μ €μž₯λœλ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€λŠ” κ±Έ μ—Όλ‘ν•˜κΈ° λ°”λžλ‹ˆλ‹€. μ΄λŠ” μž‘μ€ 크기의 νŒŒμΌλ“€μ— μ ν•©ν•©λ‹ˆλ‹€.

μ–΄λ–€ κ²½μš°μ—λŠ” UploadFile 을 μ‚¬μš©ν•˜λŠ” 것이 더 μœ λ¦¬ν•©λ‹ˆλ‹€.

File λ§€κ°œλ³€μˆ˜μ™€ UploadFile

File λ§€κ°œλ³€μˆ˜λ₯Ό UploadFile νƒ€μž…μœΌλ‘œ μ •μ˜ν•©λ‹ˆλ‹€:

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-like async μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ°–κ³  μžˆμŠ΅λ‹ˆλ‹€.
  • file-like objectλ₯Ό ν•„μš”λ‘œν•˜λŠ” λ‹€λ₯Έ λΌμ΄λΈŒλŸ¬λ¦¬μ— μ§μ ‘μ μœΌλ‘œ 전달할 수 μžˆλŠ” 파이썬 SpooledTemporaryFile 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

UploadFile

UploadFile 은 λ‹€μŒκ³Ό 같은 μ–΄νŠΈλ¦¬λ·°νŠΈκ°€ μžˆμŠ΅λ‹ˆλ‹€:

  • filename : λ¬Έμžμ—΄(str)둜 된 μ—…λ‘œλ“œλœ 파일의 파일λͺ…μž…λ‹ˆλ‹€ (예: myimage.jpg).
  • content_type : λ¬Έμžμ—΄(str)둜 된 파일 ν˜•μ‹(MIME type / media type)μž…λ‹ˆλ‹€ (예:Β image/jpeg).
  • file : SpooledTemporaryFile (파일λ₯˜ 객체)μž…λ‹ˆλ‹€. 이것은 "파일λ₯˜" 객체λ₯Ό ν•„μš”λ‘œν•˜λŠ” λ‹€λ₯Έ λΌμ΄λΈŒλŸ¬λ¦¬μ— μ§μ ‘μ μœΌλ‘œ 전달할 수 μžˆλŠ” μ‹€μ§ˆμ μΈ 파이썬 νŒŒμΌμž…λ‹ˆλ‹€.

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의 UploadFile 을 μ§μ ‘μ μœΌλ‘œ μƒμ†λ°›μ§€λ§Œ, Pydantic 및 FastAPI의 λ‹€λ₯Έ λΆ€λΆ„λ“€κ³Όμ˜ ν˜Έν™˜μ„±μ„ μœ„ν•΄ ν•„μš”ν•œ 뢀뢄듀이 μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

"폼 데이터"λž€

HTML의 폼듀(<form></form>)이 μ„œλ²„μ— 데이터λ₯Ό μ „μ†‘ν•˜λŠ” 방식은 λŒ€κ°œ 데이터에 JSONκ³ΌλŠ” λ‹€λ₯Έ "νŠΉλ³„ν•œ" 인코딩을 μ‚¬μš©ν•©λ‹ˆλ‹€.

FastAPIλŠ” JSON λŒ€μ‹  μ˜¬λ°”λ₯Έ μœ„μΉ˜μ—μ„œ 데이터λ₯Ό 읽을 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

기술적 세뢀사항

폼의 λ°μ΄ν„°λŠ” 파일이 ν¬ν•¨λ˜μ§€ μ•Šμ€ 경우 일반적으둜 "λ―Έλ””μ–΄ μœ ν˜•" application/x-www-form-urlencoded 을 μ‚¬μš©ν•΄ 인코딩 λ©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 파일이 ν¬ν•¨λœ 경우, multipart/form-data둜 μΈμ½”λ”©λ©λ‹ˆλ‹€. File을 μ‚¬μš©ν•˜μ˜€λ‹€λ©΄, FastAPIλŠ” 본문의 μ ν•©ν•œ λΆ€λΆ„μ—μ„œ νŒŒμΌμ„ 가져와야 ν•œλ‹€λŠ” 것을 μΈμ§€ν•©λ‹ˆλ‹€.

인코딩과 폼 ν•„λ“œμ— λŒ€ν•΄ 더 μ•Œκ³ μ‹Άλ‹€λ©΄, POST에 κ΄€ν•œMDNμ›Ή λ¬Έμ„œ λ₯Ό μ°Έκ³ ν•˜κΈ° λ°”λžλ‹ˆλ‹€,.

주의

λ‹€μˆ˜μ˜ File κ³Ό Form λ§€κ°œλ³€μˆ˜λ₯Ό ν•œ 경둜 μž‘λ™μ— μ„ μ–Έν•˜λŠ” 것이 κ°€λŠ₯ν•˜μ§€λ§Œ, μš”μ²­μ˜ 본문이 application/json κ°€ μ•„λ‹Œ multipart/form-data 둜 인코딩 되기 λ•Œλ¬Έμ— JSON으둜 λ°›μ•„μ•Όν•˜λŠ” Body ν•„λ“œλ₯Ό ν•¨κ»˜ μ„ μ–Έν•  μˆ˜λŠ” μ—†μŠ΅λ‹ˆλ‹€.

μ΄λŠ” FastAPI의 ν•œκ³„κ°€ μ•„λ‹ˆλΌ, HTTP ν”„λ‘œν† μ½œμ— μ˜ν•œ κ²ƒμž…λ‹ˆλ‹€.

닀쀑 파일 μ—…λ‘œλ“œ

μ—¬λŸ¬ νŒŒμΌμ„ λ™μ‹œμ— μ—…λ‘œλ“œ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

그듀은 "폼 데이터"λ₯Ό μ‚¬μš©ν•˜μ—¬ μ „μ†‘λœ λ™μΌν•œ "폼 ν•„λ“œ"에 μ—°κ²°λ©λ‹ˆλ‹€.

이 κΈ°λŠ₯을 μ‚¬μš©ν•˜κΈ° μœ„ν•΄ , bytes 의 List λ˜λŠ” UploadFile λ₯Ό μ„ μ–Έν•˜κΈ° λ°”λžλ‹ˆλ‹€:

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)

μ„ μ–Έν•œλŒ€λ‘œ, bytes 의 list λ˜λŠ” UploadFile 듀을 전솑받을 κ²ƒμž…λ‹ˆλ‹€.

μ°Έκ³ 

2019λ…„ 4μ›” 14일뢀터 Swagger UIκ°€ ν•˜λ‚˜μ˜ 폼 ν•„λ“œλ‘œ λ‹€μˆ˜μ˜ νŒŒμΌμ„ μ—…λ‘œλ“œν•˜λŠ” 것을 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 더 λ§Žμ€ 정보λ₯Ό μ›ν•˜λ©΄, #4276κ³Ό #3641을 μ°Έκ³ ν•˜μ„Έμš”.

κ·ΈλŸΌμ—λ„, FastAPIλŠ” ν‘œμ€€ Open APIλ₯Ό μ‚¬μš©ν•΄ 이미 ν˜Έν™˜μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ Swagger UI λ˜λŠ” 기타 κ·Έ μ™Έμ˜ OpenAPIλ₯Ό μ§€μ›ν•˜λŠ” 툴이 닀쀑 파일 μ—…λ‘œλ“œλ₯Ό μ§€μ›ν•˜λŠ” 경우, 이듀은 FastAPI와 ν˜Έν™˜λ©λ‹ˆλ‹€.

기술적 세뢀사항

from starlette.responses import HTMLResponse μ—­μ‹œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

FastAPIλŠ” 개발자의 편의λ₯Ό μœ„ν•΄ fastapi.responses 와 λ™μΌν•œ starlette.responses 도 μ œκ³΅ν•©λ‹ˆλ‹€. ν•˜μ§€λ§Œ λŒ€λΆ€λΆ„μ˜ 응닡듀은 Starletteλ‘œλΆ€ν„° 직접 μ œκ³΅λ©λ‹ˆλ‹€.

μš”μ•½

폼 λ°μ΄ν„°λ‘œμ¨ μž…λ ₯ λ§€κ°œλ³€μˆ˜λ‘œ μ—…λ‘œλ“œν•  νŒŒμΌμ„ μ„ μ–Έν•  경우 File 을 μ‚¬μš©ν•˜κΈ° λ°”λžλ‹ˆλ‹€.