Flask 심화: 확장 기능, 신호, 데이터베이스 통합 및 Python 고급 기법
이 문서는 Flask 프레임워크의 고급 기능과 관련된 다양한 주제를 다룹니다. 새로운 프로그래밍 패러다임, Flask 내부 동작 방식, 확장 기능 활용, 데이터베이스 연동, 그리고 Python의 특수 메서드 활용법을 탐구합니다.
1. Flask 내부 동작 및 확장점 이해
Flask의 소스 코드를 분석하면 Django와는 다른 데이터 처리 방식(전역 저장소 활용)을 배울 수 있습니다. 이는 학습 곡선이 다소 높을 수 있지만, 컨텍스트 관리 메커니즘을 이해하면 Flask의 작동 방식을 더욱 깊이 파악할 수 있습니다.
1.1. 컨텍스트 관리와 스레드 격리
Flask는 스레드 로컬(thread-local) 객체를 사용하여 여러 스레드에서 실행되는 요청 간의 데이터 격리를 보장합니다. 웹 런타임 중에는 스택에 하나의 컨텍스트 객체만 유지되지만, 오프라인 스크립트 실행 시에는 여러 컨텍스트 객체가 스택에 쌓일 수 있습니다.
from flask import current_app, g
# from your_app_module import create_app # 실제 애플리케이션 생성 함수
# 예시: create_app 함수가 있다고 가정
# app1 = create_app()
# with app1.app_context():
# print(current_app.config) # app1의 설정 출력
# app2 = create_app()
# with app2.app_context():
# print(current_app.config) # app2의 설정 출력
1.2. Flask 신호 (Signals)
Blinker 라이브러리를 사용하여 Flask는 다양한 이벤트 발생 시점에 사용자 정의 함수를 실행할 수 있는 신호 메커니즘을 제공합니다. 이는 프레임워크의 특정 지점에서 확장 기능을 통합할 수 있게 합니다.
from flask import Flask, render_template
from flask import signals
app = Flask(__name__)
@signals.appcontext_pushed.connect
def handle_appcontext_pushed(app_context):
print(f"애플리케이션 컨텍스트 푸시됨: {app_context}")
@app.route('/test')
def test_route():
return render_template('test.html')
if __name__ == '__main__':
app.run()
주요 신호 및 확장점:
appcontext_pushed: 애플리케이션 컨텍스트가 스택에 푸시될 때 트리거됩니다.before_first_request: 첫 번째 요청 전에 실행되는 함수를 등록합니다.request_started: 요청 처리가 시작될 때 트리거됩니다.url_value_preprocessor: URL 값 처리가 시작되기 전에 실행됩니다.before_request: 각 요청 전에 실행됩니다.template_rendered/before_render_template: 템플릿 렌더링 전후에 트리거됩니다.after_request: 각 요청 후에 실행됩니다.request_finished: 요청 처리가 완료될 때 트리거됩니다.got_request_exception: 요청 처리 중 예외가 발생할 때 트리거됩니다.teardown_request: 요청 처리가 끝나거나 예외 발생 시에도 호출됩니다.request_tearing_down: 요청이 정리될 때 트리거됩니다.appcontext_popped: 애플리케이션 컨텍스트가 스택에서 팝될 때 트리거됩니다.message_flashed:flash메시지가 생성될 때 트리거됩니다.
2. Flash 메시지 기능 활용
flash는 사용자에게 일시적인 메시지를 전달하는 데 사용됩니다. 이 메시지는 세션에 저장되며, 한 번 조회하면 사라집니다. 사용자 피드백이나 알림에 유용합니다.
from flask import Flask, render_template, flash, get_flashed_messages, session
app = Flask(__name__)
app.secret_key = 'your_secret_key' # 세션 암호화를 위한 키
@app.route('/show_message')
def show_message():
flash('작업이 성공적으로 완료되었습니다!', 'info')
flash('주의: 중요한 업데이트가 있습니다.', 'warning')
return render_template('message_display.html')
@app.route('/display')
def display():
messages = get_flashed_messages(with_categories=True)
return render_template('display.html', messages=messages)
# message_display.html 예시
# {% with messages = get_flashed_messages(with_categories=true) %}
# {% if messages %}
# {% for category, message in messages %}
# <p class="{{ category }}">{{ message }}</p>
# {% endfor %}
# {% endif %}
# {% endwith %}
3. Flask-Script 및 대안
Flask-Script는 과거에 명령줄 스크립트 관리에 사용되었으나, 현재는 지원이 중단되었습니다. 최신 Flask 프로젝트에서는 Python 표준 라이브러리인 argparse를 사용하거나, 더 발전된 커맨드라인 인터페이스 라이브러리를 사용하는 것이 권장됩니다.
4. 블루프린트(Blueprint)를 통한 모듈화
블루프린트는 Flask 애플리케이션을 기능별 또는 구조별로 분할하여 관리하는 데 사용됩니다. 이는 대규모 애플리케이션의 코드 구성 및 유지보수성을 향상시킵니다.
5. 데이터베이스 헬퍼 구현 (SqlHelper)
효율적인 데이터베이스 관리를 위해 연결 풀링과 컨텍스트 관리를 활용한 SqlHelper 클래스를 구현할 수 있습니다. 싱글톤 패턴과 스레드 로컬 스토리지를 조합하여 각 스레드마다 독립적인 DB 연결을 관리하거나, 단순화된 컨텍스트 관리자를 사용할 수 있습니다.
5.1. 연결 풀링 및 스레드 로컬을 이용한 싱글톤 구현
import pymysql
from dbutils.pooled_db import PooledDB
import threading
class DatabaseManager:
def __init__(self):
self.pool = PooledDB(
creator=pymysql,
maxconnections=10,
mincached=2,
blocking=True,
host='localhost',
port=3306,
user='db_user',
password='db_password',
database='db_name',
charset='utf8mb4'
)
self.local = threading.local()
def get_connection(self):
if not hasattr(self.local, 'conn') or self.local.conn is None:
self.local.conn = self.pool.connection()
self.local.cursor = self.local.conn.cursor()
return self.local.cursor
def close_connection(self):
if hasattr(self.local, 'conn') and self.local.conn:
self.local.cursor.close()
self.local.conn.close()
self.local.conn = None
self.local.cursor = None
def fetch_all(self, query, params=None):
cursor = self.get_connection()
cursor.execute(query, params or ())
return cursor.fetchall()
def __enter__(self):
return self.get_connection()
def __exit__(self, exc_type, exc_val, exc_tb):
self.close_connection()
db_manager = DatabaseManager()
# 사용 예시
# with db_manager as cursor:
# cursor.execute("SELECT * FROM users WHERE id = %s", (1,))
# user = cursor.fetchone()
# print(user)
5.2. 컨텍스트 관리자를 이용한 간결한 구현
import pymysql
from dbutils.pooled_db import PooledDB
DB_POOL = PooledDB(
creator=pymysql,
maxconnections=10,
host='localhost',
port=3306,
user='db_user',
password='db_password',
database='db_name'
)
class SimpleDBContext:
def __enter__(self):
self.conn = DB_POOL.connection()
self.cursor = self.conn.cursor()
return self.cursor
def __exit__(self, exc_type, exc_val, exc_tb):
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
# 사용 예시
# with SimpleDBContext() as cursor:
# cursor.execute("INSERT INTO logs (message) VALUES (%s)", ('Operation started',))
6. Python 클래스의 특수 메서드 활용
6.1. __call__ 메서드
클래스 인스턴스를 함수처럼 호출할 수 있게 합니다.
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return self.factor * value
multiply_by_5 = Multiplier(5)
result = multiply_by_5(10) # 5 * 10
print(result) # 출력: 50
6.2. __init__ 메서드
객체 생성 시 초기화를 담당하는 생성자 메서드입니다.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
return f"안녕하세요, 제 이름은 {self.name}이고 {self.age}살입니다."
person_instance = Person("Alice", 30)
print(person_instance.greet()) # 출력: 안녕하세요, 제 이름은 Alice이고 30살입니다.
6.3. __init__ vs __call__
__init__은 객체 생성 시 단 한 번 호출되어 속성을 초기화하는 반면, __call__은 인스턴스가 함수처럼 호출될 때마다 실행됩니다.
7. Excel 파일 데이터 읽기 및 데이터베이스 저장
xlrd (.xls) 또는 openpyxl (.xlsx) 라이브러리를 사용하여 Excel 파일의 데이터를 읽어와 데이터베이스에 저장할 수 있습니다. Flask의 파일 업로드 기능을 활용하면 사용자가 업로드한 Excel 파일을 처리할 수 있습니다.
import xlrd # .xls 파일 처리
# from openpyxl import load_workbook # .xlsx 파일 처리
from flask import Flask, request, redirect, render_template
app = Flask(__name__)
# Assuming db_manager is set up as shown previously
@app.route('/upload_excel', methods=['POST'])
def upload_excel():
if 'file' not in request.files:
return "파일이 없습니다.", 400
file = request.files['file']
if file.filename == '':
return "선택된 파일이 없습니다.", 400
if file and (file.filename.endswith('.xls')): # .xlsx의 경우 openpyxl 사용
try:
# xlrd는 파일 내용을 직접 읽기 어려우므로 임시 파일 또는 직접 파일 객체 사용
# 여기서는 파일 객체를 바로 사용
workbook = xlrd.open_workbook(file_contents=file.read())
sheet = workbook.sheet_by_index(0)
for row_index in range(1, sheet.nrows): # 헤더 행 건너뛰기
row_data = sheet.row_values(row_index)
# 예시: 첫 번째 열은 title, 두 번째는 price, 세 번째는 author
title = row_data[0]
price = row_data[1]
author = row_data[2]
# db_manager.execute("INSERT INTO books (title, price, author) VALUES (%s, %s, %s)", (title, price, author))
print(f"Inserting: {title}, {price}, {author}") # 실제 DB 삽입 로직 필요
return redirect('/success') # 성공 페이지로 리디렉션
except Exception as e:
return f"파일 처리 오류: {str(e)}", 500
else:
return "지원하지 않는 파일 형식입니다.", 400
# HTML 폼 예시 (upload.html)
# <form action="/upload_excel" method="post" enctype="multipart/form-data">
# <input type="file" name="file" accept=".xls, .xlsx" required>
# <button type="submit">업로드</button>
# </form>