Решение архитектурных задач в 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 является обеспечение уникальности URL-адресов. Werkzeug пойдет достаточно далеко, автоматически перенаправляя на канонический URL, если маршрут неоднозначен.
Один движок шаблонов
Flask выбирает один движок шаблонов: Jinja2. Почему Flask не имеет интерфейса для подключения других движков шаблонов? Вы можете, конечно, использовать другой движок шаблонов, но Flask все равно настроит для вас Jinja2. Хотя это ограничение, что Jinja2 всегда будет настроен, вероятно, исчезнет, решение использовать один движок шаблонов не изменится.
Движки шаблонов похожи на языки программирования, и каждый из этих движков имеет определенное представление о том, как все работает. На поверхности все они работают одинаково: вы говорите движку оценить шаблон с набором переменных и использовать возвращаемое значение в качестве строки.
Но на этом сходства заканчиваются. Например, Jinja2 имеет расширенную систему фильтров, определенный способ реализации наследования шаблонов, поддержку многоразовых блоков (макросов), которые можно использовать как изнутри шаблонов, так и из кода Python, поддерживает итеративное рендеринг шаблонов, настраиваемый синтаксис и многое другое. С другой стороны, такой движок, как 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 использует локальные объекты потока (фактически, локальные объекты контекста, они также поддерживают контексты greenlet) для запросов, сессий и дополнительного объекта, на котором вы можете хранить свои собственные данные (g). Почему это так и не является ли это плохой идеей?
Да, использование локальных переменных потока обычно не является хорошей идеей. Они создают проблемы для серверов, которые не основаны на концепции потоков, и усложняют поддержку больших приложений. Однако Flask не предназначен для больших приложений или асинхронных серверов. 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 заключается в создании хорошей основы для всех приложений. Всё остальное зависит от вас или расширений.
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.1.x/design/