Мега-Учебник Flask, Часть 11: Поддержка e-mail

Это одиннадцатая статья в серии, где я описываю свой опыт написания веб-приложения на Python с использованием микрофреймворка Flask.

Цель данного руководства — разработать довольно функциональное приложение-микроблог, которое я за полным отсутствием оригинальности решил назватьmicroblog.

Краткое повторение


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

Сегодня мы позволим нашей базе немного отдохнуть, и вместо этого посмотрим на одну очень важную функцию, которая есть у большинства веб-приложений: возможность отправки email пользователю.

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

Введение в Flask-Mail


К счастью для нас, Flask уже имеет расширение обрабатывающее электронную почту, и хоть оно не выполняет 100% задач, оно очень близко к этому.

Установка Flask-Mail в наше виртуальное окружение довольно простая. Пользователи на отличных от Windows системах должны сделать:

Для пользователей Windows всё немножко сложней, потому что одна из зависимостей Flask-Mail не работает в этой OS. На Windows вам нужно сделать следующее:

 

Конфигурация


Ранее, когда мы добавляли Unit-тестирование мы добавили конфигурацию для Flask в которой указали email на который должны отсылаться уведомления об ошибках в production-версии нашего приложения. Та же информация используется для отправки почты приложением.

Нужно запомнить что нам нужно следующая информация:

  • сервер через который отправляются email
  • электронный адрес администратора


Это то, что мы сделали в предыдущей статье (файл config.py):

 

Разумеется вам придется ввести фактические данные в этот конфиг, для того, чтобы приложение действительно смогло отправлять вам электронные письма. Например, если вы хотите использовать приложение для отправки писем через gmail.com, нужно указать следующее:

Мы также должны инициализировать объект Mail, т.к. это будет объект, который будет соединяться с SMTP сервером и отправлять электронные письма для нас(файлapp/__init__.py):

Давайте отправим email.

Чтобы понять как работает Flask-Mail работает, нам нужно отправить email из командной строки. Давайте запустим Python из нашего виртуального окружения и наберем следующее:

Фрагмент кода выше отправит письмо списку администраторов, указанных вconfig.py. Отправителем будет указан первый администратор из списка. Писмо будет иметь текстовую и HTML версии, что вы увидите зависит от настроек вашего почтового клиента. Обратите внимание, нам нужно создать app_context, чтобы отправить email. Последние релизы Flask-Mail это требуют. Контекст создается автоматически, когда запрос обрабатывается Flask.

Поскольку мы не находимся внутри запроса, мы можем создать контекст руками.

Теперь пришло время интегрировать этот код в наше приложение.

Простой email фреймворк


Сейчас мы напишем впомогательную функцию, которая отправляет email. Это просто более общая версия вышеуказанного теста. Мы поместим эту функцию в новый файл, который выделим для наших функций связанных с email (файлapp/emails.py):

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

Уведомления о подписках


Теперь, когда у нас есть базовый фреймворк для отправки электронной почты, мы можем написать функцию уведомляющую о подписчиках (файл app/emails.py):

Увидели что-то неожиданное?
Наш старый друг — функция render_template создает вид письма. Если вы помните, мы использовали эту функцию чтобы рендерить все HTML шаблоны из нашего представления. Так же как наши HTML, тело письма идеальный кандидат на использование шаблонов. Мы хотим, насколько это возможно, отделить логику от представления, поэтому письма будут идти в папке с шаблонами вместе с другими view.

Итак, сейчас мы напишем шаблоны для текстовой и HTML версий для нашего уведомления. Это текстовая версия (файл app/templates/follower_email.txt):

Для HTML версии мы можем сделать всё немножко красивей и показывать аватар подписчика и информацию из профиля (файлapp/templates/follower_email.html):

Обратите внимание на _external = True в поле url_for нашего шаблона. По умолчанию, функция url_for генерирует URL’ы относительно текущей страницы. Для примера, code{url_for(«index»)} будет /index, в то время, когда мы ожидаемhttp://localhost:5000/index. В электронной почте нет доменного контекста, поэтому мы должны указывать полные адреса URL, которые включают домен, в этом нам и поможет _external.

Финальным шагом станет подключение отправки электронного письма с функцией представления, которая обрабатывает "Follow" (файл app/views.py):

Сейчас вы должны создать двух пользователей (если вы еще не сделали этого) и сделать одного подписчиком другого, чтобы увидеть как работает уведомление по электронной почте. Это то что нужно? Мы закончили?

Теперь мы можем погладить себя по голове за прекрасно выполненную работу и вычеркнуть уведомления по электронной почте из списка функций, которые нам предстоит реализовать.

Но если вы поиграли с нашим приложением, вы заметили, что теперь, когда мы реализовали уведомления по электронной почте, после нажатия на «Follow» проходит несколько секунд, прежде чем браузер обновит страницу. А раньше это происходило мгновенно. 

Итак, что произошло?

Проблема в том, что Flask-Mail отправляет электронные письма синхронно. Сервер блокируется, пока писмо отправляется и отправляет ответ браузеру, только когда сообщение будет доставлено. Вы можете представить что случится если мы попробуем отправить электронное письмо медленному серверу, или, что еще хуже, выключенному? Это плохо.

Это очень страшное ограничение, отправка электронного письма должна быть фоновой задачей, которая не мешает серверу, поэтому давайте посмотрим как мы можем всё это исправить.

Асинхронные вызовы в Python


Мы хотим чтобы функция send_email завершалась мгновенно, пока работа по отправке письма идет в фоне. 

Оказывается Python уже поддерживает запуск асинхронных задач, даже более чем одним способом. Модули threading и multiprocessing могут нам помочь.

Запуск нового потока, каждый раз, когда нам нужно отправить письмо, гораздо менее ресурсоемкая операция чем запуск нового процесса, поэтому давайте переместим вызов mail.send(msg) в поток(файл app/emails.py):

Если вы тестируете функцию «Follow» вы обратите внимание что браузер показывает обновленную страницу прежде чем писмо будет отправлено. 

Итак, сейчас мы реализовали асинхронную отправку почты, но что если в будущем нам понадобится реализовать другие асинхронные функции? Процедура останется той же, но нам нужно будет дублировать код работы с потоками в каждом случае, что не есть хорошо.

Мы можем реализовать наше решение в виде декоратора. С декоратором код выше изменится на этот:

Гораздо лучше, не правда ли?

Код который делает эту магию, на самом деле очень простой. Мы запишем его в новый файл (файл app/decorators.py):

Сейчас когда мы случайно создали хорошую основу для асинхронных задач мы можем сказать что все сделано!

Ради упражнения давайте рассмотрим как изменилось бы наше решение, если бы мы использовали процессы вместо потоков.
Мы не хотим чтобы новый процесс стартовал каждый раз, когда мы отправляем письмо, вместо этого мы можем использовать класс Pool из модуля multiprocessing. Этот класс создает необходимое количество процессов (которые являются форками основного процесса) и все процессы ждут задачи, которые передаются через метод apply_async. Это может быть полезным и интересным для загруженых сайтов, но сейчас мы остановимся на потоках.

Заключительные слова

Я получил несколько просьб разместить это приложение на GitHub или похожем сайте, я думаю это очень хорошая идея. Я буду работать над этим в ближайшем будущем. Оставайтесь на связи.

Спасибо за то что следите за серией моих туториалов. Надеюсь увидеть вас в следующих частях.

 

 

Leave a Reply

Добавить комментарий