Решение архитектурных задач в 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 просто предоставляет мосты к Werkzeug для реализации приложения WSGI и к Jinja2 для обработки шаблонов. Он также связан с несколькими стандартными библиотеками Python, такими как logging. Всё остальное — задача расширений.
Почему так? Потому что у людей разные предпочтения и требования, и Flask не смог бы их удовлетворить, если бы принудительно включал всё это в ядро. Большинству веб-приложений понадобится движок шаблонов в какой-то форме. Однако не каждое приложение нуждается в базе данных SQL.
По мере роста вашего кода вы можете принимать решения об архитектуре, соответствующие вашему проекту. Flask продолжит предоставлять очень простой слой связи с лучшими возможностями Python. Вы можете реализовать сложные шаблоны в SQLAlchemy или другом инструменте для работы с базами данных, реализовать нереляционную систему хранения данных, как это необходимо, и использовать инструменты, не связанные с фреймворком, разработанные для WSGI, интерфейса Python для веб-приложений.
Цель Flask — создать хорошую основу для всех приложений. Всё остальное зависит от вас или расширений.
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.3.x/design/