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-проект.