Пользователь
0,0
рейтинг
4 сентября 2014 в 15:17

Разработка → Вместе веселей: python + flask + google app engine tutorial

Предыстория: я, как и многие на Хабрахабре, чертовски люблю слушать музыку. Чаще всего делаю это в ВКонтакте. Иногда уже сфомированный плейлист надоедает — хочется чего-нибудь нового; но так, чтобы не сильно отличалось от того, что уже есть. Для этого на всех сервисах, предоставляющих возможность прослушивания музыки, существуют рекомендации. Признаюсь честно, рекомендации в ВКонтакте меня ужасают. Может кому-то нравится, но у меня тамошний подбор вызывает желание закрыть браузер (ИМХО, конечно). В общем, решил я эту ситуацию для себя исправить и реализовать свои собственные рекомендации с использованием API ВКонтакте и Last.fm. Так как я много слышал и читал про Google App Engine, но никогда его не использовал, решено было приобщиться к этой платформе.

Сразу скажу, что тонкости взаимодействия с API или алгоритм подбора рекомендаций здесь я описывать не буду. Об этом — в следующих статьях, может быть. В данной статье описан только процесс создания, настройки и деплоя приложения на платформе Google App Engine с использованием python и flask.
Об опечатках и неточностях просьба сообщать в личку. Спасибо.

Итак, начнем. Идем на appengine.google.com. Там, если вы еще не авторизованы, у вас попросят ввести логин/пароль для своего аккаунта в Google.

После успешной авторизации видим кнопку “Create Application” (или список приложений, если они у вас есть). Насколько мне известно, без дополнительных капиталовложений Google дает возможность создать до 25-ти приложений (поправьте, если я не прав). Со списком других квот можно ознакомиться, перейдя по ссылке.

После нажатия кнопки нас перекидывает на форму, где можно выбрать идентификатор приложения (еще не занятый) и название, а также опции аутентификации. Соглашаемся с Terms of Service и жмем кнопку для создания первого приложения. Создали, радость-то какая. Теперь ваше приложение доступно по адресу http://[identifier].appspot.com, где identifier — выбранный вами уникальный идентификактор приложения. Приложение доступно, но, естественно, не работает — нечему работать. Исправим ситуацию.

Качаем PyCharm, если надо. Есть небольшой нюанс выбора версии редактора: PyCharm, который Professional Edition, имеет встроенную поддержку Google App Engine, что выражается в интегрированных в IDE инструментах деплоя. PyCharm CE такой поддержки не имеет, так что придется деплоить через консоль.

Создаем проект в PyCharm. Если вы используете Professional Edition, то при создании проекта можно выбрать тип “Google App Engine project”. В этом случае придется указать идентификатор вашего приложения (вышеупомянутый identifier), а также путь к App Engine SDK, так что позаботьтесь о его скачивании заранее. Получить SDK для любого поддерживаемого языка программирования можно здесь. PyCharm создаст проект с уже заполненным файлом конфигурации app.yaml и рабочим основным скриптом main.py.

Собственно, основной скрипт:
main.py
import webapp2

class MainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.write('Hello world!')

app = webapp2.WSGIApplication([
    ('/', MainHandler)
], debug=True)

Этот пример приводится и в самом руководстве по Google App Engine для Python, и даже на сайте JetBrains. В скрипте ничего примечательного нет — стандартный HelloWorld. Скажу только для тех, кто не знает, что webapp2 — это легковесный фреймворк, который совместим с Google App Engine и довольно прост в использовании (ну насколько я его пользовал).

Файл конфигурации:
app.yaml
application: pygask
version: 1
runtime: python27
api_version: 1
threadsafe: yes

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: .*
  script: main.app

libraries:
- name: webapp2
  version: "2.5.2"

Разберем конфиг:
  • application — наш идентификатор приложения (пусть ваш не смущает слово «pygask» — это вольное сокращение от python+gae+flask :) );
  • handlers — перечень паттернов URL и описаний как их обрабатывать. В директиве handlers можно использовать два типа обработчиков — обработчики статики и обработчики скриптов. Статику, в данном случае, представляет загрузчик фавикона, скрипты — основной скрипт для запуска приложения main.app (Внимание! Расширение .py указывать не надо);
  • libraries — библиотеки, необходимые приложению для работы; в данном случае, webapp2. Python 2.7, запускаемый на Google App Engine, поддерживает некоторый набор библиотек, работающий «из коробки». Полный список можно найти здесь.

Подробное описание конфигурации приложения Google App Engine лежит тут.

В общем-то, наше первое приложение готово к тому, чтобы проверить его работу. Запускаем проект в PyCharm; на локальной машине он будет доступен по адресу: 127.0.0.1:8080 (если вы ничего не меняли в конфигурации проекта). Если при открытии, видите «Hello world!» — всё работает.

Что делать, если вы не используете PyCharm, а запустить проект всё равно хочется?
Linux way: в терминале выполняем команду в следующем формате:
<path to python interpeter> <path to SDK>/dev_appserver.py --host 127.0.0.1 <path to project>
Для пояснения приведу команду, которую я использую, чтобы запустить pygask в консоли (при условии, что и SDK, и папка проекта находятся в /var/www/):
andymitrich@pc:~$ python /var/www/google_appengine/dev_appserver.py --host 127.0.0.1 /var/www/pygask/
Набираем что-то подобное в терминале и открываем 127.0.0.1:8080 и, надеюсь, видим «Hello world!».

Касательно пользователей Windows — там всё немного по-другому. SDK скачивается и устанавливается на компьютер и для запуска проекта используется Google App Engine Launcher. По поводу Mac OS ничего сказать не могу — возможно в комментариях кто-нибудь расскажет.

К сожалению, фреймворк webapp2 — это не то, что нам нужно. Поэтому, айда подключать flask.

Так как в списке работающих по-умолчанию библиотек, flask не значится, то нам, в первую очередь, надо позаботиться о его подключении. Google App Engine поддерживает возможность отдельного конфигурирования используемых модулей. Делается это в файле с именем appengine_config.py. Подробней о нем здесь. Будем использовать его для подключения фреймворка. Для этого с помощью pip установим flask во внутреннюю директорию, назовем её lib. Делаем это либо вручную
andymitrich@pc:~$ pip install -t /var/www/pygask/lib/ flask
Либо с помощью файла requirements.txt
andymitrich@pc:~$ pip install -r /var/www/pygask/requirements.txt -t /var/www/pygask/lib/
Обратите внимание на параметр -t — он содержит путь для установки пакета.

Flask установлен, можно использовать — перепишем немного конфиг и основной скрипт:
  1. Из конфига приложения app.yaml можно убрать участок с директивой libraries — она нам не нужна.
  2. Основной скрипт теперь выглядит следующим образом:
    main.py
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def home():
         return 'Hello world!'
    

Чтобы приложение подхватило установленный flask, укажем в файле appengine_config.py место его нахождения.
appengine_config.py
import site
import os.path

site.addsitedir(os.path.join(os.path.dirname(__file__), 'lib'))

Пробуем запустить — видим всё тот же «Hello world!».

Доведем проект до более-менее кошерного вида: добавим пакет application, папки для статических файлов и шаблонов, вынесем обработчики и представления в отдельные файлы; поступайте так, как будто собираете обычный проект на flask. После всех проведенных манипуляций дерево проекта выглядит следующим образом:

Дальше можно развивать его в какую угодно сторону.

Обращу ваше внимание на то, как изменились первоначальные файлы:
  • основной скрипт, который теперь состоит из одной строки
    main.py
    import application
    
  • файл конфигурации проекта
    app.yaml
    application: pygask
    version: 1
    runtime: python27
    api_version: 1
    threadsafe: yes
    
    handlers:
    - url: /favicon\.ico
      static_files: favicon.ico
      upload: favicon\.ico
    
    - url: .*
      script: main.application.app

В файле конфигурации изменился путь к основному скрипту — добавилось указание пакета application.

Допустим, выведенного на экран «Hello world!» нам достаточно на первый раз. Давайте деплоить на appspot.com. Собственно, здесь ничего сложного также нет. Но, перед тем, как начать, хочу сказать об одной важной весчи: в процессе разработки появляется много рабочих файлов, которые на сервере не нужны. Для того, чтобы они не попали туда при деплое, в конфиге приложения можно в директиве skip_files указать всё то, что не должно выкладываться. Подробнее.

Если вы счастливый пользователь PyCharm, то потрудитесь проделать «сложнейшую» операцию: Tools -> Google App Engine -> Upload App Engine app… Далее, если это первый деплой, выберите подходящий вам способ авторизации (я с парой email/password не разобрался — почему-то данные не подошли, поэтому использовал OAuth2 и не заморачивался) и, вуаля, можно идти на http://[identifier].appspot.com (в данном случае, pygask.appspot.com) и смотреть на результат своих трудов.

Если вы по каким-то причинам не используете вышеупомянутую IDE, для вас рецепт в одну строку (Linux way):
<path to python interpeter> <path to SDK>/appcfg.py <path to project>
Соответственно, я использую:
andymitrich@pc:~$ python /var/www/google_appengine/appcfg.py --oauth2 /var/www/pygask/

Обратите внимание на параметр --oauth2, он нужен для авторизации через OAuth2. Не укажете его — попросят ввести email/password. В случае успешного завершения исполнения команды, ваш проект будет доступен по соответствующему адресу.

На этом всё, надеюсь, что материал окажется кому-нибудь полезен. Если будет интересно, в дальнейшем расскажу как в рамках данной идеи я развлекался с API Вконтакте и Last.fm и к чему всё это привело. Спасибо за внимание.

P.S. Код лежит на гитхабе: pygask.
@andymitrich
карма
42,7
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (18)

  • +1
    Хочется побольше историй про опыт использования GAE и выводы.
    • +6
      Думаю, лучше посмотреть в сторону чего-то вроде Azure. При работе с GAE выбор языков, фреймворков, БД сокращается до неприличия. Но как зарядка для мозгов сервис весьма интересен. Написать приложение, которое держало бы хоть какую-то нагрузку, и при этом не выбивалось бы за пределы бесплатных лимитов — не самая тривиальная задача.
      Я как-то использовал GAE для создания RSS-ленты обновлений «Самиздата». Первоначальная версия регулярно сканировала странички выбранных авторов и собирало обновления в RSS.
      Эта версия проработала менее суток, после чего улетела в бан на сутки в связи с превышением лимитов на процессорное время. К счастью, GAE обладает отличной системой логгирования, т.ч. разобраться в проблеме удалось довольно быстро. Оказывается, один из отслеживаемых авторов удалил свою страничку, и при попытке её прочитать приложение падало с исключением. А GAE, как оказалось, в случае падения cron-задания сразу же его перезапускает. Т.е. приложение отрабатывало раз за разом, без перерыва, каждый раз падая. А когда подходило время следующего сканирования, по cron'у запускался новый экземпляр, и тоже начинал непрерывно падать/перезапускаться. И так пока не кончились выделенные ресурсы.
      В итоге вынес для себя первое правило: исключения надо обрабатывать, а не забивать на них в надежде, что падение просто убьёт данный конкретный экземпляр.
      Итак, исправленная версия некоторое время успешно работала, но как только количество авторов превысило некоторый предел — вновь улетело в бан, уже с исчерпанием лимита обращений к БД.
      Вообще, база у них довольно своеобразно устроена. А уж подсчёт количества «условных операций» и вовсе производится чрезвычайно хитрым способом, т.ч. чем больше полей в записи, тем больше этих операций уходит во время поиска/считывания даже одного поля.
      В итоге единственный способ кардинально уменьшить кол-во операций — завернуть все записи в неиндексируемый blob (например, с помощью pickle), а наружу пробросить и хранить в открытом виде только те поля, которые непосредственно нужны для поиска.
      На некоторое время такой оптимизации хватало, но по мере роста числа пользователей вновь превысился лимит. Пришлось провести следующий этап оптимизации — активно использовать кэш (memcache). Теперь вся работа с записями была целиком помещена в кэш, операции с которым полностью бесплатны, а в базу его содержимое скидывалось раз в сутки. Кэш периодически сбрасывается, причём логики в этом процессе я не увидел — может работать неделю, а может по три раза в сутки очищаться. В случае сброса кэша приложение загружало в него данные из базы — в этом случае терялось лишь несколько последних обновлений, которые восстанавливались путём сканирования «Самиздата».
      По мере увеличения количества отслеживаемых авторов рос и трафик приложения, т.ч. пришлось, пока он не подобрался вплотную к лимитам, оптимизировать процесс сканирования.
      Так, большинство авторов обновлялось по утрам и вечерам, а после полуночи количество обновлений было исчезающе малым, да и обращений к RSS-ленте падало почти до нуля. Поэтому было решено с полуночи и до семи утра сканирование вообще не проводить. А в дальнейшем интервалы между сканированиями сделать настраивающимися — в зависимости от того, как часто в эти часы обновляются авторы.
      Немного общих впечатлений:
      1. Абсолютно все файлы должны использовать utf-8. Если где-то у вас используется другая кодировка, потом в самых неожиданных местах начнут вываливаться исключения. Это же касается и всех входных данных. Самиздат использует win-1251, и я потратил немало времени в бесплодных попытках парсить его прямо в этой кодировке. В итоге наконец перестал маяться дурью и просто конвертировал все входные данные в utf-8 в момент их чтения.
      2. Изрядно пришлось помучиться с функциями даты и времени. В конце концов привязался к UTC, ибо автоматическую работу с локальным временем так и не осилил — все эти функции ведут себя неадекватно, настройки всё время самопроизвольно сбрасываются, часть функций вообще не работает, вместо них там стоят заглушки. В общем, Python там какой-то урезанный.
      3. SDK для Windows не обрабатывает ошибок загрузки и не позволяет «разруливать» связанные с ними проблемы. Например, если на середине деплоя прервётся соединение, приложение окажется в мёртвом состоянии, и залить поверх него рабочую версию не получится — придётся закрыть SDK и открыть консоль, через которую и решаются все подобные случаи. Т.ч. лучше учиться сразу работать с консолью, т.к. рано или поздно она вам всё равно понадобится.
      • 0
        А в чем профит, ради чего вы преодолевали столько сложностей? Ваш проект ведь легко можно было бы хостить на каком-нибудь VPSе на $5/месяц.
        • +2
          В то время Google очень активно пиарил свой сервис, облака были в моде — считалось, что за этими технологиями будущее, а стандартные хостинги рано или поздно вымрут. Вот и стало интересно попробовать на вкус облачные технологии. В итоге пришёл к выводу, что GAE сильно на любителя. Это не самое удачное из облачных решений, т.к. в нагрузку к облаку идёт пакет специфических гуглотехнологий.
      • +3
        Я когда читал ваш комментарий у меня волосы дыбом вставали. Ну как такое можно писать не разобравшись в теме?

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

        Чего только стоит ваше:
        На некоторое время такой оптимизации хватало, но по мере роста числа пользователей вновь превысился лимит. Пришлось провести следующий этап оптимизации — активно использовать кэш (memcache). Теперь вся работа с записями была целиком помещена в кэш, операции с которым полностью бесплатны, а в базу его содержимое скидывалось раз в сутки. Кэш периодически сбрасывается, причём логики в этом процессе я не увидел — может работать неделю, а может по три раза в сутки очищаться.
        Логики? Какой логики вы ожидали? Кэш, он на то и кэш, что не гарантирует постоянное хранение данных, а тем более предсказуемое время хранения!

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

        Python действительно сильно порезан, но это как бы уже сразу описано в документации.

        По последнему пункту, вообще у меня такое бывало и не раз. И постоянно я видел в логе чёткие указание, что необходимо сделать чтобы исправить. И после одной команды всё приходило в норму. Прямо представляю ситуацию из параллельного мира, разработчик на php который деплоит через ftp на хостинг. И тут рвется соединение, половина прошла, а другая нет, а посетители таким образом нашли лазейки «для чего-нибудь», к примеру из-за этого создаются временные файлы. И бедный программист заново идет и руками разгребает, что наделали посетители, а потом ещё и жалуется что деплой в лайв не консистентый!

        Ну и финишом, рекомендация использовать виртуалки другого облачного хостинга? Серьезно? Это даже не на одном уровне. Я понимаю, если бы вы конкретно разобрались, а так это не профессионально.
      • 0
        Сколько примерно пользователей / обновлений держало в бесплатном режиме?
  • 0
    Не ваш ли проект VK Auidopad?
    • 0
      Нет, не мой.
      • 0
        Ясно, спасибо за статью, добавил в избранное для саморазвития ))
  • +1
    Спасибо! Очень подробная и весьма достойная статья. Вдвойне приятно, что выполняете данные обещания — это дорого стоит.
    • 0
      Благодарю, рад, что оказался полезен :)
  • +1
    Хорошая статья, продолжайте. Для себя узнал про пакет site, даже странно что я про него ничего не знал. Позор мне! =)
    • 0
      Спасибо :)
  • +1
    Касательно Mac OS — все точно также, есть Launcher, но можно и из командной строки все делать (и даже большее).
  • 0
    Кстати, а почему flask? Вот "сравнение".
  • 0
    Очень нужно про АПИ Вконтакте, может быть ответите на вопрос на стэке: stackoverflow.com/questions/25673941/how-to-save-json-from-vk-com-api-response-to-mongodb
    • 0
      Done!
      • 0
        plus.google.com/u/0/+VicNgrail/posts/f8YWYtdqGSz вот тут еще обсуждение, ответа пока нет, застрял на
         for vk_post in vk_posts[1:]:
                        print vk_post
        

        Этот код печатает то, что нужно сохранить, айтемы вроде: {u'likes': {u'count': 4, u'can_publish': 1, u'can_like': 1, u'user_likes': 0}, u'attachments': [{u'photo': {u'access_key': u'dff779d5b0ebfa8818', u'src': u'cs540104.vk.me/c540103/v5401039… u'from_id': 71234389}

        Вот не могу этот JSON сохранить

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.