Решения по проектированию в 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 (async-first). Это необходимо для сохранения обратной совместимости 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/stable/design/