본문으로 건너뛰기
김신건의 로그

[Flask] Application Factory Pattern

· 수정 · 📖 약 2분 · 610자/단어 #python #flask #patterns #structure
Flask App Factory, Flask Application Factory, create_app, Flask 애플리케이션 팩토리, flask 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 는 대문자 필드만. 소문자는 무시. 관용 지키기.

관련 위키

이 글의 용어 (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…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기