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