[flask/python]인메모리에서 파일 작업 하기

zzarbttoo·2022년 7월 31일
0

python_web

목록 보기
1/1

안녕하세요.😊
오늘은 flask 인메모리에서 파일 업로드/파일 다운로드/zip 파일로 묶는 법/데이터 파일 변환 등에 대해서 작성하겠습니다

회사에서 반복되는 작업을 줄이기 위해 flask로 이를 자동화 하였고
(실제로 사용될 지 여부는 알 수 없으나...개발자적 해결을 시도해봤습니다)

그 과정에서 DB/파일 관리를 줄이고자 하여 모든 파일 관련 작업들을
서버에 따로 저장하지 않고 인메모리에서 진행하도록 했습니다!


파일 업로드

간단하게 file 을 전송할 수 있는 form을 만들었습니다

fileupload html 코드
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>

    <form action = "http://localhost:8787/file-upload" onSubmit = "return form_submit()" method = "post" enctype="multipart/form-data">
        <input type = 'file' name = 'file' id = 'file'/>
        <select name = "type">
            <option value = "main">main</option>
        </select>
        <input type = "submit" value = "파일 업로드"/>
    </form>
    
</body>
</html>
  • method = "post", enctype = "multipart/form-data" 를 지정해주었습니다
  • file은 'file'이라는 key 값으로 전달했습니다

그리고 file을 받는 flask 앱을 만들었습니다
참고로 아래 코드는 이해를 돕기 위해 flask 에서 file을 받는 부분을 제외한 부분은 생략 했습니다

app.py
from flask import Flask, request, send_file
...(생략)

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 #file 최대 크기 
api = Api(app)

@app.route('/file-upload', methods=['POST'])
def file_upload():
  	...(생략)
    
    try:
        file_storage = request.files['file']
    except (Exception) as error:
        print(error)
        return "file 첨부 여부 및 api key값이 명확한지 확인해주세요"

    if type == 'main' or type == 'sub':
        if file_storage and file_storage.filename != '':
        
        ... (생략)
       
if __name__ == "__main__":
	...(생략)
  • 업로드 할 파일의 크기를 제한하기 위해 MAX_CONTENT_LENGTH 설정을 했습니다
  • post api에서 request.file['key 값'] 을 통해 객체를 받습니다
  • 이 객체는 FileStorage 객체입니다
  • 파일을 이용한 이후 과정을 처리하기에 앞서 file_storage.filename을 확인해 validation을 진행합니다

validation에는 아래와 같은 함수들을 사용했습니다

def file_validation(filename):
    ALLOWED_EXTENSIONS = set(['xlsx', 'csv'])
    file_extension = filename.rsplit('.', 1)[1]
    return '.' in filename and file_extension in ALLOWED_EXTENSIONS , file_extension


def file_process(file_storage):

    is_file_allowed, extension = file_validation(file_storage.filename)
    
    ...(생략)
  • 저는 xlsx, csv 만 허용되도록 했기 때문에 파일 확장자에 대해 validation을 진행했습니다

위와 같은 방식으로 파일 업로드를 진행할 수 있었습니다


파일 변환

이후 file storage에서 file을 읽고 이를 excel, csv 파일로 변환했습니다

file -> dataframe
def file_process(file_storage):

    is_file_allowed, extension = file_validation(file_storage.filename)

    if not is_file_allowed:
        raise Exception("xlsx, csv 파일만 허용됩니다")

    file = file_storage.read()

    return file, extension
  • fileStorage 객체에서 read() 함수를 이용해 file을 읽었습니다
def file_read(file, extension):

    if extension == 'xlsx' or extension == 'xls':
        return pd.read_excel(file, sheet_name = None)
    elif extension == 'csv':
        return pd.read_csv(file, sheet_name = None)
  • 이후 file을 pandas를 이용해 dataFrame으로 변환했습니다

dataFrame -> file 객체

from pandas import DataFrame
import pandas as pd 
import io

def export_dataframe_to_excel(dataFrame:DataFrame, sheet_name:str, org_file_extension:str):

    try:
        output = io.BytesIO()
        excel_result = pd.ExcelWriter(output)

        dataFrame.to_excel(excel_result, sheet_name = sheet_name)
        excel_result.save()
        output.seek(0)
        file = output.read()    

        return file 
  • dataFrame에서 필요한 처리를 해준 후 다시 file 객체로 만들어주는 과정입니다
  • dataFrame.to_excel을 이용해 dataframe을 excel 파일로 변환해주었습니다
  • 이를 pandas의 ExcelWriter을 이용해 파일에 저장할 수 있도록 했습니다
    -> 여기서 파일은 io.ByesIO로 만든 바이트열로 메모리에서만 작업이 이루어지도록 했습니다(실제 파일 저장 X)
  • 위와 같은 방식으로 파일 객체를 만들었습니다

file 객체 합쳐서 zipfile로 만들기

import zipfile 

def file_list_to_zip_file(file_list):

    file_folder = io.BytesIO()
    
    with zipfile.ZipFile(file_folder, 'w') as zip_folder:
        
        if file_list:
            for file, file_name in file_list:
                try: 
                    if file and file_name:
                        zip_folder.writestr(file_name, file)
                except (Exception) as error:
                    print(error)
    
    file_folder.seek(0)

    return file_folder
  • 위에서 만든 여러개의 파일 객체들을 하나의 zip file로 압축하는 작업을 진행했습니다
  • 파일 객체를 만들었듯이 io.ByteIO를 이용해 zip file 객체를 만들었습니다
  • zipfile 라이브러리를 이용해 진행했습니다

file download 구현(zip file)

@app.route('/file-upload', methods=['POST'])
def file_upload():
  
  	...(생략)
            try:
                return send_file(file, download_name= 'file이름' + '.zip')
            except (Exception) as error:
                print(error)
                return str(error)
  • 위와 같은 과정으로 만든 zipfile을 send_file 함수를 이용해 다시 return 했습니다(response)
  • 이 때 download_name에 확장자를 포함시킨 파일 이름을 적었습니다

위와 같은 과정을 거쳐 flask에서 파일 처리 등을 진행할 수 있었습니다
참고로 위 프로젝트에서는 아래와 같은 라이브러리들을 사용했습니다

python:3 이용(docker), flask
postgresql 이용

pip 버전 정보

pandas : 1.4.3
openpyxl : 3.0.10
pyyaml : 6.0
psycopg2 : 2.9.3
flask : 2.1.2
flask_restx : 0.5.1

(위 코드들에서 다루어지지 않은 라이브러리도 있으니 참고해서 사용하시면 될 듯 합니다

글이 누군가에게는 도움이 되었으면 좋겠습니다
그럼 모두 즐거운 코딩 하세요!

(●'◡'●)

profile
나는야 누워있는 개발머신

0개의 댓글