Application factory и структура проекта

Application factory — это функция create_app(), которая собирает и возвращает приложение. Она пришла на смену глобальному app и стала стандартом Flask 3.x.
Вместо «создал app один раз на уровне модуля» ты заворачиваешь сборку в функцию. Это позволяет создавать несколько экземпляров с разными настройками — например, отдельный для тестов с тестовой базой — и избавляет от циклических импортов.

В простых туториалах app создают глобально: app = Flask(__name__) прямо в модуле. Для одного файла это нормально. Но как только проект растёт — появляются блюпринты, расширения, конфигурации — глобальный app превращается в проблему. Модули начинают импортировать друг друга по кругу (view импортирует app, app импортирует view), а тесты не могут создать приложение с другими настройками. Фабрика решает обе беды.

Идея проста: вся сборка живёт внутри функции. Снаружи её вызывают, когда нужен экземпляр приложения.

from flask import Flask

def create_app(config=None):
    app = Flask(__name__)
    app.config.from_mapping(
        SECRET_KEY="dev",
    )
    if config:
        app.config.update(config)

    # инициализация расширений
    # db.init_app(app)

    # регистрация блюпринтов
    from .views import main
    app.register_blueprint(main)

    return app

Запуск через flask --app "myapp:create_app" run или просто flask run, если функция называется create_app — Flask находит её автоматически.

Типичная структура проекта средней руки:

  myapp/
  ├── myapp/
  │   ├── __init__.py      # create_app() здесь
  │   ├── views.py         # блюпринт с маршрутами
  │   ├── models.py        # модели БД
  │   ├── extensions.py    # db = SQLAlchemy() и др.
  │   ├── templates/       # Jinja-шаблоны
  │   └── static/          # css, js, картинки
  ├── tests/
  ├── requirements.txt
  └── config.py

Может показаться, что для маленького проекта фабрика — избыточная церемония, ведь хватило бы и глобального app. Но опыт показывает обратное: проекты растут, и переписывать архитектуру на ходу больно. Заложив фабрику с первого дня, ты бесплатно получаешь готовность к тестам (отдельный экземпляр с тестовой базой), к разным средам (dev/prod-конфиги) и к блюпринтам (импорт внутри функции снимает циклы). Это вложение, которое окупается на втором же спринте. Запомни связку: extensions.py хранит расширения пустыми, фабрика их инициализирует через init_app, конфигурация приходит аргументом или из объекта — этот скелет ты будешь воспроизводить в каждом Flask-проекте.

Как работает под капотом

Ключевой приём — расширения создают «пустыми» на уровне модуля, а привязывают к приложению внутри фабрики методом init_app. Так один объект расширения не хранит состояние конкретного приложения и может обслуживать несколько экземпляров.

  extensions.py:  db = SQLAlchemy()      ← объект без приложения
        │
        ▼
  create_app():   db.init_app(app)       ← привязка к этому app
        │
        ▼
  несколько вызовов create_app() → несколько app, один db-объект

Смоделируем фабрику обычным Python: функция собирает «приложение»-словарь с разными настройками, и мы создаём два независимых экземпляра.

def create_app(config=None):
    app = {"routes": {}, "config": {"SECRET_KEY": "dev", "TESTING": False}}
    if config:
        app["config"].update(config)
    # регистрируем маршрут
    app["routes"]["/"] = "index_view"
    return app

prod = create_app()
test = create_app({"TESTING": True, "SECRET_KEY": "x"})

print("prod TESTING:", prod["config"]["TESTING"])
print("test TESTING:", test["config"]["TESTING"])
print("разные объекты:", prod is not test)

Запусти: два приложения с разными настройками из одной фабрики — именно ради этого паттерн и нужен. Тесты получают TESTING=True и тестовую базу, продакшен — боевые значения.

Частые ошибки

  • Создавать расширения внутри фабрики. Тогда другие модули не смогут их импортировать. Создавай в extensions.py, привязывай в фабрике через init_app.
  • Импортировать блюпринты на верхнем уровне __init__.py. Импортируй их внутри create_app, чтобы избежать циклов.
  • Хранить секреты в коде. SECRET_KEY и пароли БД — из переменных окружения, не из исходников.

Best practices

  • Конфигурацию выноси в отдельный класс/файл, грузи через app.config.from_object.
  • Принимай config-аргумент в фабрике — это точка входа для тестов.
  • Расширения: создал глобально пустым → init_app в фабрике.

Что запомнить

  • create_app собирает приложение внутри функции, а не глобально.
  • Фабрика даёт изоляцию настроек, удобные тесты и отсутствие циклических импортов.
  • Расширения создают пустыми в extensions.py и привязывают через init_app.
  • Конфигурацию выносят в отдельный класс/файл и грузят в фабрике.

Итог: фабрика create_app собирает приложение внутри функции, что даёт изоляцию настроек, удобные тесты и отсутствие циклических импортов. Это фундамент, на котором стоит весь современный Flask-проект.

Проверьте себя
1. Главное преимущество application factory?
AПриложение работает быстрее
BМожно создавать несколько экземпляров с разными настройками и избегать циклических импортов
CНе нужен SECRET_KEY
DFlask сам пишет маршруты
2. Где правильно создавать объект расширения (например db = SQLAlchemy())?
AВнутри create_app
BНа уровне модуля (extensions.py) пустым, а привязывать через init_app в фабрике
CВ каждом view-файле заново
DВ шаблоне Jinja2