Web-приложения на Flask: как бороться с циклическими импортами

от автора

Flask – один из самых популярных фреймворков Python, но некоторые ошибки при его использовании могут привести к определенным затруднениям. В этой статье мы расскажем о том, как не допустить возникновения циклических импортов в проекте.

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/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *