[Flask] Application Factory Pattern
정의
Application Factory 는 Flask 앱 인스턴스를 함수 안에서 생성 하는 패턴입니다. 모듈 스코프에 app = Flask(__name__) 를 두는 대신 create_app(config_name) 함수를 정의해 반환합니다.
이 패턴은 사실상 모든 non-trivial Flask 프로젝트의 표준 입니다.
왜 필요한가
1. 여러 앱 인스턴스
- 테스트: 각 테스트가 독립된 앱 인스턴스 (config, DB)
- 여러 환경: dev/staging/prod 를 코드 변경 없이 config 만 바꿔 인스턴스화
- 멀티-테넌시: 한 프로세스 안에서 여러 앱
2. 순환 import 회피
모듈 스코프 app 은 확장 (extension) 초기화 시점 문제:
# BAD: 순환 import
# app.py
from flask import Flask
from myapp.models import User # 이 파일이 app 을 import 하면 순환
app = Flask(__name__)
Factory 패턴은 create_app 안에서 import 하여 우회.
3. Extension 지연 초기화
db = SQLAlchemy() 를 모듈 스코프에, db.init_app(app) 을 factory 안에 두어 여러 앱과 조합 가능.
4. Blueprint 등록 지점 명확화
Factory 함수 하나가 앱 조립의 유일한 진입점. 어디서 무엇이 등록되는지 명확.
기본 구조
myapp/
├── src/
│ └── myapp/
│ ├── __init__.py # create_app
│ ├── config.py
│ ├── extensions.py # db, migrate, login 등 인스턴스
│ ├── models.py
│ ├── auth/
│ │ └── routes.py # Blueprint
│ ├── main/
│ │ └── routes.py # Blueprint
│ └── cli.py # 커스텀 CLI 명령
├── migrations/
├── tests/
├── wsgi.py # 진입점
└── pyproject.toml
1. Config
# myapp/config.py
import os
class Config:
SECRET_KEY = os.environ["SECRET_KEY"]
SQLALCHEMY_DATABASE_URI = os.environ["DATABASE_URL"]
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
class DevelopmentConfig(Config):
DEBUG = True
SESSION_COOKIE_SECURE = False
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
WTF_CSRF_ENABLED = False
class ProductionConfig(Config):
pass
def get_config(name: str = "production") -> type[Config]:
return {
"development": DevelopmentConfig,
"testing": TestingConfig,
"production": ProductionConfig,
}[name]
2. Extensions
# myapp/extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from flask_caching import Cache
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
csrf = CSRFProtect()
cache = Cache()
3. Factory 함수
# myapp/__init__.py
from flask import Flask
from myapp.config import get_config
from myapp.extensions import db, migrate, login, csrf, cache
def create_app(config_name: str = "production") -> Flask:
app = Flask(__name__, instance_relative_config=True)
app.config.from_object(get_config(config_name))
_init_extensions(app)
_register_blueprints(app)
_register_error_handlers(app)
_register_cli(app)
_register_shell_context(app)
return app
def _init_extensions(app: Flask) -> None:
db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
csrf.init_app(app)
cache.init_app(app, config={"CACHE_TYPE": "RedisCache"})
login.login_view = "auth.login"
@login.user_loader
def load_user(user_id: str):
from myapp.models import User
return db.session.get(User, int(user_id))
def _register_blueprints(app: Flask) -> None:
from myapp.auth.routes import bp as auth_bp
from myapp.main.routes import bp as main_bp
from myapp.api.v1 import bp as api_v1_bp
app.register_blueprint(auth_bp, url_prefix="/auth")
app.register_blueprint(api_v1_bp, url_prefix="/api/v1")
app.register_blueprint(main_bp)
def _register_error_handlers(app: Flask) -> None:
from flask import jsonify
@app.errorhandler(404)
def not_found(e):
return jsonify(error="not_found"), 404
@app.errorhandler(500)
def server_error(e):
return jsonify(error="server_error"), 500
def _register_cli(app: Flask) -> None:
from myapp.cli import seed_db, clear_cache
app.cli.add_command(seed_db)
app.cli.add_command(clear_cache)
def _register_shell_context(app: Flask) -> None:
@app.shell_context_processor
def make_context():
from myapp.models import User, Item
return {"db": db, "User": User, "Item": Item}
4. WSGI 진입점
# wsgi.py
import os
from myapp import create_app
config_name = os.environ.get("FLASK_ENV", "production")
app = create_app(config_name)
if __name__ == "__main__":
app.run()
gunicorn myapp:create_app() 또는 gunicorn wsgi:app.
flask CLI 통합
export FLASK_APP="myapp:create_app('development')"
flask run
flask db migrate -m "add users"
flask db upgrade
flask seed-db --count 100
또는 .flaskenv (python-dotenv):
FLASK_APP=myapp:create_app('development')
Blueprint 순서 관리
Blueprint 등록 순서가 URL 매칭 순서에 영향 (같은 URL 이 여러 blueprint 에 걸릴 때 첫 번째 승리):
# 순서 중요
app.register_blueprint(auth_bp, url_prefix="/auth") # 먼저: /auth/*
app.register_blueprint(main_bp) # 나중: 나머지
여러 앱 인스턴스 (테스트)
# tests/conftest.py
import pytest
from myapp import create_app
from myapp.extensions import db
@pytest.fixture
def app():
app = create_app("testing")
with app.app_context():
db.create_all()
yield app
db.session.remove()
db.drop_all()
@pytest.fixture
def client(app):
return app.test_client()
각 테스트가 완전히 격리된 앱을 갖음.
App Context 관리
Extension 초기화 이후 current_app, db 를 사용하려면 app context 필요:
with app.app_context():
db.create_all() # 스크립트, 마이그레이션 등
User(email="a@b.com").insert()
Request 안에서는 자동으로 활성. Background thread 나 CLI 에서는 명시 필요.
Shell context
@app.shell_context_processor
def make_context():
return {"db": db, "User": User}
이후 flask shell 실행 시 자동으로 이 변수들이 REPL 에 노출.
함정
WARNING
Extension 을 모듈 스코프에서 app 을 참조하면 안 됨. db = SQLAlchemy(app) 은 factory 패턴에서 못 쓸 방식. db = SQLAlchemy() + db.init_app(app).
CAUTION
Blueprint import 를 모듈 상단이 아니라 factory 안에서. 순환 import 회피의 핵심.
WARNING
여러 앱에서 db.session 공유. Flask-SQLAlchemy 는 앱 컨텍스트별로 세션 관리. Threading 이나 background 에서 잘못 쓰면 오염.
IMPORTANT
Config class 상속 순서. TestingConfig(Config) 는 부모의 SECRET_KEY 를 상속. 테스트에서 명시적 값 override.
CAUTION
app.config.from_object 는 대문자 필드만. 소문자는 무시. 관용 지키기.
관련 위키
- Flask - 상위 개요
- Flask Blueprints - Blueprint 조립
- Flask Extensions - init_app 패턴
- Flask Testing - fixture
- Flask Deployment - WSGI 진입점
- Flask Routing
이 글의 용어 (6개)
- [Flask] Blueprintsflask
- 정의 Blueprint 는 Flask 앱을 여러 모듈로 나누는 표준 도구입니다. 라우트, 정적파일, 템플릿, before/after hook, error handler 를 그룹화…
- [Flask] Deployment (WSGI, Gunicorn, uWSGI, Docker)flask
- 정의 Flask 는 WSGI 앱. 프로덕션 배포는 WSGI 서버 (Gunicorn, uWSGI, gevent) + 리버스 프록시 (Nginx, ALB) + 컨테이너 (Docker…
- [Flask] Extensions (SQLAlchemy, Login, Migrate, WTF, ...)flask
- 정의 Flask Extension 은 코어에 없는 기능 (DB, 인증, 마이그레이션, 캐시 등) 을 표준 인터페이스로 통합하는 서드파티 패키지입니다. 패턴이 관용이며, appli…
- [Flask] Routing (URL Rules, Converters, url_for)flask
- 정의 Flask Routing 은 Werkzeug 의 시스템에 기반한 URL 매핑입니다. 데코레이터 ( , , ...) 로 URL 패턴을 뷰 함수에 연결하고, converter …
- [Flask] Testingflask
- 정의 Flask Testing 은 앱 인스턴스를 직접 감싸 HTTP 요청을 시뮬레이션하는 workflow 입니다. (Werkzeug) 로 라우팅/뷰/미들웨어까지 실제 실행하고 응…
- [Python] Flaskflask
- 정의 Flask 는 Armin Ronacher 가 2010년 발표한 WSGI 기반 Python 마이크로프레임워크 입니다. Pallets Projects 가 유지관리하고, 2026…
이 개념을 다룬 위키 페이지 (8)
- wiki[Python] Flask
- wiki[Flask] Blueprints
- wiki[Flask] Deployment (WSGI, Gunicorn, uWSGI, Docker)
- wiki[Flask] Extensions (SQLAlchemy, Login, Migrate, WTF, ...)
- wiki[Flask] Routing (URL Rules, Converters, url_for)
- wiki[Flask] Templates (Jinja2)
- wiki[Flask] Testing
- wiki[NestJS] Config (Environment, Validation)
💬 댓글