Flask фреймворк правильная структура приложения
Это первая статья в серии, где я буду документировать мой опыт написания веб-приложения на Python, используя микрофреймворк Flask.
Моя предыстория
Я разработчик ПО с двузначным числом лет опыта разработки комплексных приложений на нескольких языках. Впервые я познакомился с Python для создания привязок к C++ библиотеке на работе. Вдобавок к Python, я писал веб-приложения на PHP, Ruby, Smalltalk и, верите вы или нет, еще на С++. Из всего этого, я нахожу комбинацию Python/Flask самой гибкой.
Приложение
Приложение, которое я собираюсь разрабатывать как часть этого руководства, является сервером микроблогов, и я решил назвать его microblog. Довольно креативно, я знаю.
Во время нашего прогресса я затрону следующие темы:
- Управление пользователями, включая управление входами, сессиями, ролями пользователя, профилями и пользовательскими аватарами
- Управление базой данных, включая миграцию
- Поддержка форм, включая валидацию полей
- Пагинация длинных списков элементов
- Полнотекстовый поиск
- E-mail уведомления пользователей
- HTML шаблоны
- Поддержка нескольких языков
- Кеширование и другая оптимизация производительности
- Методы отладки для серверов разработки и продакшна
- Установка на продакшн сервер
Как видите, я весьма значительно берусь за все это. Надеюсь, что это приложение, когда я его закончу, станет чем-то вроде шаблона для написания других веб-приложений.
Требования
Если ваш компьютер поддерживает Python 2.6/2.7, то все, вероятно, пойдет хорошо. Приложение из руководства должно нормально работать на Windows, OS X и Linux.
Это руководство подразумевает, что вы знакомы с окном терминала (командной консолью в случае Windows), и вы знаете основные команды для работы с файлами вашей ОС. Если это не так, то перед тем как продолжить, я рекомендую вам ознакомиться с тем, как создавать директории, копировать файлы и т.д., используя консоль.
Наконец, вы должны чувствовать себя свободно в написании кода на Python. Знакомство с модулями и пакетами этого языка также не помешает.
Установка Flask
Ну что ж, приступим!
Если у вас еще не установлен Python 2.7, то берем его отсюда.
Теперь нам нужно установить Flask и несколько расширений, которые мы будем использовать. Я предпочитаю создание виртуального окружения, где все это будет установлено таким образом, что ваша основная установка Python затронута не будет. В качестве бонуса, вам не нужен будет root доступ для установки таким способом.
Итак, открываем терминал, выбираем место, где будет ваше приложение, и создаем новую папку, в которой оно будет содержаться. Назовем эту папку microblog .
Далее скачиваем virtualenv.py и кладем его внутрь новой папки.
Чтобы создать новое окружение введите следующую команду:
Эта команда создаст полноценное окружение Python внутри папки flask
Виртуальные окружения могут быть активированы и деактивированы по желанию. Активированное окружение добавляет путь своей папки bin в path системы, например, когда вы вызываете интерпретатор python, то получаете версию текущего окружения, а не системную. Лично я никогда не любил это свойство, поэтому ни разу не активировал ни одно из моих окружений, вместо этого я просто вызывал тот интерпретатор, который хотел, печатая его путь.
Прим. пер.: Перед тем как вы начнете вводить команды, хочу отметить, что в комментариях к девятой части отметится баг в flask-whooshalchemy, исправленную версию можнонужно скачать отсюда (репозиторий автора)
Если вы пользователь Linux, OS x или Cygwin, установите flask и расширения, вводя следующие команды одну за одной:
Эти команды скачают и установят все пакеты, которые мы будем использовать в нашем приложении.
Учтите, что мы собираемся использовать Flask 0.9, а не последнюю версию. Flask 0.10 не было довольно долго и некоторые расширения не готовы для работы с данной версией. Также есть несколько несовместимостей между пакетами и последней версией pip , которые решены использованием конкретных версий для установки.
Windows пользователей ждет еще один шаг. Наблюдательный читаель заметит, что в списке команд для Windows отсутствует flask-mail . Это расширение не устанавливается чисто на Windows, поэтому мы пойдем обходным путем:
Я не буду вдаваться в подробности, поэтому если вы хотите узнать больше, прочтите документацию к flask-mail.
Если установка всех пакетов прошла успешно, вы можете удалить virtualenv.py , поскольку файл нам больше не понадобится.
«Hello, World!» в Flask
Теперь у вас есть подпапка flask , в вашей папке microblog , содержащая интерпретатор Python и фреймворк Flask с расширениями, которые мы будем использовать в этом приложении. Настало время написать наше первое веб-приложение!
После того как вы переместились папку microblog , давайте создадим основную структуру папок для нашего приложения:
В папку app мы поместим само наше приложение. Подпапка static нужна для хранения статики, такой как картинки, javascript файлы и таблицы стилей. Подпапка templates , очевидно, предназначена для хранения наших шаблонов.
Давайте начнем с создания простого скрипта инициализации нашего пакета app (файл app/__init__.py )
Скрипт выше просто создает объект приложения (наследуя Flask ), затем импортирует модуль представлений, который мы еще не написали.
Представления — это обработчики, которые отвечают на запросы веб-браузера. Представления в Flask пишутся как Python функции. Каждая функция представления сопоставляется с одним или несколькими запросами URL.
Напишем нашу первую функцию представления (файл app/views.py )
Это весьма простое представление, которое просто возвращает строку для отображения в пользовательском браузере. Два декоратора route создают привязку адресов / и /index к этой функции.
Последним шагом для получения полностью работающего веб-приложения будет создание скрипта, который стартует веб-сервер нашего приложения. Давайте назовем скрипт run.py и положим его в корневой каталог ( microblog/ ):
Скрипт просто импортирует переменную app из нашего пакета app и вызывает метод run для того, чтобы запустить сервер. Помните, что переменная app — экземпляр класса Flask , мы создали его выше.
Для запуска вашего приложения просто запустите скрипт. На OS X, Linux и Cygwin вам следует пометить файл исполняемым перед тем как вы сможете его запустить.
Тогда скрипт может быть вызван просто:
В Windows процесс немного отличается. Нет необходимости помечать файл исполняемым. Вместо этого вам нужно запустить скрипт как аргумент интерпретатора Python:
После старта сервер будет прослушивать порт 5000, ожидая соединений. Теперь откроем браузер и введем следующий URL в адресную строку:
Или же вы можете использовать эту ссылку:
Теперь вы увидели маршрутизацию в действии? Первый URL привязан к / , в то время как второй к /index . Оба маршрута ассоциированы с нашей функцией представления, поэтому они дают одинаковый результат. Если вы введете любой другой маршрут, то получите ошибку, так как только эти два были привязаны к функции представления.
Когда вы закончите играть с сервером вы можете просто нажать Ctrl-C, чтобы остановить его.
И этим я хочу закончить первую часть этого руководства.
Те из вас, кто поленился печатать, могут скачать код из этого руководства ниже:
Обратите внимание, что вам нужно установить Flask, чтобы запустить приложение из архива выше.
Что дальше
В следующей части серии мы изменим наше маленькое приложение для использования HTML шаблонов.
В мире python существует довольно много различных приложений "супервизоров" предназначенных для запуска веб-приложений: uWSGI, gunicorn, gevent, twisted web и т.д. Однако, пожалуй, наиболее производительным и гибким супервизором является uWSGI. В этом посте я расскажу как запустить ваше Flask приложение под uWSGI. Конечно же, настроим nginx для проксирования запросов и отдачи статического контента.
Заключение
В этой статье мы рассмотрели два самых распространенных варианта запуска веб-приложения написанного на Flask под uWSGI. Конечно же, конфигурация uWSGI представленная в посте является довольно простой, но подойдёт для старта особенно тем, кто не знаком с uWSGI. Для нагруженных крупных проектов я настоятельно рекомендую ознакомиться с документацией uwsgi и подстроить под свои нужды.
Многие фреймворки диктуют свою структуру приложения при разработке. С одной стороны это хорошо: в больших проектах человеку знакомому с фреймворком будет легче и проще вникнуть в проект. С другой стороны - это накладывает определенные ограничения, и иногда вынуждает использовать методы и паттерны без которых вполне можно было обойтись. Во всех таких фреймворках есть определенные инструменты позволяющие разделять приложение на модули. Во flask так же есть подобный инструмент, и называются он blueprints.
Вопреки частому заблуждению, flask прекрасно подходит для больших проектов. В нескольких таких крупных проектах мне даже довелось учавствовать. Поэтому, если вас убеждают, что flask никто не использует не верьте им. Ещё как используют. Flask прекрасный, небольшой фреймворк подходящий для очень широкого круга задач.
В этой статье я расскажу про наиболее популярную, гибкую и правильную на мой взгляд структуру flask приложения. Используя подобную структуру при разработке, вы сможете построить гибкое и удобное приложение модули которого можно будет с легкостью переиспользовать в других проектах. Определенно стоит уточнить, что серебряной пули нет, и далеко не всем может подойти такая структура, но если вы не очень хорошо знакомы с flask, то вам определенно стоит как минимум взять её за основу.
Для тех, кто не знаком с Flask - это небольшой фреймворк написанный на языке python c весьма большим сообществом, и множеством модулей на все случаи жизни. В отличии от, скажем, Django, Flask не навязывает определенное решение той или иной задачи. Вместо этого, он предлагает использовать различные сторонние или собственные решения по вашему усмотрению. Я ни в коем случае не хочу сказать, что Django плохой фреймворк. Просто в некоторых случаях Django вынуждает писать много лишнего кода попутно залезая все глубже и глубже во внутренности самого фреймворка. Так что flask - это лишь ещё один инструмент, подходящий под определенные задачи.
Disclaimer: в этой статье нет ничего принципиально нового и неизвестного. Все это общеизвестные практики, только собранные в одном месте. Эта статья ориентирована в основном на тех, кто не знаком с flask и новичков.
Чаще всего я (да и большинство остальных разработчиков) использую flask совместно со следующими python-библиотеками:
- Sqlalchemy (используя расширение Flask-Sqlalchemy) - для работы с базой данных;
- Wtforms - (используя расширение flask-wtf) для обработки html-форм;
- psycopg2 - прослойка для Sqlalchemy. Необходима для работы postgresql;
- flask-script - для удобного запуска приложения и различных скриптов;
Конечная структура будет такой:
Перед началом, убедимся что все нужные пакеты у нас в системе установлены. Нам нужен собственно сам python и pip:
Кстати, так же не слушайте тех, кто говорит что не стоит использовать python3. Возьмите обогреватель и попробуйте разморозить их. Все нужное давно есть и нет смысла не использовать python3 для новых проектов. Разве что нет какой-то редкой, специфичной библиотекии написать которую самостоятельно не представляется возможным. До сих пор я не встретил ни одной такой библиотеки хотя писал самые разные приложения.
Создаем приложение и его виртуальное окружение
Войдём под пользователем flask-uwsgi которого ранее создали:
Обратите внимание, что мы сразу окажемся в директории /srv/flask-uwsgi так как задали её в качестве домашней директории для пользователя.
Создадим виртуальное окружение для нашего flask приложения:
Активируем виртуальное окружение и установим в нём Flask:
Теперь создадим файл app.py в котором будет наше наипростейшее приложение:
Обратите внимание, что мы не указываем каких либо параметров в вызове метода run() : запущенный веб-сервер будет "слушать" на стандарном порту и локальном хосте ( 127.0.0.1:5000 ).
Так же нам необходимо создать директорию для статики - несмотря на то, что мы не используем никакой статики в нашем простом приложении, в конфигурации nginx мы её указываем, и если директорию не создать, то nginx не запустится:
И выходим из под пользователя:
Шаблоны
Как я уже выше писал, шаблоны приложения будут находится в app/templates/ . В этой директории надо расположить базовый шаблон, шаблоны страниц с ошибками и какие либо служебные части. Шаблоны отдельных модулей надо помещать в директорию с именем соответствующим имени модуля.
Примеры самих шаблонов я приводить не буду, они как правило слишком объёмные.
Зависимости
Все зависимости приложения необходимо обязательно сохранять. Это необходимо для того, чтобы не забыть какие библиотеки вы использовали при разработке приложения, и это так же знали другие разработчики которые работают над этим приложением. Возможно сейчас вы думаете, что никогда в будущем не будете ничего делать с этим приложением, или вы всегда будете делать его один, но спустя несколько лет успешной работы приложения, вам может понадобиться добавить совсем чуть-чуть новых возможностей и даже может быть будете делать его уже не в одиночку. Поверьте, уже спустя месяц вы не будете помнить какие именно библиотеки вы устанавливали и использовали, и будете неимоверно рады что сохранили все зависимости.
Зависимости мы будем хранить в нескольких различных файлах: по одному для каждого используемого окружения, и один, в котором содержаться общие для всех окружений. Под окружением подразумевается среда, в которой это приложение запущено. В рамках этой статьи мы будем использовать два:
- development - зависимости, необходимые для разработки;
- production - зависимости, необходимые только в production окружении;
При желании вы легко сможете добавить сколько угодно необходимых. Сами же файлы с версиями будем хранить в отдельной директории - requipments . Создадим её:
Теперь создадим базовый файл, в котором будут содержаться зависимости общие для всех окружений:
Этой командой мы добавим в файл base.txt список всех установленных в данный момент библиотек с их зависимостями. В данный момент мы уже установили в наше виртуальное окружение собственно сам flask , Flask-Sqlalchemy , flask-wtf и psycopg2 . Содержимое файла будет таким:
Для того, чтобы переиспользовать содержимое base.txt в файлах других окружений, в начало каждого из них необходимо добавить:
А затем остальные зависимости построчно.
Например, во время разработки в development окружении, возможно вы захотите использовать flask-debugtoolbar . В этом случае файл requipments/development.txt может быть таким:
В production окружении будет хорошей идеей использовать gunicorn для запуска самого приложения, и содержимое файла requipments/production.txt может быть таким:
Так же не лишнем будет указать версии используемых библиотек аналогично тому, как они указаны в файле requipments/base.txt .
Фабрика приложения
В приложении уже используются пакеты и эскизы (blueprints). Его можно улучшать и дальше, передав функцию создания экземпляров приложения Фабрике приложения. Это всего лишь функция, создающая объект.
- Упростит процесс тестирования, потому что можно будет создать экземпляр приложения с разными настройками
- Можно будет запускать несколько экземпляров одного приложения в одном и том же процессе. Это удобно, когда используется балансировка нагрузки трафика между разными серверами.
Для внедрения фабрики приложения нужно обновить app/__init__.py :
Теперь за создание экземпляров приложения ответственна функция create_app . Она принимает один аргумент config и возвращает экземпляр приложения.
Фабрика приложений разделяет процесс создания экземпляров расширений и их настройки. Создание экземпляров происходит до того, как create_app() вызывается, а настройка происходит внутри функции create_app() с помощью метода init_app() .
Дальше нужно обновить runner.py для фабрики приложения:
Стоит заметить, что при использовании фабрик приложения пропадает доступ к экземпляру приложения в эскизе во время импорта. Для получения доступа к экземплярам в эскизе нужно использовать прокси current_app из пакета flask . Необходимо обновить проект для использования переменной current_app :
В этих уроках речь шла о многих вещах, которые дают необходимые знания о Flask, его составляющих и том, как они сочетаются между собой.
Настройка nginx
Создадим файл конфигурации nginx. В большинстве дистрибутивов принятно, что файлы с конфигурацией виртуальных хостов помещаются в /etc/nginx/site-available , и создаются символические ссылки на них в директории /etc/nginx/site-enabled . Это позволяет отключать виртуальные хосты не удаляя их конфигурацию. Мы поступим так же (в других ваших проектах тоже стоит так делать, если вы до этого делали как-то иначе).
Создадим файл /etc/nginx/sites-available/flask-uwsgi.conf :
С таким содержимым:
Nginx так же будет раздавать статические файлы вашего веб-приложения. Пожалуй, лучше чем nginx с этим не справится ни один другой веб-сервер. :)
Вся коммуникация между nginx и uWSGI будет происходит по протоколу uWSGI через unix-сокет - это наиболее производительный способ коммуникации приложений в Linux.
Активируем наш виртуальный хост:
Проверим, что наш конфигурационный файл в порядке:
Результат выполнения команды должен быть примерно такой:
Если всё в порядке, то скажем nginx перечитать конфигурацию:
Создание route (путей)
Маршрут (или путь) используется во фреймворке Flask для привязки URL к функции представления. Эта функция отвечает на запрос. Во Flask декоратор route используется, чтобы связать URL адрес с функций. Вот как маршрут создается.
Этот код назначает функцию index() обработчиком корневого URL в приложении. Другими словами, каждый раз, когда приложение будет получать запрос, где путь — / , вызывается функция index() , и на этом запрос завершается.
Как вариант можно использовать метод add_url_rule() вместо декоратора route для маршрутизации. add_url_rule() — это простой метод, а не декоратор. Помимо URL он принимает конечную точку и название функции представления. Конечная точка относится к уникальному имени маршрута. Обычно, название функции представления — это и есть конечная точка. Flask может генерировать URL из конечной точки, но об этом позже. Предыдущий код аналогичен следующему:
Декоратор route используется в большинстве случаев, но у add_url_rule() есть свои преимущества.
Функция представления должна вернуть строку. Если пытаться вернуть что-то другое, сервер ответит ошибкой 500 Internal Sever Error.
Можно создать столько столько, сколько нужно приложению. Например, в следующем списке 3 пути.
Когда URL в маршруте заканчивается завершающим слешем ( / ), Flask перенаправляет запрос без слеша на URL со слешем. Так, запрос к /career будет перенаправлен на /career/ .
Для одной функции представления может быть использовано несколько URL. Например:
В этом случае в ответ на запросы /contact/ или /feedback/ , будет вызвана функция feedback() .
Если перейти по адресу, для которого нет соответствующей функции представления, появится ошибка 404 Not Found.
Эти маршруты статичны. Большая часть современных приложений имеют динамичные URL. Динамичный URL – это адрес, который состоит из одной или нескольких изменяемых частей, влияющих на вывод страницы. Например, при создании веб-приложения со страницами профилей, у каждого пользователя будет уникальный id. Профиль первого пользователя будет на странице /user/1 , второго — на /user/2 и так далее. Очень неудобный способ добиться такого результата — создавать маршруты для каждого пользователя отдельно.
Вместе этого можно отметить динамические части URL как (переменные). Эти части потом будут передавать ключевые слова функции отображения. Следующий код демонстрирует путь с динамическим элементом.
В этом примере на месте будет указываться часть URI, которая идет после /user/ . Например, если зайти на /user/100/ , ответ будет следующим.
В этом списке все конвертеры, доступные во Flask:
Конвертер | Описание |
---|---|
string | принимает любые строки (значение по умолчанию). |
int | принимает целые числа. |
float | принимает числа с плавающей точкой. |
path | принимает полный путь включая слеши и завершающий слеш. |
uuid | принимает строки uuid (символьные id). |
Blueprints (чертежы/схемы/планы/эскизы)
Эскизы — еще один способ организации приложений. Они предполагают разделение на уровне представления. Как и приложение Flask, эскиз может иметь собственные функции представления, шаблоны и статические файлы. Для них даже можно выбрать собственные URI. Предположим, ведется работа над блогом и административной панелью. Чертеж для блога будет включать функцию представления, шаблоны и статические файлы, необходимые только блогу. В то же время эскиз административной панели будет содержать файлы, которые нужны ему. Их можно использовать в виде модулей или пакетов.
Пришло время добавить эскиз к проекту.
Режим отладки (Debug)
Баги неизбежны в программировании. Поэтому так важно знать, как находить ошибки в программе и исправлять их. Во Flask есть мощный интерактивный отладчик, который по умолчанию отключен. Когда он выключен, а в программе обнаруживается ошибка, функция показывает 500 Internal Sever Error. Чтобы увидеть это поведение наглядно, можно специально добавить баг в файл main.py .
Тем не менее сам браузер не сообщает о типе ошибки. Если посмотреть в консоль, то можно увидеть отчет об ошибке. В данном случае он выглядит вот так:
Когда режим отладки выключен, после изменения кода нужно каждый раз вручную запускать сервер, чтобы увидеть изменения. Но режим отладки будет перезапускать его после любых изменений в коде.
Чтобы включить режим, нужно передать аргумент debug=True методу run() :
Еще один способ — указать значение True для атрибута debug .
После обновления файл main.py следующим образом его можно запускать.
Теперь, когда отладчик включен, вместо ошибки 500 Internal Server на странице будет отображаться отчет об ошибке. Он в полной мере описывает, какая ошибка случилась. Внизу страницы видно, что оператор print пытался вывести значение неопределенной переменной i . Там же указан тип ошибки, NameError , что подтверждает то, что ошибка заключается в том, что имя i не определено.
Кликнув на строчку кода на странице вывода ошибки, можно получить исходный код, где эта ошибка обнаружена, а также предыдущие и следующие строчки. Это помогает сразу понять контекст ошибки.
При наведении на строчку кода отображается иконка терминала. Нажав на нее, откроется консоль, где можно ввести любой код Python.
В ней можно проверить локальные переменные.
Если консоль открывается первый раз, то нужно ввести PIN-код.
Это мера безопасности, призванная ограничить доступ к консоли неавторизованным пользователям. Посмотреть код можно в консоли при запуске сервера. Он будет указан в начале вывода.
Завершить урок стоит созданием еще одного приложения Flask с применением всех имеющихся знаний.
Создаем еще один файл main2.py со следующим кодом:
Стоит заметить, что путь /user// будет работать только с теми URL, где динамическая часть ( user_id ) представлена числом.
Файл настроек
Перед тем как мы неподсредственно начнем писать модули приложения, необходимо создать файл с конфигурацией и скрипт для запуска приложения.
Настройки будем хранить в файле config.py . В нем аналогично тому, как мы описывали зависимости, опишем базовую конфигурацию для всех окружений и переопределим некоторые настройки под определенные окружения.
Сейчас файл выглядит пустоватым, но когда начнете активно разарабывать приложение, количество настроек может очень сильно увеличиться.
База данных
Содержимое файла app/database.py :
uWSGI
Создание эскиза
Сначала нужно создать папку main в папке flask_app/app и переместить туда views.py и forms.py . Внутри папки main необходимо создать файл __init__.py со следующим кодом:
Здесь создается объект эскиза с помощью класса Blueprint . Конструктор Blueprint() принимает два аргумента: имя эскиза и имя пакета, где он расположен; для большинства приложений достаточно будет передать __name__ .
По умолчанию функции представления в эскизе будут искать шаблоны и статические файлы в папках приложения templates и static .
Изменить это можно, задав местоположение шаблонов и статических файлов при создании объекта Blueprint :
В этом случае Flask будет искать шаблоны и статические файлы в папках templates_dir и static_dir , которые находятся в папке эскиза.
Путь шаблона, добавленный в эскизе, имеет более низкий приоритет по сравнению с папкой шаблонов приложения. Это значит, что если есть два шаблона с одинаковыми именами в папках templates_dir и templates , Flask использует шаблон из папки templates .
Вот некоторые вещи, которые важно помнить, когда речь заходит о эскизах:
- При использовании эскизов маршруты определяется с помощью декоратора route , а не экземпляра приложения ( app ).
- Для создания URL при использовании эскизов нужно в качестве префикса указать название эскиза, а через оператор точки ( . ) — конечную точку. Это необходимо для создания URL и в Python, и в шаблонах. Например:
Этот код вернет URL маршрута index эскиза main.
Можно не указывать название эскиза, если работа ведется в том же эскизе, для которого создается URL. Например:
Этот код вернет URL маршрута index для эскиза main в том случае, если код редактируется в функции представления или шаблоне эскиза main .
Чтобы приспособить изменения, нужно обновить инструкции import , вызовы url_for() и маршруты во views.py . Это обновленная версия файла views.py .
Стоит обратить внимание, что в файле views.py URL создаются без определения названия эскиза, потому что работа ведется в этом же эскизе.
Также нужно следующим образом обновить вызов url_for() в admin.html :
Функции представления во views.py теперь ассоциируются с эскизом main . Дальше нужно зарегистрировать эскизы в приложении Flask. Необходимо открыть app/__init__.py и изменить его следующим образом:
Метод register_blueprint() экземпляра приложения используется для регистрации эскиза. Можно зарегистрировать несколько эскизов, вызвав register_bluebrint() для каждого. Важно обратить внимание, что на 11 строке login_manager.login_view присваивается main.login . В этом случае важно указать, о каком эскизе идет речь, иначе Flask не сможет понять, на какой эскиз ссылается код.
Сейчас структура приложения выглядит так:
Порядок выполнения
В этом уроке было создано немало файлов, и достаточно легко запутаться в том, за что отвечает каждый из них, а также в порядке, в котором они запускаются. Этот раздел создан для разъяснения всего процесса.
Все начинается с исполнения файла runner.py . Вторая строка в файле runner.py импортирует app и db из пакета app . Когда интерпретатор Python доходит до этой строки, управление программой передается файлу __init__.py , который в этот момент начинает исполняться. На 7 строке __init__.py импортирует модуль config , который передает управление config.py . Когда исполнение config.py завершается, управление снова возвращается к __init__.py . На 21 строке __init__.py импортирует модуль views , который передает управление views.py . Первая строка views.py снова импортирует экземпляр приложения app из пакета app . Экземпляр приложения app уже в памяти, поэтому снова он не будет импортирован. На строках 4, 5 и 6 views.py импортирует модели, формы и функцию send_mail и временно передает управление соответствующим файлам. Когда исполнение views.py завершается, управление программой возвращается к __init__.py . Это завершает исполнение __init__.py . Управление возвращается к runner.py и начинается исполнения инструкции на строке 3.
Третья строка runner.py импортирует классы, определенные в модуле models.py . Поскольку модели уже доступны в файле views.py , файл models.py не будет исполняться.
Определяем переменные окружения
На предыдущих шагах я часто упоминал про окружения. Настройки какого окружения будут использоваться, будет определяться с помощью переменных окружения (без тафтологии никак, да).
В терминале пишем:
Это хорошая практика не хранить нигде логин/пароль в файлах конфигурации. В случае взлома приложения, взломщику будет намного тяжелее получить пароль базы данных. На production сервере в переменной окружения APP_SETTINGS конечно же будет необходимо указать config.ProductionConfig .
Настройки на основе классов
Проект по созданию ПО обычно работает в трех разных средах:
- Разработка
- Тестирование
- Рабочий режим
При развитии проекта понадобится определить разные параметры для разных сред. Некоторые также будут оставаться неизменными вне зависимости от среды. Внедрить такую систему конфигурации можно с помощью классов.
Начать стоит с определения настроек по умолчанию в базовом классе и только потом — создавать классы для отдельных сред, которые будут наследовать параметры из базового. Специальные классы могут перезаписывать или дополнять настройки, необходимые для конкретной среды.
Создадим файл config.py внутри папки flask_app и добавим следующий код:
Стоит обратить внимание, что в этом коде значения некоторых настроек впервые берутся у переменных среды. Также здесь есть некоторые значения по умолчанию, если таковые для сред не будут указаны. Этот метод особенно полезен, когда имеется конфиденциальная информацию, и ее не хочется вписывать напрямую в приложение.
Считывать настройки из класса будет метод from_object() :
uWSGI из репозитория дистрибутива
Установим uWSGI из репозитория. Для этого под обычным пользователем выполним:
Мейнтейнеры ubuntu собрали и настроили uWSGI аналогично nginx: конфигурации приложений хранятся в директории /etc/uwsgi/apps-available . Для активации конфигурации необходимо создать символическую ссылку в директории /etc/uwsgi/apps-enabled .
Создадим файл конфигурации нашего приложения. Создадаём файл /etc/uwsgi/apps-available/flask-uwsgi.ini с таким содержимым:
Часть настроек представленные в файле, уже определены в /etc/init.d/uwsgi . В частности gid , master и socket . Это означает, что их можно не добавлять в файл, но лично я предпочитаю видеть сразу все настройки, чем держать в голове что они ещё где-то определены.
Далее создадим символическую ссылку на файл конфигурации который мы создали таким образом активируя конфигурацию.
И перезапускаем uWSGI:
После этого проверьте работоспособность приложения. Если всё в порядке, то включаем автоматическую загрузку uWSGI вместе с загрузкой системы:
Основной "скелет" приложения
Сначала создадим наше виртуальное окружение. Создадим директорию, где будет располагаться наш проект. Здесь, и дальше во всей статье я буду предполагать что это директория testproject :
Создадим виртуальное окружение c помощью virtualenv :
Создадим директорию для кода нашего будущего проекта:
И заодно директорию для шаблонов, и статических файлов:
Активируем виртуальное окружение:
Важно: здесь и далее считаем, что все команды выполняются внутри виртуального окружения!
Установим нужные нам пакеты с помощью pip:
Основной "скелет" приложения готов.
Этот пункт строго обязателен. Разработку любого приложения всегда следует вести с использованием системы контроля версий.
В корневой директории нашего проекта (напомню, это testproject ) создадим файл .gitignore .
Он необходим для того, чтобы лишние файлы, такие как кэши не попали в git репозиторий. Добавим в него следующее:
И сделаем первый коммит:
Теперь необходимо указать url git репозитория. Для этого его необходимо сначала создать. Сейчас очень много различных компаний предлагают бесплатный хостинг git репозиториев. Из них можно выделить всем известные github и bitbucket. Лично я для своих проектов предпочитаю последний, т.к. он позволяет создавать скрытые репозитории бесплатно. После создания репозитория на любом из этих сервисов, будет показана подсказка, в которой будет описано как добавить url репозитория в существующий проект. Сейчас я на этом подробно останавливаться не буду.
Запуск проекта
Теперь можно запускать проект. В терминале для запуска сервера нужно ввести следующую команду.
Также необходимо проверить остальные страницы приложения, чтобы убедиться, что все работает.
Приложение теперь является гибким. Оно может получить совсем иной набор настроек с помощью всего лишь одной переменной среды. Предположим, нужно перевести приложение в рабочий режим. Для нужно всего лишь создать переменную среды FLASK_ENV со значением config.ProductionConfig .
В терминале нужно вести следующую команду для создания переменной среды FLASK_ENV :
Эта команда создает переменную среды в Linux и macOS. Пользователи Windows могут использовать следующую команду:
Снова запустим приложение.
Теперь приложение работает в рабочем режиме. Если сейчас Python вызовет исключения, то вместо трассировки стека отобразится ошибка 500.
Поскольку сейчас все еще этап разработки, переменную среды FLASK_ENV следует удалить. Она будет удалена автоматически при закрытии терминала. Чтобы сделать это вручную, нужно ввести следующую команду:
Пользователи Windows могут использовать следующую команду:
Проект теперь в лучшей форме. Его элементы организованы более логично. Использованный здесь подход подойдет для маленьких и средних по масштабу проектов. Тем не менее у Flask есть еще несколько козырей для тех, кто хочет быть еще продуктивнее.
Создание приложения Flask
У каждого Flask-приложения должен быть экземпляр класса. Экземпляр — это WSGI-приложение (WSGI – это интерфейс для взаимодействия сервера с фреймворком), которое показывает, что сервер передает все полученные запросы экземпляру для дальнейшей обработки. Объект класса Flask создается следующим образом:
В первой строке класс Flask импортируется из пакета flask .
Во второй строке создается объект Flask . Для этого конструктору Flask назначается аргумент __name__ . Конструктор Flask должен иметь один обязательный аргумент. Им служит название пакета. В большинстве случаев значение __name__ подходит. Название пакета приложения используется фреймворком Flask, чтобы находить статические файлы, шаблоны и т. д.
Предисловие
Мы создадим простейшее web-приложение на Flask. Само приложение будет запускать uWSGI, запросы на который будет проксировать nginx. Приложение будет называться flask-uwsgi . Запомните это название, мы далее будем его часто использовать для имени пользователя и директорий.
Есть два основных способа запуска приложения под uWSGI: используя версию uWSGI из репозиториев дистрибутива, и второй - это установка uWSGI в виртуальное окружение. Лично я предпочитаю устанавливать uWSGI в виртуальное окружение - есть возможность контролировать версию uWSGI, хранить его конфигурацию в системе контроля версий да, и, как правило, в pip версии более "свежие". Первый вариант проще, и в целом подойдёт большинству людей. В этом посте я опишу оба варианта.
Запуск сервера
Для запуска сервера разработки нужно использовать метод run() объекта Flask .
Условие __name__ == "__main__" гарантирует, что метод run() будет вызван только в том случае, если main.py будет запущен, как основная программа. Если попытаться использовать метод run() при импорте main.py в другой модуль Python, он не вызовется.
Важно: сервер разработки Flask используется исключительно для тестирования, поэтому его производительность невысокая.
Теперь должно быть понятнее, как работает main.py .
uWSGI в виртуальном окружении
При установке uWSGI в виртуальном окружении, понадобятся утилиты для компиляции ряда его модулей. Установим пакеты которые содержат компилятор и нужные заголовочные файлы:
Снова войдём под пользователем которого мы создали для запуска приложения:
Установим uWSGI в виртуальном окружении:
Создадим файл uwsgi.ini , но уже в директории с проектом:
Выходим из под пользователя flask-uwsgi :
Под обычным пользователем создадим файл /etc/systemd/system/flask-uwsgi.service с таким содержимым:
Таким образом мы создадим сервис systemd которым можно управлять - запускать, останавливать, перезапускать и смотреть его статус. Так же он автоматически будет запущен вместе с системой при загрузке. Запустим сервис:
Проверим, что всё запустилось:
И если всё в порядке, то разрешаем сервису запускаться вместе с системой при загрузке:
Как Flask обрабатывает запрос?
Откуда Flask знает, какую функцию выводить, когда он получает запрос от клиента?
Flask сопоставляет URL и функции отображения, которые будут выводиться. Определение соответствий (маршрутизация) создается с помощью декоратора route или метода add_url_rule() в экземпляре Flask. Получить доступ к этим соответствиям можно с помощью атрибута url_map у экземпляра Flask .
Как видно, есть 4 правила. Flask определяет соответствия URL в следующем формате:
Путь /static/ автоматически добавляется для статических файлов Flask. О работе со статическими файлами речь пойдет в отдельном уроке «Обслуживание статических файлов во Flask».
До этого момента все приложение хранилось в одном файле main2.py . Это нормально для маленьких программ, но когда масштабы растут, ими становится сложно управлять. Если разбить крупный файл на несколько, код в каждом из них становится читабельнее и предсказуемее.
У Flask нет никаких ограничений в плане структурирования приложений. Тем не менее существуют советы (гайдлайны) о том, как делать их модульными.
Мы будем использовать следующую структуру приложения.
Ниже описание каждого файла и папки:
Файл | Описание |
---|---|
app_dir | Корневая папка проекта |
app | Пакет Python с файлами представления, шаблонами и статическими файлами |
__init__.py | Этот файл сообщает Python, что папка app — пакет Python |
static | Папка со статичными файлами проекта |
templates | Папка с шаблонами |
views.py | Маршруты и функции представления |
config.py | Настройки приложения |
runner.py | Точка входа приложения |
Оставшаяся часть урока будет посвящена преобразованию проекта к такой структуре. Начнем с создания config.py .
Создание пакета приложения
В папке flask_app нужно создать новую папку под названием app и переместить все файлы и папки в нее (за исключением env и migrations , а также созданного только что файла config.py ). Внутри папки app нужно создать файл __init__.py со следующим кодом:
__init__.py создает экземпляр приложения и запускает расширения. Если переменная среды FLASK_ENV не задана, приложение запустится в режиме отладки (то есть, app.debug = True ). Чтобы перевести приложение в рабочий режим, нужно установить для переменной среды FLASK_ENV значение config.ProductionConfig .
После запуска расширений инструкция import на 21 строке импортирует все функции представления. Это нужно, что подключить экземпляр приложение к этим функциям, иначе Flask не будет о них знать.
Необходимо переименовать файл main2.py на views.py и обновить его так, чтобы он содержал только маршруты и функции представления. Это полный код обновленного файла views.py .
views.py содержит не только функции представления. Сюда перемещен код моделей, классов форм и другие функции для соответствующих файлов:
Наконец, для запуска приложения нужно добавить следующий код в файл runner.py :
runner.py — это точка входа проекта. В первую очередь файл создает экземпляр объекта Manager() . Затем он определяет функцию make_shell_context() . Объекты, которые вернет make_shell_context() , будут доступны в оболочке без импорта соответствующих инструкций. Наконец, метод run() экземпляра Manager будет вызван для запуска сервера.
Запуск приложения
В примерах кода выше было очень много допущений, поэтому если вы просто копировали их, то ничего не заработает. :) Сделано это специально, т.к. в статье я рассказывал не о том как писать приложения на flask, а как правильно организовывать их структуру. Полностью рабочий пример можете найти в репозитории на github.
Запуск приложения будет выгядеть следующим образом:
Главное не забудьте про переменные окружения, иначе ничего тем более не заработает. :)
Начать знакомство с Flask можно с создания простого приложения, которое выводит “Hello World”. Создаем новый файл main.py и вводим следующий код.
Это приложение “Hello World”, созданное с помощью фреймворка Flask. Если код в main.py не понятен, это нормально. В следующих разделах все будет разбираться подробно. Чтобы запустить main.py , нужно ввести следующую команду в виртуальную среду Python.
Остановить сервер можно с помощью комбинации CTRL+C.
Подготовка
Для начала установим пакеты, которые нам понадобятся вне зависимости, от выбранного способа. В Ubuntu:
Создадим директорию для приложения. Эта же директория будет "домашней" для пользователя которого мы далее создадим:
Запускать любое веб-приложение лучше под отдельным пользователем. Создадим его:
Обратите внимание, что мы установили командную оболочку пользователя в /bin/false - то есть по сути отключили ему возможность как либо войти в систему. Сделано это опять же из соображений безопасности. Используя su и имея права супер-пользователя в системе мы сможем войти под этим пользователем и настроить всё для запуска приложения.
Дадим права на директорию /srv/flask-uwsgi созданному пользователю:
Командами выше мы:
- владельцем директории назначили созданного ранее пользователя. Этот пользователь имеет все права на директорию (читать/создавать/удалять);
- группой-владельцем директории назначили www-data - это группа создается при установке nginx, и с правами этой группы он запускается. Таким образом nginx сможет получить доступ к директории и корректно отдавать статику;
- группе www-data и всем остальным пользователям дали возможность исключительно читать файлы из директории;
Инициализация приложения
Как я уже выше писал, инициализация приложения будет происходить в функции-фабрике. Под инициализацией подразумевается создание экземпляра flask приложения со всеми необходимыми настройками. Использовать фабрику строго обязательно. Без этого, в какой-то момент "по запарке" можем выстрелить себе в ногу все теми же "круговыми импортами". Да и тесты иначе не получится нормально писать, а без тестов вообще нельзя. Совсем. Никогда. :)
Наша фабрика будет находится в файле app/__init__.py . Вот его содержимое:
Обратите внимание, что инициализация расширения flask-sqlaclhemy происходит внутри тела функции, а не просто в файле. Тоже самое касается и импортов blueprint модулей.
Файл запуска приложения
Для запуска приложения мы будем использовать удобное расширение - flask-scripts . Весь необходимый код поместим в файл manage.py в корень проекта. Название не случайно. Кроме того, что оно "говорящее" ещё и довольно привычное многим python-разработчикам.
Само flask приложение мы будем создавать используя "фабрику" - функцию, создающую экземпляр приложения и возвращающую его. Её мы опишем дальше в этой статье.
Модули (blueprints)
Теперь займемся непосредственно модулями нашего приложения.
Создадим директорию для шаблонов модуля:
И директорию для самого модуля:
Да, шаблоны используемые модулем лучше хранить в отдельной под-директории для лучшей его (модуля) переносимости.
Вот так выглядит структура модуля:
- controllers.py - предназначен для контроллеров;
- models.py - предназначен для sqlalchemy моделей;
- forms.py - предназначен для форм;
Теперь более подробно примерное содержимое каждого из файлов.
Читайте также: