Form은 HTML 태그 중 하나로, 어떤 정보를 입력 받는 “폼”에 대한 컴포넌트를 렌더링하기 위해 쓰인다. 아이디와 비밀번호를 입력받을때 가장 많이 사용된다.


정적 파일

보통 웹에서의 정적 파일은 단순 이미지 뿐만 아니라 html, js, css 등을 포함한다. 브라우저는 서버에 요청을 하고, 정적 파일들을 다운로드 한다.

FastAPI에서 정적 파일을 이용하기 위해서는 StaticFiles를 import 해서 사용해야 한다.

from fastapi import FastAPI, Form
from fastapi.staticfiles import StaticFiles


app = FastAPI()
# mount(): static 파일을 import 할수 있게 해줌 
app.mount("/static", StaticFiles(directory="static"), name="static")


@app.post("/login")
# ...으로 필수 값임을 표시
def login(username: str = Form(...), password: str = Form(...)):
    return {"username": username}

image

폼의 경우 미디어 타입은 application/x-www-form-urlencoded 이다. 파일의 경우 폼을 정말 많이 사용하는데, 이 때는 타입을 multipart/form-data로 반드시 작성해주어야 한다.


파일 처리

FastAPI에서 파일을 입력받기 위해서는 File이라는 클래스를 import 해서 사용하면 된다. File은 multipart/form-data 이다.

바이트 스트림으로 받기

아주 간단하게 File 클래스만 선언해주면 된다. 대신 bytes 라고 바이트열임을 명시만 해주면 된다. (byte는 파이썬 표준 타입) 하지만, 이렇게 할 경우 파일 이름이나 확장자 등 다양한 파일 정보를 받을 수가 없다.

from fastapi import FastAPI, File

app = FastAPI()


@app.post("/file/size")
def get_filesize(file: bytes = File(...)):
    return {"file_size": len(file)}

image

UploadFile 이용하기

위에서 byte 로 파일을 받으면 다양한 파일 정보들을 받을 수 없다고 했다. 이를 보완하기 위해 FastAPI에서는 UploadFile을 사용한다.

UploadFile 을 이용하면 content_type이나 filename을 받을 수 있다.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/file/info")
def get_file_info(file: UploadFile = File(...)):
    return {
        "content_type": file.content_type,
        "filename": file.filename
    }

image

UploadFile 은 기존 파일 IO와 같은 write, read, seek, close 메소드를 비동기로 지원한다. 여기서 IO는 파이썬 표준 입출력 객체이다.

비동기 메소드들이므로 결과값을 받으려면 반드시 await 키워드와 함께 써야한다. 그렇지 않으면 UploadFile.file 객체를 이용하면 다. UploadFile.file file-like 객체로 파일 같은 객체인데 사실상 그냥 파일이라고 생각하면 된다. 비동기로 사용하려면 함수 앞에 async 키워드가 붙여야 한다.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/file/info")
async def get_file_info(file: UploadFile = File(...)):
    file_like_obj = file.file
    contents = await file.read()

    return {
        "content_type": file.content_type,
        "filename": file.filename,
    }

파일 저장

요즘에는 클라우드 환경이 발달하면서 정적 파일들은 S3와 같은 스토리지 서비스에 저장한다. 여기서는 업로드한 파일을 서버에 저장하는 것을 살펴보자.

from tempfile import NamedTemporaryFile
from typing import IO

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


async def save_file(file: IO):
    # delete=True(기본값)이면 현재 함수가 닫히고 파일도 지워짐
    # NamedTemporaryFile: 이름이 있는 임시 파일을 생성하겠다
    with NamedTemporaryFile("wb", delete=False) as tempfile:
        tempfile.write(file.read())  # 전달받은 파일 객체를 읽고 tempfile에 쓰기
        return tempfile.name  # tempfile.name은 파일의 절대 경로임


@app.post("/file/store")
async def store_file(file: UploadFile = File(...)):
    path = await save_file(file.file)
    return {"filepath": path}

아래와 같이 “C:\Users\KBS\AppData\Local\Temp\tmpgi6ss5xp” 경로에 파일이 저장되었다.

image


(참고) 동기 vs 비동기

동기(synchronous)

동기(synchronous)는 데이터의 요청과 응답 한 자리에서 동시에 일어나는것을 말한다. 요청을 하면 시간이 얼마나 걸리던지 요청한 자리에서 결과가 주어져야 한다. 사용자가 데이터를 서버에게 요청한다면 그 서버가 데이터 요청에 따른 응답을 사용자에게 다시 리턴해주기 전까지 사용자는 다른 활동을 할 수 없으며 기다려야한다.

비동기(Asynchronous)

비동기(Asynchronous)는 요청과 응답은 동시에 일어나지 않을거라는 약속이다. 서버에게 데이터를 요청한 후 요청에 따른 응답을 계속 기다리지 않아도되며 다른 외부 활동을 수행하여도되고 서버에게 다른 요청사항을 보내도 상관없다.