FastAPIμ κ°λ ₯ν λ©ν°ννΈ νΌ νμΌ μ λ‘λ κΈ°λ₯μ νμ©ν΄ 보μΈμ. μ΄ μ’ ν© κ°μ΄λλ κΈλ‘λ² κ°λ°μλ₯Ό μν λͺ¨λ² μ¬λ‘, μ€λ₯ μ²λ¦¬ λ° κ³ κΈ κΈ°μ μ λ€λ£Ήλλ€.
FastAPI νμΌ μ λ‘λ λ§μ€ν°νκΈ°: λ©ν°ννΈ νΌ μ²λ¦¬ μ¬μΈ΅ λΆμ
νλ μΉ μ ν리μΌμ΄μ μμ νμΌ μ λ‘λ μ²λ¦¬ κΈ°λ₯μ νμμ μΈ μꡬμ¬νμ λλ€. μ¬μ©μκ° νλ‘ν μ¬μ§μ μ μΆνλ , μ²λ¦¬λ₯Ό μν΄ λ¬Έμλ₯Ό μ λ‘λνλ , 곡μ λ₯Ό μν΄ λ―Έλμ΄λ₯Ό μ¬λ¦¬λ , κ²¬κ³ νκ³ ν¨μ¨μ μΈ νμΌ μ λ‘λ λ©μ»€λμ¦μ λ§€μ° μ€μν©λλ€. κ³ μ±λ₯ νμ΄μ¬ μΉ νλ μμν¬μΈ FastAPIλ μ΄ λΆμΌμμ νμνλ©°, HTTPλ₯Ό ν΅ν΄ νμΌμ μ μ‘νλ νμ€ λ°©μμΈ λ©ν°ννΈ νΌ λ°μ΄ν°λ₯Ό κ΄λ¦¬νλ κ°μνλ λ°©λ²μ μ 곡ν©λλ€. μ΄ μ’ ν© κ°μ΄λλ κΈ°λ³Έμ μΈ κ΅¬νλΆν° κ³ κΈ κ³ λ €μ¬νκΉμ§ FastAPI νμΌ μ λ‘λμ 볡μ‘ν λΆλΆμ μλ΄νμ¬, μ¬λ¬λΆμ΄ κΈλ‘λ² μ¬μ©μλ₯Ό μν κ°λ ₯νκ³ νμ₯ κ°λ₯ν APIλ₯Ό μμ μκ² κ΅¬μΆν μ μλλ‘ λ³΄μ₯ν©λλ€.
λ©ν°ννΈ νΌ λ°μ΄ν° μ΄ν΄νκΈ°
FastAPIμ ꡬνμ λν΄ μμ보기 μ μ, λ©ν°ννΈ νΌ λ°μ΄ν°κ° 무μμΈμ§ μ΄ν΄νλ κ²μ΄ μ€μν©λλ€. μΉ λΈλΌμ°μ κ° νμΌμ ν¬ν¨ν νΌμ μ μΆν λ, μΌλ°μ μΌλ‘ enctype="multipart/form-data" μμ±μ μ¬μ©ν©λλ€. μ΄ μΈμ½λ© μ νμ νΌ μ μΆμ μ¬λ¬ λΆλΆ(part)μΌλ‘ λλλ©°, κ° λΆλΆμ κ³ μ ν μ½ν
μΈ μ νκ³Ό μ²λ¦¬ μ 보λ₯Ό κ°μ§λλ€. μ΄λ₯Ό ν΅ν΄ ν
μ€νΈ νλ, λΉν
μ€νΈ νλ, λ°μ΄λ리 νμΌμ ν¬ν¨ν λ€μν μ νμ λ°μ΄ν°λ₯Ό λ¨μΌ HTTP μμ² λ΄μμ μ μ‘ν μ μμ΅λλ€.
λ©ν°ννΈ μμ²μ κ° λΆλΆμ λ€μμΌλ‘ ꡬμ±λ©λλ€:
- Content-Disposition ν€λ: νΌ νλμ μ΄λ¦(
name)κ³Ό νμΌμ κ²½μ° μλ³Έ νμΌ μ΄λ¦(filename)μ μ§μ ν©λλ€. - Content-Type ν€λ: ν΄λΉ λΆλΆμ MIME μ νμ λνλ
λλ€ (μ:
text/plain,image/jpeg). - Body: ν΄λΉ λΆλΆμ μ€μ λ°μ΄ν°μ λλ€.
FastAPIμ νμΌ μ λ‘λ μ κ·Ό λ°©μ
FastAPIλ νμ΄μ¬μ νμ€ λΌμ΄λΈλ¬λ¦¬λ₯Ό νμ©νλ©° λ°μ΄ν° μ ν¨μ± κ²μ¬λ₯Ό μν΄ Pydanticκ³Ό μννκ² ν΅ν©λ©λλ€. νμΌ μ
λ‘λλ₯Ό μν΄ fastapi λͺ¨λμ UploadFile νμ
μ μ¬μ©ν©λλ€. μ΄ ν΄λμ€λ μ
λ‘λλ νμΌ λ°μ΄ν°μ μ κ·ΌνκΈ° μν νΈλ¦¬νκ³ μμ ν μΈν°νμ΄μ€λ₯Ό μ 곡ν©λλ€.
κΈ°λ³Έ νμΌ μ λ‘λ ꡬν
λ¨μΌ νμΌ μ
λ‘λλ₯Ό μλ½νλ FastAPI μλν¬μΈνΈλ₯Ό λ§λλ κ°λ¨ν μμ λΆν° μμνκ² μ΅λλ€. fastapiμ File ν¨μλ₯Ό μ¬μ©νμ¬ νμΌ λ§€κ°λ³μλ₯Ό μ μΈν κ²μ
λλ€.
from fastapi import FastAPI, File, UploadFile
app = FastAPI()
@app.post("/files/")
async def create_file(file: UploadFile):
return {"filename": file.filename, "content_type": file.content_type}
μ΄ μμ μμ:
FastAPI,File,UploadFileμ μν¬νΈν©λλ€./files/μλν¬μΈνΈλPOSTμμ²μΌλ‘ μ μλ©λλ€.fileλ§€κ°λ³μλUploadFileλ‘ νμ ννΈκ° μ§μ λμ΄, νμΌ μ λ‘λλ₯Ό μμνλ€λ κ²μ μλ―Έν©λλ€.- μλν¬μΈνΈ ν¨μ λ΄μμ, μ
λ‘λλ νμΌμ
filenameμ΄λcontent_typeκ³Ό κ°μ μμ±μ μ κ·Όν μ μμ΅λλ€.
ν΄λΌμ΄μΈνΈκ° /files/λ‘ νμΌμ 첨λΆνμ¬ POST μμ²μ 보λ΄λ©΄(μΌλ°μ μΌλ‘ enctype="multipart/form-data"λ₯Ό μ¬μ©ν νΌμ ν΅ν΄), FastAPIλ μλμΌλ‘ νμ±μ μ²λ¦¬νκ³ UploadFile κ°μ²΄λ₯Ό μ 곡ν©λλ€. κ·Έλ¬λ©΄ μ΄ κ°μ²΄μ μνΈμμ©ν μ μμ΅λλ€.
μ λ‘λλ νμΌ μ μ₯νκΈ°
μ’
μ’
μ
λ‘λλ νμΌμ λμ€ν¬μ μ μ₯νκ±°λ κ·Έ λ΄μ©μ μ²λ¦¬ν΄μΌ ν νμκ° μμ΅λλ€. UploadFile κ°μ²΄λ μ΄λ₯Ό μν λ©μλλ₯Ό μ 곡ν©λλ€:
read(): νμΌμ μ 체 λ΄μ©μ λ©λͺ¨λ¦¬μ λ°μ΄νΈλ‘ μ½μ΄λ€μ λλ€. μμ νμΌμ μ¬μ©νμΈμ.write(content: bytes): νμΌμ λ°μ΄νΈλ₯Ό μλλ€.seek(offset: int): νμ¬ νμΌ μμΉλ₯Ό λ³κ²½ν©λλ€.close(): νμΌμ λ«μ΅λλ€.
νΉν λμ©λ νμΌμ λ€λ£¨κ±°λ I/O λ°μ΄λ μμ
μ μ²λ¦¬ν λ νμΌ μμ
μ λΉλκΈ°μ μΌλ‘ μ²λ¦¬νλ κ²μ΄ μ€μν©λλ€. FastAPIμ UploadFileμ λΉλκΈ° μμ
μ μ§μν©λλ€.
from fastapi import FastAPI, File, UploadFile
import shutil
app = FastAPI()
@app.post("/files/save/")
async def save_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"file '{file.filename}' saved at '{file_location}'"}
μ΄ ν₯μλ μμ μμ:
File(...)μ μ¬μ©νμ¬ μ΄ λ§€κ°λ³μκ° νμμμ λνλ λλ€.- νμΌμ΄ μ μ₯λ λ‘컬 κ²½λ‘λ₯Ό μ§μ ν©λλ€.
uploadsλλ ν λ¦¬κ° μ‘΄μ¬νλμ§ νμΈνμΈμ. - λμ νμΌμ λ°μ΄λ리 μ°κΈ° λͺ¨λ(
"wb+")λ‘ μ½λλ€. await file.read()λ₯Ό μ¬μ©νμ¬ μ λ‘λλ νμΌμ λ΄μ©μ λΉλκΈ°μ μΌλ‘ μ½κ³ λ‘컬 νμΌμ μλλ€.
μ°Έκ³ : await file.read()λ‘ μ 체 νμΌμ λ©λͺ¨λ¦¬μ μ½λ κ²μ λ§€μ° ν° νμΌμ κ²½μ° λ¬Έμ κ° λ μ μμ΅λλ€. κ·Έλ° μλ리μ€μμλ νμΌ μ½ν
μΈ λ₯Ό μ€νΈλ¦¬λ°νλ κ²μ κ³ λ €νμΈμ.
νμΌ μ½ν μΈ μ€νΈλ¦¬λ°νκΈ°
λμ©λ νμΌμ κ²½μ°, μ 체 λ΄μ©μ λ©λͺ¨λ¦¬λ‘ μ½μΌλ©΄ κ³Όλν λ©λͺ¨λ¦¬ μλΉμ μ μ¬μ μΈ λ©λͺ¨λ¦¬ λΆμ‘± μ€λ₯λ‘ μ΄μ΄μ§ μ μμ΅λλ€. λ λ©λͺ¨λ¦¬ ν¨μ¨μ μΈ μ κ·Ό λ°©μμ νμΌμ μ²ν¬ λ¨μλ‘ μ€νΈλ¦¬λ°νλ κ²μ
λλ€. shutil.copyfileobj ν¨μλ μ΄λ₯Ό μν΄ νλ₯νμ§λ§, λΉλκΈ° μμ
μ μν΄ μ‘°μ ν νμκ° μμ΅λλ€.
from fastapi import FastAPI, File, UploadFile
import aiofiles # Install using: pip install aiofiles
app = FastAPI()
@app.post("/files/stream/")
async def stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
content = await file.read()
await out_file.write(content)
return {"info": f"file '{file.filename}' streamed and saved at '{file_location}'"}
aiofilesλ₯Ό μ¬μ©νλ©΄ μ 체 νμΌμ ν λ²μ λ©λͺ¨λ¦¬μ λ‘λνμ§ μκ³ λ μ
λ‘λλ νμΌμ λ΄μ©μ λμ νμΌλ‘ ν¨μ¨μ μΌλ‘ μ€νΈλ¦¬λ°ν μ μμ΅λλ€. μ΄ λ¬Έλ§₯μμ await file.read()λ μ¬μ ν μ 체 νμΌμ μ½μ§λ§, aiofilesκ° μ°κΈ°λ₯Ό λ ν¨μ¨μ μΌλ‘ μ²λ¦¬ν©λλ€. UploadFileμ μ¬μ©ν μ§μ ν μ²ν¬ λ¨μ μ€νΈλ¦¬λ°μ μν΄μλ μΌλ°μ μΌλ‘ await file.read(chunk_size)λ₯Ό λ°λ³΅νμ§λ§, aiofiles.openκ³Ό await out_file.write(content)λ μ μ₯μ μν μΌλ°μ μ΄κ³ μ±λ₯μ΄ μ’μ ν¨ν΄μ
λλ€.
μ²νΉμ μ¬μ©ν λ λͺ μμ μΈ μ€νΈλ¦¬λ° μ κ·Ό λ°©μμ λ€μκ³Ό κ°μ΅λλ€:
from fastapi import FastAPI, File, UploadFile
import aiofiles
app = FastAPI()
CHUNK_SIZE = 1024 * 1024 # 1MB chunk size
@app.post("/files/chunked_stream/")
async def chunked_stream_file(file: UploadFile = File(...)):
file_location = f"./uploads/{file.filename}"
async with aiofiles.open(file_location, "wb") as out_file:
while content := await file.read(CHUNK_SIZE):
await out_file.write(content)
return {"info": f"file '{file.filename}' chunked streamed and saved at '{file_location}'"}
μ΄ chunked_stream_file μλν¬μΈνΈλ νμΌμ 1MB μ²ν¬ λ¨μλ‘ μ½κ³ κ° μ²ν¬λ₯Ό μΆλ ₯ νμΌμ μλλ€. μ΄λ μ μ¬μ μΌλ‘ λ§€μ° ν° νμΌμ μ²λ¦¬νλ κ°μ₯ λ©λͺ¨λ¦¬ ν¨μ¨μ μΈ λ°©λ²μ
λλ€.
λ€μ€ νμΌ μ λ‘λ μ²λ¦¬νκΈ°
μΉ μ ν리μΌμ΄μ μ μ’ μ’ μ¬μ©μκ° μ¬λ¬ νμΌμ λμμ μ λ‘λνλλ‘ μꡬν©λλ€. FastAPIλ μ΄λ₯Ό κ°λ¨νκ² λ§λλλ€.
νμΌ λͺ©λ‘ μ λ‘λνκΈ°
λ§€κ°λ³μμ UploadFileμ 리μ€νΈλ‘ νμ
ννΈλ₯Ό μ§μ νμ¬ νμΌ λͺ©λ‘μ λ°μ μ μμ΅λλ€.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/multiple/")
async def create_multiple_files(
files: List[UploadFile] = File(...)
):
results = []
for file in files:
# Process each file, e.g., save it
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {"files_processed": results}
μ΄ μλ리μ€μμ ν΄λΌμ΄μΈνΈλ λμΌν νΌ νλ μ΄λ¦(μ: `files`)μΌλ‘ μ¬λ¬ ννΈλ₯Ό 보λ΄μΌ ν©λλ€. FastAPIλ μ΄λ₯Ό UploadFile κ°μ²΄μ νμ΄μ¬ 리μ€νΈλ‘ μμ§ν©λλ€.
νμΌκ³Ό λ€λ₯Έ νΌ λ°μ΄ν° νΌν©νκΈ°
νΌμ΄ νμΌ νλμ μΌλ° ν
μ€νΈ νλλ₯Ό λͺ¨λ ν¬ν¨νλ κ²μ νν μΌμ
λλ€. FastAPIλ λ€λ₯Έ λ§€κ°λ³μλ₯Ό νμ€ νμ
ννΈμ ν¨κ», νμΌμ΄ μλ νΌ νλμ λν΄μλ Formμ μ¬μ©νμ¬ μ μΈν¨μΌλ‘μ¨ μ΄λ₯Ό μ²λ¦¬ν©λλ€.
from fastapi import FastAPI, File, UploadFile, Form
from typing import List
app = FastAPI()
@app.post("/files/mixed/")
async def upload_mixed_data(
description: str = Form(...),
files: List[UploadFile] = File(...) # Accepts multiple files with the name 'files'
):
results = []
for file in files:
# Process each file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
results.append({"filename": file.filename, "content_type": file.content_type, "saved_at": file_location})
return {
"description": description,
"files_processed": results
}
Swagger UIλ Postmanκ³Ό κ°μ λꡬλ₯Ό μ¬μ©ν λ, descriptionμ μΌλ° νΌ νλλ‘ μ§μ ν λ€μ files νλμ λν΄ μ¬λ¬ ννΈλ₯Ό μΆκ°νκ³ κ° ννΈμ μ½ν
μΈ νμ
μ μ μ ν μ΄λ―Έμ§/λ¬Έμ μ νμΌλ‘ μ€μ ν©λλ€.
κ³ κΈ κΈ°λ₯ λ° λͺ¨λ² μ¬λ‘
κΈ°λ³Έμ μΈ νμΌ μ²λ¦¬ μΈμλ, κ²¬κ³ ν νμΌ μ λ‘λ APIλ₯Ό ꡬμΆνκΈ° μν΄ λͺ κ°μ§ κ³ κΈ κΈ°λ₯κ³Ό λͺ¨λ² μ¬λ‘κ° μ€μν©λλ€.
νμΌ ν¬κΈ° μ ν
무μ ν νμΌ μ λ‘λλ₯Ό νμ©νλ©΄ μλΉμ€ κ±°λΆ(DoS) 곡격μ΄λ κ³Όλν 리μμ€ μλΉλ‘ μ΄μ΄μ§ μ μμ΅λλ€. FastAPI μ체λ νλ μμν¬ μμ€μμ κΈ°λ³Έμ μΌλ‘ μ격ν μ νμ κ°μ νμ§ μμ§λ§, λ€μκ³Ό κ°μ κ²μ¬λ₯Ό ꡬνν΄μΌ ν©λλ€:
- μ ν리μΌμ΄μ λ 벨μμ: νμΌμ λ°μ ν μ²λ¦¬νκ±°λ μ μ₯νκΈ° μ μ νμΌ ν¬κΈ°λ₯Ό νμΈν©λλ€.
- μΉ μλ²/νλ‘μ λ 벨μμ: μΉ μλ²(μ: Nginx, μ컀λ₯Ό μ¬μ©νλ Uvicorn)λ₯Ό ꡬμ±νμ¬ νΉμ νμ΄λ‘λ ν¬κΈ°λ₯Ό μ΄κ³Όνλ μμ²μ κ±°λΆνλλ‘ ν©λλ€.
μ ν리μΌμ΄μ λ 벨 ν¬κΈ° κ²μ¬ μμ :
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
MAX_FILE_SIZE_MB = 10
MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024
@app.post("/files/limited_size/")
async def upload_with_size_limit(file: UploadFile = File(...)):
if len(await file.read()) > MAX_FILE_SIZE_BYTES:
raise HTTPException(status_code=400, detail=f"File is too large. Maximum size is {MAX_FILE_SIZE_MB}MB.")
# Reset file pointer to read content again
await file.seek(0)
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully."}
μ€μ: νμΌ ν¬κΈ°λ₯Ό νμΈνκΈ° μν΄ νμΌμ μ½μ ν, λ΄μ©μ λ€μ μ½μΌλ €λ©΄(μ: μ μ₯νκΈ° μν΄) await file.seek(0)μ μ¬μ©νμ¬ νμΌ ν¬μΈν°λ₯Ό μ²μμΌλ‘ μ¬μ€μ ν΄μΌ ν©λλ€.
νμ©λλ νμΌ μ ν (MIME νμ )
νΉμ νμΌ μ νμΌλ‘ μ
λ‘λλ₯Ό μ ννλ©΄ 보μμ΄ κ°νλκ³ λ°μ΄ν° 무결μ±μ΄ 보μ₯λ©λλ€. UploadFile κ°μ²΄μ content_type μμ±μ νμΈν μ μμ΅λλ€.
from fastapi import FastAPI, File, UploadFile, HTTPException
app = FastAPI()
ALLOWED_FILE_TYPES = {"image/jpeg", "image/png", "application/pdf"}
@app.post("/files/restricted_types/")
async def upload_restricted_types(file: UploadFile = File(...)):
if file.content_type not in ALLOWED_FILE_TYPES:
raise HTTPException(status_code=400, detail=f"Unsupported file type: {file.content_type}. Allowed types are: {', '.join(ALLOWED_FILE_TYPES)}")
# Proceed with saving or processing the file
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {"info": f"File '{file.filename}' uploaded successfully and is of an allowed type."}
MIME νμ μ λλλ‘ μμ‘°λ μ μμΌλ―λ‘, νΉν μ΄λ―Έμ§μ κ²½μ° λ κ°λ ₯ν νμ κ²μ¬λ₯Ό μν΄ Pillowμ κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμ¬ νμΌμ μ€μ λ΄μ©μ κ²μ¬νλ κ²μ κ³ λ €ν μ μμ΅λλ€.
μ€λ₯ μ²λ¦¬ λ° μ¬μ©μ νΌλλ°±
μ¬μ©μμκ² λͺ
ννκ³ μ€ν κ°λ₯ν μ€λ₯ λ©μμ§λ₯Ό μ 곡νμΈμ. νμ€ HTTP μ€λ₯ μλ΅μ μν΄ FastAPIμ HTTPExceptionμ μ¬μ©ν©λλ€.
- νμΌ μμ/λλ½: νμ νμΌ λ§€κ°λ³μκ° μ μ‘λμ§ μμ κ²½μ°.
- νμΌ ν¬κΈ° μ΄κ³Ό: ν¬κΈ° μ ν μμ μμ 보μ¬μ€ κ²½μ°.
- μλͺ»λ νμΌ μ ν: μ ν μ ν μμ μμ 보μ¬μ€ κ²½μ°.
- μλ² μ€λ₯: νμΌ μ μ₯ λλ μ²λ¦¬ μ€ λ¬Έμ λ°μ μ (μ: λμ€ν¬ κ³΅κ° λΆμ‘±, κΆν μ€λ₯).
보μ κ³ λ €μ¬ν
νμΌ μ λ‘λλ 보μ μνμ μ΄λν©λλ€:
- μ
μ± νμΌ: μ€ν νμΌ(
.exe,.sh)μ΄λ λ€λ₯Έ νμΌ μ νμΌλ‘ μμ₯ν μ€ν¬λ¦½νΈλ₯Ό μ λ‘λνλ κ²½μ°. νμ νμΌ μ νμ κ²μ¦νκ³ μ λ‘λλ νμΌμ μ μ± μ½λ κ²μ¬λ₯Ό κ³ λ €νμΈμ. - κ²½λ‘ νμ(Path Traversal): νμΌ μ΄λ¦μ μμ /μ 리(sanitize)νμ¬ κ³΅κ²©μκ° μλνμ§ μμ λλ ν 리μ νμΌμ μ
λ‘λνλ κ²μ λ°©μ§νμΈμ(μ:
../../etc/passwdκ°μ νμΌ μ΄λ¦ μ¬μ©). FastAPIμUploadFileμ κΈ°λ³Έμ μΈ νμΌ μ΄λ¦ μμ /μ 리λ₯Ό μ²λ¦¬νμ§λ§, μΆκ°μ μΈ μ£Όμκ° νλͺ ν©λλ€. - μλΉμ€ κ±°λΆ(Denial of Service): νμΌ ν¬κΈ° μ νμ ꡬννκ³ μ λ‘λ μλν¬μΈνΈμ μλ μ ν(rate limiting)μ μ μ©ν μ μμ΅λλ€.
- ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS): νμΌ μ΄λ¦μ΄λ νμΌ λ΄μ©μ μΉ νμ΄μ§μ μ§μ νμνλ κ²½μ°, XSS 곡격μ λ°©μ§νκΈ° μν΄ μ μ ν μ΄μ€μΌμ΄ν μ²λ¦¬λμλμ§ νμΈνμΈμ.
λͺ¨λ² μ¬λ‘: μ λ‘λλ νμΌμ μΉ μλ²μ λ¬Έμ 루νΈ(document root) μΈλΆμ μ μ₯νκ³ , μ μ ν μ κ·Ό μ μ΄κ° μλ μ μ© μλν¬μΈνΈλ₯Ό ν΅ν΄ μ 곡νκ±°λ μ½ν μΈ μ μ‘ λ€νΈμν¬(CDN)λ₯Ό μ¬μ©νμΈμ.
Pydantic λͺ¨λΈκ³Ό νμΌ μ λ‘λ ν¨κ» μ¬μ©νκΈ°
UploadFileμ΄ νμΌμ μν μ£Όμ νμ
μ΄μ§λ§, λ 볡μ‘ν λ°μ΄ν° ꡬ쑰λ₯Ό μν΄ νμΌ μ
λ‘λλ₯Ό Pydantic λͺ¨λΈμ ν΅ν©ν μ μμ΅λλ€. κ·Έλ¬λ νμ€ Pydantic λͺ¨λΈ λ΄μ μ§μ μ μΈ νμΌ μ
λ‘λ νλλ λ©ν°ννΈ νΌμμ κΈ°λ³Έμ μΌλ‘ μ§μλμ§ μμ΅λλ€. λμ , μΌλ°μ μΌλ‘ νμΌμ λ³λμ λ§€κ°λ³μλ‘ λ°μ λ€μ, Pydantic λͺ¨λΈμ μν΄ μ μ₯λκ±°λ μ ν¨μ± κ²μ¬λ₯Ό λ°μ μ μλ νμμΌλ‘ μ²λ¦¬ν©λλ€.
μΌλ°μ μΈ ν¨ν΄μ λ©νλ°μ΄ν°λ₯Ό μν Pydantic λͺ¨λΈμ κ°μ§κ³ νμΌμ λ³λλ‘ λ°λ κ²μ λλ€:
from fastapi import FastAPI, File, UploadFile, Form
from pydantic import BaseModel
from typing import Optional
class UploadMetadata(BaseModel):
title: str
description: Optional[str] = None
app = FastAPI()
@app.post("/files/model_metadata/")
async def upload_with_metadata(
metadata: str = Form(...), # Receive metadata as a JSON string
file: UploadFile = File(...)
):
import json
try:
metadata_obj = UploadMetadata(**json.loads(metadata))
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="Invalid JSON format for metadata")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Error parsing metadata: {e}")
# Now you have metadata_obj and file
# Proceed with saving file and using metadata
file_location = f"./uploads/{file.filename}"
with open(file_location, "wb+") as file_object:
file_object.write(await file.read())
return {
"message": "File uploaded successfully with metadata",
"metadata": metadata_obj,
"filename": file.filename
}
μ΄ ν¨ν΄μμ ν΄λΌμ΄μΈνΈλ λ©νλ°μ΄ν°λ₯Ό νΌ νλ(μ: metadata) λ΄μ JSON λ¬Έμμ΄λ‘ 보λ΄κ³ , νμΌμ λ³λμ λ©ν°ννΈ ννΈλ‘ 보λ
λλ€. κ·Έλ¬λ©΄ μλ²λ JSON λ¬Έμμ΄μ Pydantic κ°μ²΄λ‘ νμ±ν©λλ€.
λμ©λ νμΌ μ λ‘λμ μ²νΉ
λ§€μ° ν° νμΌ(μ: κΈ°κ°λ°μ΄νΈ λ¨μ)μ κ²½μ° μ€νΈλ¦¬λ°μ‘°μ°¨λ μΉ μλ²λ ν΄λΌμ΄μΈνΈ μΈ‘μ μ νμ λΆλͺν μ μμ΅λλ€. λ κ³ κΈ κΈ°μ μ μ²ν¬ μ λ‘λ(chunked uploads)λ‘, ν΄λΌμ΄μΈνΈκ° νμΌμ μμ μ‘°κ°μΌλ‘ λλμ΄ μμ°¨μ μΌλ‘ λλ λ³λ ¬λ‘ μ λ‘λνλ λ°©μμ λλ€. κ·Έλ¬λ©΄ μλ²λ μ΄ μ²ν¬λ€μ μ¬μ‘°λ¦½ν©λλ€. μ΄λ μΌλ°μ μΌλ‘ μ¬μ©μ μ μ ν΄λΌμ΄μΈνΈ μΈ‘ λ‘μ§κ³Ό μ²ν¬ κ΄λ¦¬λ₯Ό μν΄ μ€κ³λ μλ² μλν¬μΈνΈ(μ: μ²ν¬ μλ³, μμ μ μ₯, μ΅μ’ 쑰립)κ° νμν©λλ€.
FastAPIκ° ν΄λΌμ΄μΈνΈ μ£Όλ μ²ν¬ μ λ‘λμ λν λ΄μ₯ μ§μμ μ 곡νμ§λ μμ§λ§, FastAPI μλν¬μΈνΈ λ΄μμ μ΄ λ‘μ§μ ꡬνν μ μμ΅λλ€. μ΄λ λ€μκ³Ό κ°μ μλν¬μΈνΈλ₯Ό λ§λλ κ²μ ν¬ν¨ν©λλ€:
- κ°λ³ νμΌ μ²ν¬λ₯Ό μμ ν©λλ€.
- μ΄ μ²ν¬λ€μ μμλ‘ μ μ₯νλ©°, μμμ μ΄ μ²ν¬ μλ₯Ό λνλ΄λ λ©νλ°μ΄ν°λ₯Ό ν¨κ» μ μ₯ν μ μμ΅λλ€.
- λͺ¨λ μ²ν¬κ° μ λ‘λλμμ λ μ νΈλ₯Ό λ³΄λ΄ μ¬μ‘°λ¦½ νλ‘μΈμ€λ₯Ό νΈλ¦¬κ±°νλ μλν¬μΈνΈλ λ©μ»€λμ¦μ μ 곡ν©λλ€.
μ΄λ λ 볡μ‘ν μμ μ΄λ©° μ’ μ’ ν΄λΌμ΄μΈνΈ μΈ‘μ JavaScript λΌμ΄λΈλ¬λ¦¬κ° νμν©λλ€.
κ΅μ ν λ° μΈκ³ν κ³ λ €μ¬ν
κΈλ‘λ² μ¬μ©μλ₯Ό μν APIλ₯Ό ꡬμΆν λ νμΌ μ λ‘λλ νΉλ³ν μ£Όμκ° νμν©λλ€:
- νμΌ μ΄λ¦: μ μΈκ³ μ¬μ©μλ νμΌ μ΄λ¦μ λΉ ASCII λ¬Έμ(μ: μ μΌνΈ, νμ λ¬Έμ)λ₯Ό μ¬μ©ν μ μμ΅λλ€. μμ€ν μ΄ μ΄λ¬ν νμΌ μ΄λ¦μ μ¬λ°λ₯΄κ² μ²λ¦¬νκ³ μ μ₯νλμ§ νμΈνμΈμ. UTF-8 μΈμ½λ©μ΄ μΌλ°μ μΌλ‘ νμ€μ΄μ§λ§, κΉμ νΈνμ±μ μν΄μλ μ μ€ν μΈμ½λ©/λμ½λ© λ° μμ /μ λ¦¬κ° νμν μ μμ΅λλ€.
- νμΌ ν¬κΈ° λ¨μ: MBμ GBκ° μΌλ°μ μ΄μ§λ§, μ¬μ©μκ° νμΌ ν¬κΈ°λ₯Ό μ΄λ»κ² μΈμνλμ§ μ μνμΈμ. μ¬μ©μ μΉνμ μΈ λ°©μμΌλ‘ μ νμ νμνλ κ²μ΄ μ€μν©λλ€.
- μ½ν μΈ μ ν: μ¬μ©μλ λ μΌλ°μ μΈ MIME μ νμ νμΌμ μ λ‘λν μ μμ΅λλ€. νμ©λ μ ν λͺ©λ‘μ΄ μ¬μ© μ¬λ‘μ μΆ©λΆν ν¬κ΄μ μ΄κ±°λ μ μ°νμ§ νμΈνμΈμ.
- μ§μ κ·μ : λ€λ₯Έ κ΅κ°μ λ°μ΄ν° μμ£Ό λ²λ₯ λ° κ·μ μ μ μνμΈμ. μ λ‘λλ νμΌμ μ μ₯νλ €λ©΄ μ΄λ¬ν κ·μΉμ μ€μν΄μΌ ν μ μμ΅λλ€.
- μ¬μ©μ μΈν°νμ΄μ€: νμΌ μ λ‘λλ₯Ό μν ν΄λΌμ΄μΈνΈ μΈ‘ μΈν°νμ΄μ€λ μ§κ΄μ μ΄μ΄μΌ νλ©° μ¬μ©μμ μΈμ΄μ λ‘μΌμΌμ μ§μν΄μΌ ν©λλ€.
ν μ€νΈλ₯Ό μν λꡬ λ° λΌμ΄λΈλ¬λ¦¬
νμΌ μ λ‘λ μλν¬μΈνΈλ₯Ό ν μ€νΈνλ κ²μ λ§€μ° μ€μν©λλ€. λ€μμ λͺ κ°μ§ μΌλ°μ μΈ λꡬμ λλ€:
- Swagger UI (λνν API λ¬Έμ): FastAPIλ Swagger UI λ¬Έμλ₯Ό μλμΌλ‘ μμ±ν©λλ€. λΈλΌμ°μ μΈν°νμ΄μ€μμ μ§μ νμΌ μ λ‘λλ₯Ό ν μ€νΈν μ μμ΅λλ€. νμΌ μ λ ₯ νλλ₯Ό μ°Ύμ "νμΌ μ ν" λ²νΌμ ν΄λ¦νμΈμ.
- Postman: μΈκΈ° μλ API κ°λ° λ° ν μ€νΈ λꡬμ λλ€. νμΌ μ λ‘λ μμ²μ 보λ΄λ €λ©΄:
- μμ² λ©μλλ₯Ό POSTλ‘ μ€μ ν©λλ€.
- API μλν¬μΈνΈ URLμ μ λ ₯ν©λλ€.
- "Body" νμΌλ‘ μ΄λν©λλ€.
- μ νμΌλ‘ "form-data"λ₯Ό μ νν©λλ€.
- ν€-κ° μμ νμΌ λ§€κ°λ³μμ μ΄λ¦(μ:
file)μ μ λ ₯ν©λλ€. - μ νμ "Text"μμ "File"λ‘ λ³κ²½ν©λλ€.
- "Choose Files"λ₯Ό ν΄λ¦νμ¬ λ‘컬 μμ€ν μμ νμΌμ μ νν©λλ€.
- λ€λ₯Έ νΌ νλκ° μλ κ²½μ°, μ νμ "Text"λ‘ μ μ§νλ©΄μ λΉμ·νκ² μΆκ°ν©λλ€.
- μμ²μ 보λ λλ€.
- cURL: HTTP μμ²μ λ§λλ λͺ λ Ήμ€ λꡬμ λλ€.
- λ¨μΌ νμΌμ κ²½μ°:
curl -X POST -F "file=@/path/to/your/local/file.txt" http://localhost:8000/files/ - λ€μ€ νμΌμ κ²½μ°:
curl -X POST -F "files=@/path/to/file1.txt" -F "files=@/path/to/file2.png" http://localhost:8000/files/multiple/ - νΌν© λ°μ΄ν°μ κ²½μ°:
curl -X POST -F "description=My description" -F "files=@/path/to/file.txt" http://localhost:8000/files/mixed/ - νμ΄μ¬μ `requests` λΌμ΄λΈλ¬λ¦¬: νλ‘κ·Έλλ° λ°©μ ν μ€νΈμ©μ λλ€.
import requests
url = "http://localhost:8000/files/save/"
files = {'file': open('/path/to/your/local/file.txt', 'rb')}
response = requests.post(url, files=files)
print(response.json())
# For multiple files
url_multiple = "http://localhost:8000/files/multiple/"
files_multiple = {
'files': [('file1.txt', open('/path/to/file1.txt', 'rb')),
('image.png', open('/path/to/image.png', 'rb'))]
}
response_multiple = requests.post(url_multiple, files=files_multiple)
print(response_multiple.json())
# For mixed data
url_mixed = "http://localhost:8000/files/mixed/"
data = {'description': 'Test description'}
files_mixed = {'files': open('/path/to/another_file.txt', 'rb')}
response_mixed = requests.post(url_mixed, data=data, files=files_mixed)
print(response_mixed.json())
κ²°λ‘
FastAPIλ λ©ν°ννΈ νμΌ μ
λ‘λλ₯Ό μ²λ¦¬νλ κ°λ ₯νκ³ ν¨μ¨μ μ΄λ©° μ§κ΄μ μΈ λ°©λ²μ μ 곡ν©λλ€. UploadFile νμ
κ³Ό λΉλκΈ° νλ‘κ·Έλλ°μ νμ©νμ¬ κ°λ°μλ νμΌ μ²λ¦¬ κΈ°λ₯μ μννκ² ν΅ν©νλ κ²¬κ³ ν APIλ₯Ό ꡬμΆν μ μμ΅λλ€. 보μμ μ΅μ°μ μΌλ‘ νκ³ , μ μ ν μ€λ₯ μ²λ¦¬λ₯Ό ꡬννλ©°, νμΌ μ΄λ¦ μΈμ½λ© λ° κ·μ μ€μμ κ°μ μΈ‘λ©΄μ ν΄κ²°νμ¬ κΈλ‘λ² μ¬μ©μ κΈ°λ°μ μꡬλ₯Ό κ³ λ €νλ κ²μ μμ§ λ§μΈμ.
κ°λ¨ν μ΄λ―Έμ§ 곡μ μλΉμ€λ₯Ό ꡬμΆνλ 볡μ‘ν λ¬Έμ μ²λ¦¬ νλ«νΌμ ꡬμΆνλ , FastAPIμ νμΌ μ λ‘λ κΈ°λ₯μ λ§μ€ν°νλ κ²μ μ€μν μμ°μ΄ λ κ²μ λλ€. κ³μν΄μ κ·Έ κΈ°λ₯μ νμνκ³ , λͺ¨λ² μ¬λ‘λ₯Ό ꡬννλ©°, κ΅μ μ μΈ μ¬μ©μμκ² νμν μ¬μ©μ κ²½νμ μ 곡νμΈμ.