Flask – один из самых популярных фреймворков Python, но некоторые ошибки при его использовании могут привести к определенным затруднениям. В этой статье мы расскажем о том, как не допустить возникновения циклических импортов в проекте.
![](https://habrastorage.org/r/w1560/webt/k0/gq/r6/k0gqr6keoj9_qf15i_itjfbmqhs.png)
Разработчики, использующие flask, нередко сталкиваются с проблемой возникновения зависимостей между модулями. Для объявления view и моделей разработчик использует глобальные объекты, созданные и инициализированные в главном модуле («точке входа»). При этом он рискует получить циклические импорты, из-за которых проект будет трудно поддерживать.
Документация и основные учебники flask для решения этой проблемы предлагают вынести в __init__.py код инициализации проекта, который создает инстанс-классы Flask и производит настройку приложения. Это позволяет получить доступ ко всем глобальным объектам из области видимости пакета.
При использовании этого подхода структура выглядит примерно так:
app/__init__.py
app/views.py
Очевидно, что эта архитектура не очень удачна, поскольку все компоненты сильно связаны. Впоследствии дорабатывать такой проект будет сложно, потому что изменение кода в одном месте потянет за собой изменения в десятке других мест.
Как правило, мы решаем эту проблему следующим образом:
Давайте рассмотрим это подробнее.
Вместо стандартного способа роутинга, который описан в документации, можно применять classy. С этим подходом вам не нужно вручную писать роутинг для view: он настроится автоматически на основании имен ваших классов и методов. Это позволяет повышать структурированность кода, а также объявлять view без объекта app. В результате удается решить проблему циклического импорта.
Пример структуры проекта при использовании библиотеки flask-classful.
app.py
app/routes.py
app/views.py
При изучении кода необходимо обратить внимание на то, что теперь инициализация происходит в app.py, расположенном в корне. Приложение разбито на подпроекты, которые конфигурируются при помощи blueprint и в дальнейшем одной строкой регистрируются в объекте app.
Приведенный выше код показывает, как flask-classful помогает бороться с циклическими импортами. Причиной этой проблемы в классических проектах flask могут быть как объявление view, так и некоторые расширения. Один из ярких примеров – flask-sqlalchemy.
Расширение flask-sqlalchemy призвано улучшать интеграцию sqlalchemy и flask, но на практике оно зачастую привносит в проект больше проблем, чем пользы:
По этим причинам мы стараемся не использовать flask-sqlalchemy.
Внедрение classy-подхода и отказ от flask-sqlalchemy – это лишь первые шаги для решения проблемы циклического импорта. Далее нужно реализовать в приложении логику доступа к глобальным объектам. Для этого удобно применять паттерн dependency injection, реализованный в библиотеке dependency-injector.
Пример использования паттерна в коде с библиотекой dependency-injector:
app.py
app/routes.py
app/views.py
Перечисленные в статье меры позволяют устранить циклические импорты, а также повысить качество кода. Предлагаем посмотреть, как выглядит flask-проект с использованием описанных выше подходов, на примере игры «Быки и коровы», выполненной в виде web-приложения.
Мы рассмотрели, какими способами можно решить распространенную архитектурную проблему flask-приложений, связанную с циклическими импортами. С их помощью вы можете упростить доработку и поддержку ваших приложений.
Спасибо за внимание! Надеемся, что эта статья была вам полезна.
![](https://habrastorage.org/webt/k0/gq/r6/k0gqr6keoj9_qf15i_itjfbmqhs.png)
Flask и циклические импорты
Разработчики, использующие flask, нередко сталкиваются с проблемой возникновения зависимостей между модулями. Для объявления view и моделей разработчик использует глобальные объекты, созданные и инициализированные в главном модуле («точке входа»). При этом он рискует получить циклические импорты, из-за которых проект будет трудно поддерживать.
Документация и основные учебники flask для решения этой проблемы предлагают вынести в __init__.py код инициализации проекта, который создает инстанс-классы Flask и производит настройку приложения. Это позволяет получить доступ ко всем глобальным объектам из области видимости пакета.
При использовании этого подхода структура выглядит примерно так:
.
├── app
│ ├── __init__.py
│ ├── forms.py
│ ├── models.py
│ ├── views.py
│ └── templates
├── config.py
└── migrations
app/__init__.py
import flask
from flask_mail import Mail
# other extensions
app = Flask(__name__)
mail = Mail(app)
# configure flask app
from app import views, models
app/views.py
from app import app
@app.route('/view_name/'):
def view_name():
pass
Очевидно, что эта архитектура не очень удачна, поскольку все компоненты сильно связаны. Впоследствии дорабатывать такой проект будет сложно, потому что изменение кода в одном месте потянет за собой изменения в десятке других мест.
Как правило, мы решаем эту проблему следующим образом:
- Избегаем стандартного роутинга.
- Предпочитаем оригинальные версии библиотек, без «оберток».
- Используем dependency injection.
Давайте рассмотрим это подробнее.
Работа с classy
Вместо стандартного способа роутинга, который описан в документации, можно применять classy. С этим подходом вам не нужно вручную писать роутинг для view: он настроится автоматически на основании имен ваших классов и методов. Это позволяет повышать структурированность кода, а также объявлять view без объекта app. В результате удается решить проблему циклического импорта.
Пример структуры проекта при использовании библиотеки flask-classful.
.
├── app
│ ├── static
│ ├── templates
│ ├── forms.py
│ ├── routes.py
│ ├── views.py
│ └── tasks.py
├── models
├── app.py
├── config.py
└── handlers.py
app.py
import flask
from flask_mail import Mail
# other extensions
from app import routes as app_route
app = Flask(__name__)
mail = Mail(app)
# configure flask app
app.register_blueprint(app_route.app_blueprint)
app/routes.py
from flask import Blueprint
from app import views
app_blueprint = Blueprint(...)
views.AccountView.register(app_blueprint)
# register other views
app/views.py
from flask_classy import FlaskView, route
from flask_login import login_required
class AccountView(FlaskView):
def login(self):
pass
# other views
@login_required
def logout(self):
pass
При изучении кода необходимо обратить внимание на то, что теперь инициализация происходит в app.py, расположенном в корне. Приложение разбито на подпроекты, которые конфигурируются при помощи blueprint и в дальнейшем одной строкой регистрируются в объекте app.
Предпочтение оригинальным версиям библиотек
Приведенный выше код показывает, как flask-classful помогает бороться с циклическими импортами. Причиной этой проблемы в классических проектах flask могут быть как объявление view, так и некоторые расширения. Один из ярких примеров – flask-sqlalchemy.
Расширение flask-sqlalchemy призвано улучшать интеграцию sqlalchemy и flask, но на практике оно зачастую привносит в проект больше проблем, чем пользы:
- Расширение пропагандирует использование глобального объекта для взаимодействия с базой данных и в том числе для объявления моделей, что снова приводит к проблеме циклических импортов.
- Нужно описывать модели, используя свои собственные классы, что приводит к жесткой привязке моделей к flask проекту. В результате эти модели нельзя использовать в подпроектах или во вспомогательном скрипте.
По этим причинам мы стараемся не использовать flask-sqlalchemy.
Использование паттерна Dependency injection
Внедрение classy-подхода и отказ от flask-sqlalchemy – это лишь первые шаги для решения проблемы циклического импорта. Далее нужно реализовать в приложении логику доступа к глобальным объектам. Для этого удобно применять паттерн dependency injection, реализованный в библиотеке dependency-injector.
Пример использования паттерна в коде с библиотекой dependency-injector:
app.py
import dependency_injector.containers as di_cnt
import dependency_injector.providers as di_prv
from flask import Flask
from flask_mail import Mail
from app import views as app_views
from app import routes as app_routes
app = Flask(__name__)
mail = Mail(app)
# регистрация blueprints
app.register_blueprint(app_routes.app_blueprint)
# создание providers
class DIServices(di_cnt.DeclarativeContainer):
mail = di_prv.Object(mail)
# injection
app_views.DIServices.override(DIServices)
app/routes.py
from os.path import join
from flask import Blueprint
import config
from app import views
conf = config.get_config()
app_blueprint = Blueprint(
'app', __name__, template_folder=join(conf.BASE_DIR, 'app/templates'),
static_url_path='/static/app', static_folder='static'
)
views.AccountView.register(app_blueprint, route_base='/')
app/views.py
import dependency_injector.containers as di_cnt
import dependency_injector.providers as di_prv
from flask_classy import FlaskView
from flask_login import login_required
class DIServices(di_cnt.DeclarativeContainer):
mail = di_prv.Provider()
class AccountView(FlaskView):
def registration(self):
# реализация регистрации
msg = 'text'
DIServices.mail().send(msg)
def login(self):
pass
@login_required
def logout(self):
pass
Перечисленные в статье меры позволяют устранить циклические импорты, а также повысить качество кода. Предлагаем посмотреть, как выглядит flask-проект с использованием описанных выше подходов, на примере игры «Быки и коровы», выполненной в виде web-приложения.
Вывод
Мы рассмотрели, какими способами можно решить распространенную архитектурную проблему flask-приложений, связанную с циклическими импортами. С их помощью вы можете упростить доработку и поддержку ваших приложений.
Спасибо за внимание! Надеемся, что эта статья была вам полезна.