Решение архитектурных задач в 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, поддержку итеративного рендеринга шаблонов, настраиваемый синтаксис и многое другое. С другой стороны, движок Genshi основан на оценке потока XML, наследовании шаблонов, учитывая доступность XPath, и многом другом. Mako, с другой стороны, рассматривает шаблоны как Python-модули.
Когда речь заходит о подключении движка шаблонов к приложению или фреймворку, существует больше, чем просто рендеринг шаблонов. Например, Flask использует широкую поддержку автоматической экранизации Jinja2. Также он предоставляет способы доступа к макросам из шаблонов Jinja2.
Абстракция уровня шаблонов, которая не лишала бы движки шаблонов их уникальных функций, — это целая наука и слишком большая задача для микрофреймворка, такого как Flask.
Кроме того, расширения могут легко полагаться на наличие одного языка шаблонов. Вы можете легко использовать свой собственный язык шаблонов, но расширение всё равно может зависеть от самого Jinja.
Что означает «микро»?
«Микро» не означает, что всё ваше веб-приложение должно помещаться в один Python-файл (хотя это, безусловно, возможно), и не означает, что Flask не функционален. «Микро» в микрофреймворке означает, что Flask стремится сохранить ядро простым, но расширяемым. Flask не будет принимать за вас многие решения, такие как выбор базы данных. Решения, которые он всё-таки принимает, например, какой движок шаблонов использовать, легко изменить. Всё остальное зависит от вас, чтобы Flask мог быть всем, что вам нужно, и ничего лишнего.
Flask по умолчанию не включает в себя абстракцию базы данных, валидацию форм или что-то ещё, где уже существуют другие библиотеки, которые могут с этим справиться. Вместо этого Flask поддерживает расширения для добавления такой функциональности в ваше приложение так, как будто она была реализована самим Flask. Многочисленные расширения обеспечивают интеграцию с базами данных, валидацию форм, обработку загрузки, различные технологии открытой аутентификации и многое другое. Flask может быть «микро», но он готов к использованию в производстве для самых разнообразных задач.
Почему 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 использует локальные объекты потоков (на самом деле контекстные локальные объекты, они также поддерживают контексты greenlet) для запроса, сессии и дополнительного объекта, на котором вы можете разместить свои собственные данные (g). Почему это так и не является ли это плохой идеей?
Да, в целом использование локальных переменных потоков — не лучшая идея. Они создают проблемы для серверов, не основанных на концепции потоков, и усложняют обслуживание крупных приложений. Однако Flask не предназначен для крупных приложений или асинхронных серверов. Flask стремится упростить и ускорить разработку традиционных веб-приложений.
Поддержка async/await и ASGI
Flask поддерживает async корутины для функций представления, выполняя корутину в отдельном потоке вместо использования цикла событий в главном потоке, как это делает асинхронный фреймворк (ASGI). Это необходимо для сохранения обратной совместимости Flask с расширениями и кодом, созданным до того, как async была введена в Python. Эта компромиссная мера приводит к снижению производительности по сравнению с ASGI-фреймворками из-за накладных расходов на потоки.
В связи с тесной привязкой кода Flask к WSGI, неясно, возможно ли сделать класс Flask одновременно поддерживать ASGI и WSGI. В настоящее время в Werkzeug ведётся работа над взаимодействием с ASGI, что может в конечном итоге позволить поддержку и в Flask.
См. Использование async и await для более подробного обсуждения.
Что такое Flask, а что Flask не является
В Flask никогда не будет слоя базы данных. Он не будет иметь библиотеку форм или что-либо подобное. Flask просто связывает Werkzeug для реализации WSGI-приложения и Jinja2 для обработки шаблонов. Он также связан с некоторыми общими стандартными библиотеками Python, такими как logging. Всё остальное — на усмотрение расширений.
Почему это так? Потому что у людей разные предпочтения и требования, и Flask не смог бы соответствовать им, если бы навязывал что-либо из этого в ядро. Большинство веб-приложений потребуют движок шаблонов. Однако не каждое приложение нуждается в базе данных SQL.
По мере роста вашего проекта вы можете принимать решения, подходящие для вашего проекта. Flask продолжит предоставлять очень простой слой связывания с лучшими возможностями Python. Вы можете реализовывать сложные шаблоны в SQLAlchemy или другом инструменте для баз данных, внедрять нереляционную персистентность данных, как это необходимо, и использовать инструменты, не зависящие от фреймворка, созданные для WSGI — веб-интерфейса Python.
Идея Flask заключается в том, чтобы создать хорошую основу для всех приложений. Всё остальное — на ваше усмотрение или же на усмотрение расширений.
© 2010 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/3.0.x/design/