ํ์ผ ์์ฒญ¶
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 ์ ์ฌ์ฉํ๊ธฐ ๋ฐ๋๋๋ค.