Для построения URL-адреса функции-представления, фреймворк Flask обычно используется с декораторами @app.route()
. Декораторы просты и удобны, к тому же рядом с функцией есть URL-адрес, на запрос которого, она отрисовывает страницу. НО у этого подхода есть обратная сторона: это означает, что весь код, использующий @app.route()
, должен быть импортирован заранее, иначе Flask никогда не найдет нужную функцию.
Это может быть проблемой, если приложение большое и должно быстро запускаться, а следовательно - импортировать множество представлений. Поэтому, если приложение переросло во что то большое, или часто получает программные обновления, в ходе которых происходит перезапуск приложения, то стоит подумать о централизованном подходе к сопоставлении URL-адресов.
Централизованный подход к сопоставлении URL-адресов обеспечивает метод экземпляра приложения app.add_url_rule()
.Идея состоит в том, что бы вместо использования декораторов, создать файл, который создает экземпляр приложения со всеми URL-адресами.
Допустим, что текущее приложение выглядит примерно так:
from flask import Flask app = Flask(__name__) @app.route('/') def index(): pass @app.route('/user/<username>') def user(username): pass
Тогда при централизованном подходе, в приложении будет два файла, один файл с представлениями (views.py
) без декораторов:
def index(): pass def user(username): pass
И второй файл, который создает экземпляр приложения и сопоставляет функции с URL-адресами:
from flask import Flask from yourapplication import views app = Flask(__name__) app.add_url_rule('/', view_func=views.index) app.add_url_rule('/user/<username>', view_func=views.user)
Пока произошло разделение функций-представлений от соответствующих им URL-адресов, но модуль все еще загружается заранее. Хитрость заключается в том, чтобы фактически загрузить функцию просмотра по мере необходимости. Это может быть выполнено с помощью вспомогательного класса, который ведет себя так же, как функция, но внутренне импортирует реальную функцию при первом использовании:
from werkzeug.utils import import_string, cached_property class LazyView(object): def __init__(self, import_name): self.__module__, self.__name__ = import_name.rsplit('.', 1) self.import_name = import_name @cached_property def view(self): return import_string(self.import_name) def __call__(self, *args, **kwargs): return self.view(*args, **kwargs)
Здесь важно то, что __module__
и __name__
были установлены правильно. Эти имена используется Flask внутри, для поиска названий функций-представлений соответствующим правилам URL.
Затем можно переписать файл, который создает экземпляр приложения, следующим образом:
from flask import Flask from yourapplication.helpers import LazyView app = Flask(__name__) app.add_url_rule('/', view_func=LazyView('yourapplication.views.index')) app.add_url_rule('/user/<username>', view_func=LazyView('yourapplication.views.user'))
Можно еще оптимизировать код выше с точки зрения объема, необходимого для записи правил маршрутизации. Для этого необходимо создать функцию, которая будет вызывать app.add_url_rule()
.
def url(import_name, url_rules=[], **options): # обернем полное название функции- # представления в `LazyView` view = LazyView(f"yourapplication.{import_name}") # пройдемся по всем URL принадлежащим # конкретной функции-представления for url_rule in url_rules: # регистрируем `app.add_url_rule` # при первом обращении к URL app.add_url_rule(url_rule, view_func=view, **options) # единственный маршрут к функции-представлению # `index()`, расположенной в файле `views.py` url('views.index', ['/']) # два маршрута к функции-представлению `user()`, # расположенной в файле `views.py` url_rules = ['/user/', '/user/<username>'] url('views.user', url_rules)
Следует иметь в виду, что обработчики до и после запроса должны быть в файле, который импортирован заранее, чтобы правильно работать с первым запросом.