FastAPI - Request Files

Kjjeddยท2026๋…„ 1์›” 13์ผ

FastAPI

๋ชฉ๋ก ๋ณด๊ธฐ
12/16
post-thumbnail

๐Ÿ“ Request Files โ€“ ํŒŒ์ผ ์—…๋กœ๋“œ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ์„ ํƒ ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋‹ค.

ํ”„๋กœํ•„ ์ด๋ฏธ์ง€, ์ฒจ๋ถ€ ํŒŒ์ผ, ๋ฌธ์„œ ์—…๋กœ๋“œ, ์ด๋ฏธ์ง€ ๋ถ„์„, ๋จธ์‹ ๋Ÿฌ๋‹ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘๊นŒ์ง€ ํ˜„๋Œ€ ์›น ์„œ๋น„์Šค์˜ ๊ฑฐ์˜ ๋ชจ๋“  ์˜์—ญ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค.

์ด๋ฒˆ ๊ธ€์—์„œ๋Š” FastAPI์—์„œ ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€, ์™œ UploadFile์„ ์จ์•ผ ํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  ์‹ค๋ฌด์—์„œ ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  ํ•ต์‹ฌ ๊ฐœ๋…๋งŒ ์ •๋ฆฌํ•œ๋‹ค.


๐Ÿ“Œ 1. ํŒŒ์ผ ์—…๋กœ๋“œ์˜ ๋ณธ์งˆ

ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ๋‹จ์ˆœํžˆ โ€œ๋ฐ์ดํ„ฐ ํ•˜๋‚˜ ๋ณด๋‚ด๊ธฐโ€๊ฐ€ ์•„๋‹ˆ๋‹ค.

ํŒŒ์ผ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง„๋‹ค.

  • ํ…์ŠคํŠธ๊ฐ€ ์•„๋‹ˆ๋ผ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋‹ค
  • ์šฉ๋Ÿ‰์ด ํด ์ˆ˜ ์žˆ๋‹ค
  • ํŒŒ์ผ๋ช…, ํƒ€์ž… ๊ฐ™์€ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๊ฐ€ ์ค‘์š”ํ•˜๋‹ค

๊ทธ๋ž˜์„œ ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” JSON์ด๋‚˜ Query Parameter๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค.

๐Ÿ’ก ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” ๋ฐ˜๋“œ์‹œ
multipart/form-data ์ธ์ฝ”๋”ฉ์„ ์‚ฌ์šฉํ•œ๋‹ค

โš™๏ธ 2. ํŒจํ‚ค์ง€ ์„ค์น˜

FastAPI์—์„œ ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ ค๋ฉด python-multipart ํŒจํ‚ค์ง€๊ฐ€ ํ•„์ˆ˜๋‹ค.

pip install python-multipart

์ด ํŒจํ‚ค์ง€๋Š” ์š”์ฒญ์„ ๋‹ค์Œ์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌํ•œ๋‹ค.

  • multipart/form-data ์š”์ฒญ ํŒŒ์‹ฑ
  • ํŒŒ์ผ๊ณผ ํ…์ŠคํŠธ ํ•„๋“œ ๋ถ„๋ฆฌ
  • FastAPI ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฃผ์ž…
โš ๏ธ ์ด ํŒจํ‚ค์ง€๊ฐ€ ์—†์œผ๋ฉด
ํŒŒ์ผ ์—…๋กœ๋“œ API๋Š” ์‹คํ–‰์กฐ์ฐจ ๋˜์ง€ ์•Š๋Š”๋‹ค

๐Ÿ“‚ 3. FastAPI์—์„œ ํŒŒ์ผ ๋ฐ›๋Š” ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•

FastAPI๋Š” ํŒŒ์ผ์„ ๋ฐ›๋Š” ๋ฐฉ์‹์„ ์˜๋„์ ์œผ๋กœ ๋‘ ๊ฐ€์ง€ ์ œ๊ณตํ•œ๋‹ค.

โ‘  bytes ๋ฐฉ์‹

from typing import Annotated
from fastapi import FastAPI, File

app = FastAPI()

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

์ด ๋ฐฉ์‹์˜ ํŠน์ง•์€ ๋ช…ํ™•ํ•˜๋‹ค.

  • ํŒŒ์ผ ์ „์ฒด๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œ๋จ
  • ๊ตฌํ˜„์€ ๋‹จ์ˆœ
  • ์ž‘์€ ํŒŒ์ผ์—๋งŒ ์ ํ•ฉ
๐Ÿšจ 10MB ํŒŒ์ผ โ†’ ๋ฉ”๋ชจ๋ฆฌ 10MB ์‚ฌ์šฉ
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ์—์„œ๋Š” ์œ„ํ—˜ํ•˜๋‹ค

โ‘ก UploadFile ๋ฐฉ์‹ (๊ถŒ์žฅ)

from fastapi import FastAPI, UploadFile

app = FastAPI()

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

์ด ๋ฐฉ์‹์ด ์‹ค๋ฌด ํ‘œ์ค€!

  • ํŒŒ์ผ์„ ๋ฐ”๋กœ ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ์ง€ ์•Š์Œ
  • ์ž๋™์œผ๋กœ ์ž„์‹œ ํŒŒ์ผ ๊ด€๋ฆฌ
  • ํŒŒ์ผ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ œ๊ณต
โœ… ํŠน๋ณ„ํ•œ ์ด์œ ๊ฐ€ ์—†๋‹ค๋ฉด
UploadFile์„ ์‚ฌ์šฉํ•˜์ž

๐Ÿ“Š bytes vs UploadFile ๋น„๊ต

๊ตฌ๋ถ„ bytes UploadFile
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ ํŒŒ์ผ ์ „์ฒด ํ•„์š”ํ•œ ๋งŒํผ๋งŒ
๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ โŒ ์œ„ํ—˜ โœ… ์•ˆ์ „
๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์—†์Œ ํŒŒ์ผ๋ช…, ํƒ€์ž… ์ œ๊ณต
์‹ค๋ฌด ์ ํ•ฉ์„ฑ ๋‚ฎ์Œ ๋งค์šฐ ๋†’์Œ

๐Ÿง  4. multipart/form-data์˜ ๋™์ž‘ ์›๋ฆฌ

ํŒŒ์ผ ์—…๋กœ๋“œ ์š”์ฒญ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ ‡๊ฒŒ ํ˜๋Ÿฌ๊ฐ„๋‹ค.

[๋ธŒ๋ผ์šฐ์ € ํŒŒ์ผ ์„ ํƒ]
        โ†“
POST ์š”์ฒญ: Content-Type: multipart/form-data
        โ†“
python-multipart ํŒŒ์‹ฑ
        โ†“
ํŒŒ์ผ / ํ…์ŠคํŠธ ๋ถ„๋ฆฌ
        โ†“
FastAPI ํŒŒ๋ผ๋ฏธํ„ฐ ์ฃผ์ž…

์ด๋•Œ ์ค‘์š”ํ•œ ๊ทœ์น™์ด ํ•˜๋‚˜ ์žˆ๋‹ค.

โš ๏ธ ํŒŒ์ผ(Form/File)๊ณผ JSON Body๋Š”
๊ฐ™์€ ์š”์ฒญ์—์„œ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค

์ด๊ฑด FastAPI ์ œ์•ฝ์ด ์•„๋‹ˆ๋ผ HTTP ํ”„๋กœํ† ์ฝœ ๊ทœ์น™


๐Ÿ“ฆ 5. UploadFile ๊ฐ์ฒด ์™„์ „ ํ•ด๋ถ€

๐Ÿ”น UploadFile ์†์„ฑ (Attributes)

@app.post("/upload/")
async def upload(file: UploadFile):
    print(file.filename)
    print(file.content_type)
    print(file.file)
์†์„ฑ ์„ค๋ช…
filename ์›๋ณธ ํŒŒ์ผ๋ช…
content_type MIME ํƒ€์ž… (image/png, application/pdf ๋“ฑ)
file SpooledTemporaryFile ๊ฐ์ฒด

๐Ÿ”น UploadFile ๋ฉ”์„œ๋“œ (Methods)

๋ชจ๋“  ๋ฉ”์„œ๋“œ๋Š” async๋‹ค.

@app.post("/upload/")
async def upload(file: UploadFile):
    contents = await file.read()
    await file.seek(0)
    contents_again = await file.read()
    await file.close()
๋ฉ”์„œ๋“œ ์—ญํ• 
read() ํŒŒ์ผ ์ฝ๊ธฐ
seek(0) ์ฝ๊ธฐ ์œ„์น˜ ์ดˆ๊ธฐํ™”
close() ํŒŒ์ผ ๋‹ซ๊ธฐ
๐Ÿ’ก ํŒŒ์ผ์„ ํ•œ ๋ฒˆ ์ฝ์œผ๋ฉด
ํฌ์ธํ„ฐ๊ฐ€ ๋์œผ๋กœ ์ด๋™ํ•œ๋‹ค
โ†’ ๋‹ค์‹œ ์ฝ์œผ๋ ค๋ฉด seek(0)

๐Ÿ“Œ 6. ์‹ค๋ฌด์—์„œ ๊ผญ ์•Œ์•„์•ผ ํ•  ํŒจํ„ด

โœ” ์„ ํƒ์  ํŒŒ์ผ ์—…๋กœ๋“œ

@app.post("/upload/")
async def upload(file: UploadFile | None = None):
    if not file:
        return {"message": "ํŒŒ์ผ์ด ์—†์Šต๋‹ˆ๋‹ค"}
    return {"filename": file.filename}

ํ•ต์‹ฌ์€ ๋‘ ๊ฐ€์ง€๋‹ค.

  • | None โ†’ ํƒ€์ž… ํžŒํŠธ
  • = None โ†’ ๊ธฐ๋ณธ๊ฐ’

โœ” ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ

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

HTML์—์„œ๋Š” multiple ์†์„ฑ์ด ํ•„์š”ํ•˜๋‹ค.

<input type="file" name="files" multiple>

๐Ÿ“Œ Form Data์˜ ํ•ต์‹ฌ ์—ญํ• 

Form Data๋Š” ๋‹ค์Œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค.

  • ํŒŒ์ผ์€ ๋ฐ”์ด๋„ˆ๋ฆฌ ๋ฐ์ดํ„ฐ๋‹ค
  • JSON์€ ๋ฐ”์ด๋„ˆ๋ฆฌ์— ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค
  • ํ…์ŠคํŠธ + ํŒŒ์ผ์„ ๊ฐ™์ด ๋ณด๋‚ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค

๊ทธ๋ž˜์„œ HTTP๋Š” ํŒŒ์ผ ์ „์†ก์„ ์œ„ํ•ด multipart/form-data๋ผ๋Š” ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹์„ ์ œ๊ณตํ•œ๋‹ค.

๐Ÿ” multipart/form-data ์š”์ฒญ ํ๋ฆ„

ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ํฌํ•จ๋œ ์š”์ฒญ์€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋‹ค์Œ ์ˆœ์„œ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.

[ํด๋ผ์ด์–ธํŠธ (๋ธŒ๋ผ์šฐ์ €)]
    โ”‚
    โ”‚  ํŒŒ์ผ ์„ ํƒ
    โ–ผ
POST ์š”์ฒญ
Content-Type: multipart/form-data
    โ”‚
    โ–ผ
python-multipart
(์š”์ฒญ ํŒŒ์‹ฑ)
    โ”‚
    โ”œโ”€ ํ…์ŠคํŠธ ํ•„๋“œ
    โ””โ”€ ํŒŒ์ผ ๋ฐ์ดํ„ฐ
    โ”‚
    โ–ผ
FastAPI
(File / UploadFile)

โœ… ํ•ต์‹ฌ ์ •๋ฆฌ

  • ํŒŒ์ผ ์—…๋กœ๋“œ๋Š” multipart/form-data๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค
  • JSON Body์™€ File/Form์€ ๋™์‹œ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค
  • ์‹ค๋ฌด์—์„œ๋Š” UploadFile์ด ํ‘œ์ค€์ด๋‹ค
  • ๋ฉ”๋ชจ๋ฆฌ, ์„ฑ๋Šฅ, ์•ˆ์ •์„ฑ์„ ๋ชจ๋‘ ๊ณ ๋ คํ•ด์•ผ ํ•œ๋‹ค
ํŒŒ์ผ ์—…๋กœ๋“œ๋ฅผ ์ดํ•ดํ–ˆ๋‹ค๋Š” ๊ฑด
HTTP ยท ๋ฉ”๋ชจ๋ฆฌ ยท ์„œ๋ฒ„ ์„ค๊ณ„๋ฅผ ํ•จ๊ป˜ ์ดํ•ดํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค๋Š” ๋œป์ด๋‹ค.
profile
Gongbuhaja

0๊ฐœ์˜ ๋Œ“๊ธ€