Проектные решения во 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 для работы с шаблонами. Он также связывается с несколькими общими стандартными библиотечными пакетами, такими как логирование. Все остальное - на усмотрение расширений.

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

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

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

Вернуться на верх