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-приложений, связанную с циклическими импортами. С их помощью вы можете упростить доработку и поддержку ваших приложений.
Спасибо за внимание! Надеемся, что эта статья была вам полезна.
ссылка на оригинал статьи https://habr.com/ru/company/simbirsoft/blog/486112/

Добавить комментарий