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 ์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.