воскресенье, 24 марта 2013 г.

Проектные решения во Flask

Перевод статьи: Design Decisions in Flask

Если вам кажется странным, почему Flask делает некоторые вещи именно так, а не как-нибудь иначе, этот раздел для вас. После его прочтения у вас должно появиться понимание, почему некоторые проектные решения, на первый взгляд кажутся случайными и неожиданными, особенно по сравнению с другими фреймворками.

Явный объект приложения

Веб-приложения на Python, основанные на WSGI, должны обладать центральным вызываемым объектом, реализующим приложение. Во Flask это экземпляр класса Flask. Каждое приложение Flask должно само создать экземпляр этого класса и передать ему имя модуля, но почему Flask не может сделать это сам?

Если бы не было явного объекта приложения, то следующий код:
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World!'
Выглядел бы так:
from hypothetical_flask import route

@route('/')
def index():
    return 'Hello World!'
Для этого есть три основных причины. Наиболее важная заключается в том, что неявный объект приложения может существовать одновременно только в одном экземпляре. Есть способы сымитировать множество приложений в одном объекте приложения, например, поддерживая пачку приложений, но это вызовет некоторые проблемы, в детали которых мы не станем вдаваться. Следующий вопрос: в каких случаях микрофреймворку может понадобиться создавать больше одного приложения одновременно? Хорошим примером является модульное тестирование. Когда вам нужно протестировать что-нибудь, может оказаться полезным создать минимальное приложение для тестирования определённого поведения. Когда объект приложения удалятся, занятые им ресурсы снова становятся свободными.

Другой возможной причиной, почему могут пригодиться явные объекты приложений, может быть необходимость обернуть объект в дополнительный код, что можно сделать создав подкласс базового класса (Flask) для изменения части его поведения. Это невозможно сделать без костылей, если объект был создан до этого, на основе класса, который вам не доступен.

Но существует и ещё одна очень важная причина, почему Flask зависит от явных экземпляров классов: имя пакета. Когда бы вы ни создавали экземпляр Flask, обычно вы передаёте ему __name__ в качестве имени пакета. Flask использует эту информацию для правильной загрузки ресурсов относящихся к модулю. При помощи выдающихся возможностей Python к рефлексии, вы можете получить доступ к пакету, чтобы узнать, где хранятся шаблоны и статические файлы (см. open_resource()). Очевидно, что фреймворкам не требуется какая-либо настройка и они по-прежнему смогут загружать шаблоны относительно модуля вашего приложения. Но они используют для этого текущий рабочий каталог, поэтому нет возможности надёжно узнать, где именно находится приложение. Текущий рабочий каталог одинаков в рамках процесса, поэтому если запустить несколько приложений в рамках одного процесса (а это может происходить в веб-сервере, даже если вы об этом не догадываетесь), путь может измениться. Даже хуже: многие веб-серверы устанавливают рабочим каталогом не каталог приложения, а корневой каталог документов, который обычно является другим каталогом.

Третья причина заключается в том, что "явное лучше неявного". Этот объект - ваше приложение WSGI, вам больше не нужно помнить о чём-то ещё. Если вам нужно воспользоваться промежуточным модулем WSGI, просто оберните его этим модулем и всё (хотя есть способы лучше, чтобы сделать это, если вы не хотите потерять ссылки на объект приложения wsgi_app()).

Кроме того, такой дизайн делает возможным использование фабричных методов для создания приложения, которое может оказаться полезным для модульного тестирования и других подобных вещей (Фабрики приложений).

Система маршрутизации

Flask использует систему маршрутизации Werkzeug, который был спроектирован для того, чтобы автоматически упорядочивать маршруты в зависимости от их сложности. Это означает, что вы можете объявлять маршруты в произвольном порядке и они всё равно будут работать ожидаемым образом. Это необходимо, если вы хотите, чтобы маршруты, основанные на декораторах, работали правильно, потому что декораторы могут активироваться в неопределённом порядке, если приложение разделено на несколько модулей.

Другое проектное решение системы маршрутизации Werkzeug заключается в том, что Werkzeug старается, чтобы URL'ы были уникальными. Werkzeug настолько последователен в этом, что автоматически выполнит переадресацию на канонический URL, если маршрут окажется неоднозначным.

Одна система шаблонизации

Flask остановился на одной системе шаблонизации: Jinja2. Почему Flask не имеет интерфейса для подключения систем шаблонизации? Очевидно, что вы можете использовать любые системы шаблонизации, но Flask всё равно настроит Jinja2. Хотя всегда настроенная Jinja2, возможно и не потребуется, скорее всего нет причин, чтобы не принять решение воспользоваться единой системой шаблонизации.

Системы шаблонизации похожи на языки программирования и каждая из них обладает собственным мировоззрением. Внешне они все работают одинаково: вы просите систему шаблонизации заполнить шаблон набором переменных и получаете его в виде строки.

Но на этом сходство заканчивается. Jinja2, например, обдает системой расширяемых фильтров, возможностью наследования шаблонов, поддержкой повторного использования блоков (макросов), которые можно использовать внутри шаблонов и из кода на Python для всех операций, использует Unicode, поддерживает поэтапную отрисовку шаблонов, настраиваемый синтаксис и многое другое. С другой стороны, системы шаблонизации вроде Genshi основываются на потоковом заполнении XML, наследовании шаблонов с помощью XPath и т.п. Mako интерпретирует шаблоны аналогично модулям Python.

Когда нужно соединить систему шаблонизации с приложением или фреймворком, требуется больше чем просто отрисовка шаблонов. Например, Flask широко использует поддержку экранирования из Jinja2. А ещё он позволяет получить доступ к макросам из шаблонов Jinja2.

Слой абстракции над системами шаблонизации, учитывающий уникальные особенности каждой из них - это почти наука, слишком сложная для поддержки в микрофреймворке вроде Flask.

Кроме того, расширения из-за этого могут оказаться слишком зависимыми от имеющейся системы шаблонизации. Вы можете воспользоваться собственным языком шаблонов, но расширения по-прежнему будут зависеть от Jinja.

Микро, но с зависимостями

Почему Flask называет себя микрофреймворком, если он зависит от двух библиотек (а именно - от Werkzeug и Jinja2)? Если посмотреть на Ruby со стороны веб-разработки, то и там имеется протокол очень похожий на WSGI. Только там он называется Rack, но всё же он больше похож на реализацию WSGI для Ruby. Но практически ни одно приложение для Ruby не работает с Rack напрямую, используя для этого одноимённую библиотеку. У библиотеки Rack в Python имеется два аналога: WebOb (прежде известная под именем Paste) и Werkzeug. Paste всё ещё существует, но насколько я понимаю, он уступил своё место в пользу WebOb. Разработчики WebOb и Werkzeug развивались параллельно, имея схожие цели: сделать хорошую реализацию WSGI для использования в других приложениях.

Фреймворк Flask использует преимущества уже реализованные в Werkzeug для правильного взаимодействия с WSGI (что иногда может оказаться сложной задачей). Благодаря недавним разработкам в инфраструктуре пакетов Python, пакеты с зависимостями больше не являются проблемой и осталось мало причин, чтобы воздерживаться от использования библиотек, зависящих от других библиотек.

Локальные объекты потоков

Flask использует объекты, локальные внутри потока (фактически - объекты локального контекста, по сути они поддерживают контексты гринлетов) для запроса, сеанса и для дополнительного объекта, в который можно положить собственные объекты (g). Почему сделано так и почему это не так уж плохо?

Да, обычно это не лучшая мысль, использовать локальные объекты потоков. Они могут вызвать проблемы с серверами, которые не основаны на понятии потоков и приводят к сложностям в сопровождении больших приложений. Однако, Flask просто не предназначен для больших приложений или асинхронных серверов. Flask стремится упростить и ускорить разработку традиционных веб-приложений.

Также обратитесь к разделу документации Становимся большими за мыслями о том, как стоит писать большие приложения на основе Flask.

Что есть Flask и чем он не является

Во Flask никогда не будет слоя для работы с базами данных. В нём нет библиотеки форм или чего-то подобного. Flask сам по себе является мостом к Werkzeug для реализации хорошего приложения WSGI и к Jinja2 для обработки шаблонов. Он также связан с несколькими стандартными библиотеками, например, с библиотекой журналирования logging. Всё остальное отдаётся на откуп расширениям.

Почему так? Потому что люди обладают разными предпочтениями и требованиями и Flask не сможет удовлетворить их, если всё это будет частью ядра. Большинству веб-приложений нужна какая-нибудь система шаблонизации. Однако не каждому приложению необходима база данных SQL.

Идея Flask заключается в создании хорошей основы для других приложений. Всё остальное должны сделать вы сами или расширения.

Примечания переводчика

В сети можно найти перевод первых глав документации по Flask - Flask - викиучебник.

Этот и другие переводы можно найти на сайте проекта перевода документации по Flask. Автор проекта - Виталий Кузьмин aka ferm32.

Комментариев нет: