Для построения 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)
Следует иметь в виду, что обработчики до и после запроса должны быть в файле, который импортирован заранее, чтобы правильно работать с первым запросом.