[파이썬 Projects]/<파이썬 웹개발>

[파이썬] FastAPI - 파일 업로드

기록자_Recordian 2025. 5. 8. 12:12
728x90
반응형
이전 내용
 

[FastAPI] 비동기 처리 (asynchronous processing)

이전 내용 [FastAPI] 고급 인증: 세션이전 내용 [FastAPI] 고급인증: JWT이전 내용 [FastAPI] 인증과 세션이전 내용 [FastAPI] SQLAlchemy와 CRUD (Depends, db.query)이전 내용 [파이썬] FastAPI - ORM 연동하기(SQLAlchemy)이

puppy-foot-it.tistory.com


파일 업로드

 

FastAPI는 파일 업로드를 간단하게 처리할 수 있도록 여러 도구와 기능을 제공한다.

  • HTTP: 웹상에서 데이터를 주고받을 수 있는 프로토콜
  • POST 메서드: HTTP 요청 메서드 중 하나로, 서버에 데이터를 보내는 데 사용
  • multipart/form-data: 여러 부분으로 나뉜 데이터를 하나의 패키지로 전송하는 미디어 유형

★ python-multipart 라이브러리

python-multipart 패키지는 FastAPI에서 파일 업로드를 처리하기 위해 필요한 의존성으로, 이 패키지는 multipart/form-data 인코딩을 해석하여 파일 업로드를 가능하게 해준다.

이 패키지를 사용하기 위해 설치해 준다.

conda install python-multipart

 

FastAPI에서는 파일 업로드를 위해 File과 UploadFile 이라는 두 가지 주요 클래스를 제공한다.

  • file: 파이썬 내장 file 객체와 유사
  • UploadFile: 추가 메타데이터와 비동기 작업을 위한 메서드 포함

File, UploadFile 을 사용한 파일 업로드

 

File과 UploadFile 을 사용하여 클라이언트가 업로드할 파일들을 정의할 수 있다.

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}

 

- 파일 업로드 엔드포인트 (/files/)

  • file: bytes = File(): File()을 사용하여 전송된 파일의 내용을 바이트 배열로 수신
  • File()을 사용하여 매개변수를 선언하면 FastAPI는 해당 매개변수를 파일 업로드로 해석하여 FastAPI는 요청의 multipart/form-data로 전송된 파일을 적절히 처리하도록 설계
  • 또한, 매개변수 앞에 File()을 추가함으로써 FastAPI는 해당 매개변수가 파일 전송이라는 것을 명확히 인식하여, 쿼리 매개변수 또는 JSON 본문 매개변수와의 혼동을 방지
  • return {"file_size": len(file)}: 업로드된 파일의 크기를 계산하여 JSON 형태로 반환

▶ 전체 내용이 메모리에 저장되므로, 작은 크기의 파일들에 적합

 

- 파일 업로드 엔드포인트 (/uploadfile/) - UploadFile 사용

  • file: UploadFile: UploadFile 객체를 사용하여 파일의 메타정보(이름 등)를 수신
  • return {"filename": file.filename}: 업로드된 파일의 이름을 JSON 형태로 반환

[UploadFile의 속성(attribute)]

  • filename : 업로드된 파일의 이름을 문자열로 반환(예: myimage.jpg). 파일 이름을 데이터베이스에 저장하거나 파일을 디스크에 저장할 때 사용.
  • content_type : 파일의 콘텐츠 유형(MIME type / media type)을 문자열로 반환. (예: image/jpeg). 해당 정보를 통해 파일의 유형 확인.
  • file : 파일의 실제 내용을 담고 있는 파일 객체 반환. SpooledTemporaryFile (파일류 객체). 이것은 "파일류" 객체를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 실질적인 파이썬 파일. ▶ 파일의 내용을 읽거나 기타 처리 가능

[UploadFile의 async 메서드]

  • write(data): data(str 또는 bytes)를 파일에 작성.
  • read(size): 파일의 바이트 및 글자의 size(int)를 읽어서 반환.
  • seek(offset): 파일 내 offset(int) 위치의 바이트로 이동. 파일 내의 특정 위치로 포인터를 이동  예) await myfile.seek(0) 를 사용하면 파일의 시작부분으로 이동. 일반적으로 .read() 메서드를 사용한 후 다시 파일을 처음부터 읽고 싶을 때 유용.
  • await myfile.read() 를 사용한 후 내용을 다시 읽을 때 유용.
  • close(): 파일을 닫음.

이 메서드들은 내부적인 SpooledTemporaryFile 을 사용하여 해당하는 파일 메소드를 호출하며, async 메서드이므로 "await" 키워드를 사용하여야 한다.

 

[UploadFile 사용의 장점 - bytes 대비]

  • "스풀 파일" 사용. ▶ 최대 크기 제한까지만 메모리에 저장되며, 이를 초과하는 경우 디스크에 저장.
  • 이미지, 동영상, 큰 이진코드와 같은 대용량 파일들을 많은 메모리를 소모하지 않고 처리하기에 적합.
  • 업로드 된 파일의 메타데이터를 얻을 수 있다.
  • file-like async 인터페이스를 갖고 있다.
  • file-like object를 필요로하는 다른 라이브러리에 직접적으로 전달할 수 있는 파이썬 SpooledTemporaryFile 객체를 반환.
※ File-like async interface
"File-like async interface"란 비동기 프로그래밍에서 파일을 다루는 방식으로, 파일과 유사하게 동작하는 객체를 비동기적으로 사용할 수 있도록 설계된 인터페이스를 의미한다. 이러한 인터페이스는 비동기 프로그래밍의 장점을 살려, I/O 작업을 수행할 때 블로킹 없이 동작할 수 있도록 돕는다.

※ 파일 유사 객체 (File-like Object)
일반적으로 파일을 다룰 때 사용되는 객체로, 읽기, 쓰기 등의 메서드를 제공한다. 예를 들어, 파일을 읽기 위해 read(), 쓰기 위해 write() 등의 메서드를 사용할 수 있다.
파일 유사 객체는 메모리에 있는 데이터나 네트워크 소켓 등 다양한 형태의 I/O 소스를 추상화하여 다룰 수 있다.

 

[테스트]

해당 코드를 실행하여 서버를 시작한 뒤, /docs로 이동하여 Swagger 문서를 연다.

1. files 테스트

files 의 오른쪽 V 버튼을 클릭하고 try it out 버튼 클릭

 

file에서 파일을 선택하고 [Excute] 버튼 클릭

 

Responses 섹션에서 결과 확인 (파일 크기 출력)

 

2. UploadFile 테스트

UploadFile의 오른쪽에 V 버튼 클릭 → try it out 클릭 → 파일 업로드 → [Excute] 클릭 → Responses 섹션에서 결과 확인 (파일 이름 출력)


UploadFile 예제

 

실제 코드에서 사용하는 방법을 예제 코드를 통해 확인한다.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    # 파일 이름과 콘텐츠 타입 출력
    print(f"파일 이름: {file.filename}")
    print(f"콘텐츠 타입: {file.content_type}")

    # 파일 읽기 (비동기)
    contents = await file.read()
    print(f"파일 크기: {len(contents)}")

    # 파일 포인터를 처음으로 이동 (비동기)
    await file.seek(0)

    # 파일 내용 다시 읽기 (비동기)
    contents_again = await file.read()
    print(f"파일 크기(다시 읽기): {len(contents_again)}")

    # 파일 리소스 해제 (비동기)
    await file.close()

    return {"filename": file.filename}

 

[테스트 방법]

main.py 파일을 실행하여 서버를 실행하고 /docs로 이동하여 Swagger 문서를 열어 앞서 진행한 테스트 과정과 동일한 방법으로 진행한다.

 

그리고나서 main.py를 실행한 터미널을 확인해 보면 파일 이름, 콘텐츠 타입, 파일 크기 등 파일 관련 상세 내용을 확인할 수 있다.

이러한 기능을 잘 활용하여 파일 업로드와 관련된 다양한 웹 애플리케이션 기능을 구현할 수 있다.


파일 서버에 저장하기

 

파일을 업로드한 이후 이 파일을 서버에 어떻게 저장할 것인지 매우 중요하다.

여기서는 파일을 저장하기 전에 폴더를 생성하고, 쓰기 권한을 부여하는 작업을 추가한다.

import shutil
from pathlib import Path
from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def crate_upload_file(file: UploadFile = File(...)):
    folder_name = "upload_files" # 폴더이름
    Path(folder_name).mkdir(exist_ok=True) # 폴더가 없을 경우 생성
    file_location = f"{folder_name}/{file.filename}" # 파일 경로 지정

    # 읽기/쓰기 포인터를 파일의 시작 위치로 이동
    file.file.seek(0)

    with open(file_location, 'wb+') as buffer:
        shutil.copyfileobj(file.file, buffer)

    return {"Info": f"Your file '{file.filename}' has been uploaded at {file_location}"}
  • Path(folder_name).mkdir(exist_ok=True): 파이썬의 pathlib 라이브러리를 활용해 지정한 폴더가 없으면 새로 생성. exist_ok=True 옵션은 해당 폴더가 이미 존재한다면 에러 없이 넘어가라는 의미.
  • file.file.seek(0): 파일의 읽기/쓰기 포인터를 파일의 시작점으로 이동시켜 shutil.copyfileobj() 함수가 파일의 내용을 처음부터 끝까지 올바르게 복사할 수 있도록 해줌.
  • with open(file_location, 'wb+') as buffer: 업로드된 파일을 서버에 저장. open() 함수의 두 번째 옵션은 파일 모드 옵션으로 'wb+'는 이진 모드로 파일을 읽고 쓸 수 있음을 의미.
  •  shutil.copyfileobj(file.file, buffer): 파일의 내용을 buffer에 복사
※ open() 함수에서 사용 가능한 파일 모드 옵션
- 'r': 읽기 전용, 파일이 존재하지 않으면 에러 발생
- 'w': 쓰기 전용. 파일이 이미 존재하면 내용을 삭제하고 새로 작성
- 'x': 배타적 생성으로 파일 열 때 사용. 파일이 이미 존재하면 에러 발생
배타적 생성
한 대상만 허용되는 방식으로 무언가를 만드는 것을 의미한다. 즉, 특정 대상 이외에는 다른 대상이 생성될 수 없거나, 생성하더라도 배타적으로 생성된 대상에 영향을 미치지 않도록 제어되는 경우를 말한다.

- 'a': 파일 추가/쓰기 모드(append mode). 파일이 존재하지 않으면 새로 생성하고, 파일이 존재하면 파일의 끝에 내용 추가
- 'b': 이진 모드. 텍스트가 아닌 파일(이미지, 동영상 등)을 다룰 때 주로 사용
- 't': 텍스트 모드. 기본값이며, 주로 텍스트 파일을 다룰 때 사용
- '+': 읽기와 쓰기 모드 모두를 활성화. 다른 모드와 결합하여 사용 가능. 예) 'r+', 'w+', 'a+' 등

모드들은 필요에 따라 조합하여 사용 가능. 예) 'rb', 'wb', 'wb+' 등

★ 이진모드(binary mode)
파일을 읽거나 쓸 때 파일을 바이트 단위로 다루는 모드. 파일을 바이너리 모드로 열 때 그 내용은 문자나 특정 데이터 형식에 대한 문자열로 해석되지 않고, 대신 파일의 실제 바이트 데이터를 그대로 읽거나 쓸 수 있다. 이는 텍스트 파일과는 달리 특정 문자 인코딩을 고려하지 않으며 모든 데이터를 그대로 다룰 수 있게 해준다.

★ 텍스트 모드
파일을 텍스트 형식으로 읽거나 쓰는 모드이며 파일 내용을 문자열로 해석하고 문자 인코딩을 고려하여 처리한다. 텍스트 모드는 텍스트 파일을 다루는 데 유용하며 텍스트 파일에 저장된 데이터를 문자열 형태로 읽거나 쓸 수 있다.

 

 

테스트를 위해 main.py 파일을 실행하여 서버를 실행한 후, /docs로 접속하여 Swagger를 확인한다.

파일을 업로드 후, Excute를 누르면

 

main.py가 있는 폴더에 'upload_files' 폴더가 생성되고, 업로드한 파일이 해당 폴더 내에 저장되어 있는 것을 확인할 수 있다.

 


[참고]

https://fastapi.tiangolo.com/ko/tutorial/request-files/

가장 빠른 풀스택을 위한 플라스크 & FastAPI

https://velog.io/@cottoncandy/%EB%B0%94%EC%9D%B4%EB%84%88%EB%A6%AC-%EB%AA%A8%EB%93%9C-binary-mode


다음 내용

 

[FastAPI] 캐싱(caching)

이전 내용 [FastAPI] 파일 업로드이전 내용 [FastAPI] 비동기 처리 (asynchronous processing)이전 내용 [FastAPI] 고급 인증: 세션이전 내용 [FastAPI] 고급인증: JWT이전 내용 [FastAPI] 인증과 세션이전 내용 [FastAPI]

puppy-foot-it.tistory.com

728x90
반응형