ラベル gae の投稿を表示しています。 すべての投稿を表示
ラベル gae の投稿を表示しています。 すべての投稿を表示

2014/08/08

Google AppEngine/Python入門 - サーバを作ってみよう


Google App Engine(Python)でお手軽にサーバ構築する方法をご紹介します。
(これは東北Tech道場のハンズオンの資料でもあります)

アプリの応用範囲を広げられるように、本家のチュートリアルを構成変更して、実用性を高めています。


#1: Google App Engineとは?


簡単に説明するならGoogleが提供する至れり尽くせりなサーバ環境です。
こんな↓アイコンです。

昔はこんな↓アイコンでした。

むかしむかし、サーバを構築するには
・物理マシンを購入してきて、
・LinuxなどのOSをインストールして、
・データベース・サーバ(MySQLとか)、
・Webサーバ(Apache HTTP Serverとか)、
・開発環境(PHP、Perl、Pythonとか)
などをインストールする必要がありました。
さらに、それらが正しく動くように設定する必要がありました。

こういうのをまとめてLAMP環境(Linux,、Apache, MySQL, P系言語)などと言います。
お金と手間がかかって、サーバを構築するだけで一苦労でした。
多くの人が挫折を繰り返し、
多くの人が残業を繰り返しました。
昔はね。

App Engineはそういうのを初めから全部準備してくれているサービスです。
OS?データベース?Webサーバ?開発環境?全部あるよ、App Engineにね。
(もちろん、これら以外にも沢山のサーバ機能が準備されています。)

#2: でもお高いんでしょ?


ちょっとしか使わないなら無料なので、安心して使いはじめることができます。
たとえばアップロードするファイル容量は合計で1GBまで無料で使えます。
データベースの容量(Datastoreなど)も1GBまで無料で使えます。

無料で使える制限を超えてしまった場合は、クレジットカードを登録しない限りは課金されないので、それも安心。

課金体系はココを参考にして下さい。
英語とドル表記で分かりにくいですが、それほど高くないです。

たとえば、
ファイル容量を1GB追加したかったら月$0.026(3円ぐらい)かかります。
データベースの容量を1GB追加したかったら月$0.18(20円ぐらい)かかります。

安いだけでなく、使えば使うほど、値上がり幅を小さくすると今年の6月ぐらいに発表されました。


値上がり幅の詳細も公表されています。


#3: プログラミング始めたばかりですが…


現時点でApp EngineはPython/Java/Go/PHPの4つの言語が使えます。
Android開発からプログラミングを始めました、という人にとっては安心のJavaが含まれています。

聞いたことのない言語があるかもしれません。
Goとか何?ググったら20億件以上の結果が出てくるんですけど。

プログラミング言語は道具なので、何を使っても良いのですが、道具を色々使えると今後の楽しみが増えるので、今回はPythonを使ってApp Engineのアプリケーション開発の方法を説明します。

人:「おめー、言語、何使ってんよ?」
俺:「Java」
人:「ふーん」
俺:「と、Python」
人:「ぉ、おぉぅ!」
俺:「おめーは?
人:「まぁ、いろいろよ

って感じになれるはずです。
Pythonじゃなくてもいいけど、Pythonもいいよ。Pythonがいいよ。

でも、言語自慢してると、

人2:「おれHaskell」
人3:「おれCOBOL」
人4:「おれBrainF*ck」

などと、ややこしそうな人が寄ってくるのでホドホドに。
(同じ現象がエディタにも起こるので、そちら方面も注意!)

ちなみにPythonのアイコンはこれ↓
ニシキヘビ(Python)です。
これが可愛く見えてきたら立派なパイソニストです。
(パイソニスタとかパイソニアンとかパイソナーと呼ぶ人もいます)
ちなみにJava使いはJavaer(じゃばー)、PHP使いはPHPer(ぺちぱー)とか呼ばれます。Goは?

#4: Google App Engine(Python)をはじめよう!

#4-1: App Engineアプリの開発環境を整えよう

開発を始めるために、SDKをダウンロードします。
ダウンロードページから自分が使っているOSのPython版のSDK(というかランチャー)をダウンロードします。

ここからはMac OS X版をベースに紹介します。
WindowsやLinuxを使っている人もMac版とほぼ同じです。
本家でもインストール手順を紹介しています。
とっても簡単なので参考にして環境構築して下さい。

ダウンロードしてたファイルをダブルクリックするとシンプルなアイコンが登場します。


これをアプリケーションフォルダーにコピーすればインストール完了。


Pythonはversion2.7系が必要です。
Mac OSXの場合ははじめからインストールされています。(たぶん)
Pythonのバージョンを確認しましょう。
まずターミナルを開いて


/usr/bin/env python -V

と入力して下さい。
するとバージョンが表示されます。


2.7.6と表示されたのがバージョン名です。
バージョン名が表示されなかったり、バージョンが2.7.*でなかったら本家からダウンロードしてインストールしましょう。

これでApp Engineを始める準備が完了しました。
Pythonがインストールされている環境であればApp EngineのSDK(ランチャー)をダウンロードするだけです。
これでもうサーバアプリケーションの開発ができるようになりました。

挫折した人は比較的少ないのではないでしょうか?
残業する人もぐっと減ったのではないでしょうか?
お金はまだかかっていません。

#4-2: App Engineアプリを登録する


App Engineのアプリを開発を始めましょう。
アプリはインターネット上に公開されますので、まずはApp Engineアプリを登録する必要があります。

と言ってもとても簡単で、1ステップです。
アプリ登録画面を開きます。


2項目を入力するだけです。

Application Identifierは世界で唯一の名前を指定する必要があります。
Check Availability」ボタンで名前が利用可能か調べましょう。


すでに誰かに使われていたり、使えない名前だと「Sorry,...」と表示されます。
使える名前を探しましょう。

Application Identifierは「Application ID.appspot.com」というサーバのドメイン(ブラウザでアクセスするURL)名にも使われます。
あなたが開発したアプリが公開されるアドレスです。
(独自ドメインを後から設定することもできます)

といっても、気に入らなければ新しいのを作れば良いので、気軽に始めましょう。

あとはApplication Title(アプリのタイトル)を指定して、「Create Application」ボタンをクリックするだけです。
すると「Application Registered Successfully」という味気ないシンプルな画面が表示されたらアプリの登録作業が完了です。


2項目も入力しちゃった!ちょっと休憩しましょう。

#4-3: App Engineのアプリケーションを作ろう


さて、いよいよ、App Engineアプリを開発していきます。
ApplicationフォルダのApp Engineランチャーアイコンをダブルクリック。


ランチャーが起動したら、左下の「+」をクリックしてアプリを追加します。
Application IDに先ほど登録したApplication Identifierの文字をと入力します。
Application Directoryには任意のフォルダを指定します。


文字の入力が完了したら「Create」ボタンをクリックするとアプリのベースが完成です。
指定したフォルダの中にApplication ID名のフォルダができていて、その中に幾つかファイルができていれば成功です。


#4-4: アプリケーションを実行する


実は、この時点でHello world!を表示するアプリがすでに完成しています。
試しにアプリを動かしてみます。

ランチャーにはApplication IDがリスト表示されているので、
それを選択し左上の「Run」ボタンをクリックします。


そうすると開発サーバが自動で起動します。
起動したら「Stop」ボタンと「Browse」ボタンがクリックできるようになるので、
「Browser」ボタンをクリックします。
すると、アプリが実行された画面が表示されます。


勝手にブラウザが開きます。
開いたページは以下のようになります。


Hello world!とだけ表示されました。

#4-5: 挙動の説明するよ


言われるがままにポチポチしてたら何か出た。
という感じだと思うので、ここで少し何がどうなっているのかを説明します。

このアプリの実行に必要なファイルはapp.yamlとmain.pyの2つです。

app.yamlの内容は以下のようになっています。
YAML(ヤムル)という形式の書き方です。

application: helloworld-adamrocker
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"

色々設定らしき項目が見えますが、注目は「url」の項目です。
その下に「script: main.app」と書かれています。

これはurlで指定したURLのパス(ここの表記だと全てのパス)にブラウザでアクセスされた時にはmain.pyを実行する、ということを書いています。

次にmain.pyを開いてみましょう。

#!/usr/bin/env python
import webapp2

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

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

実際のファイルには色々とコメントが書かれていますが、プログムで必要な部分はこれだけです。
下から3行分がURLのパスとプログラムを関連付けるところです。

パス「/」に「MainHandler」というクラスが関連付けられています。

つまり、App Engineはapp.yamlを読み取り、ユーザが「/」にアクセスしたらmain.pyを事項し、main.pyの中で「/」に関連付けられているMainHandlerクラスを実行します。

ブラウザを開いてアプリにアクセスする場合、HTTP GETメソッドが使われますので、MainHandlerクラスのgetというメソッドが実際には実行されます。

実行内容はたった1行で

self.response.write('Hello world!')

です。
これがApp Engineのアプリでブラウザに「Hello world!」を表示する仕組みです。
(注:これはApp EngineというよりdjangoというPythonフレームワークの仕組みです)


#4-6: ちょっとカスタマイズしてみよう


「/say」にアクセスした時に、別の文字を表示するようにカスタマイズしてみます。

main.pyにパスとクラスの関連付けを追加します。

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

/sayにアクセスした時にSayHandlerクラスが実行されるようにしました。
次にSayHandlerクラスを定義します。

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

class SayHandler(webapp2.RequestHandler):
    def get(self):
        self.response.write('Say hello!')

ちなみにPythonは「{」や「}」のようなコードブロックを明示する構文がありません。その代わりに、スペースやタブの数がコードブロックを表します。なのでPythonのプログラムを書く時やコピペするときはスペースの数を間違えないように気をつけましょう。

ブラウザで/sayにアクセスするとこうなります。


Say hello!が表示されました。
簡単ですね!

では、さっそく、この素晴らしいアプリを全世界に公開してみましょう。

全世界とかコワイ!とかビビらなくても大丈夫です。
どこからもリンク貼られてないので、実質、誰からも見つけられませんw

公開するのも、とても簡単です。
「Deploy」ボタンをクリックするだけ。


クリックするとGoogleアカウントの認証が出てきますので、アカウントとパスワードを入力します。
認証が完了したら、アプリのアップロード(これをデプロイと言います)が開始されます。
デプロイが完了すると、ブラウザで「http://{{ Application ID }}.appspot.com」にアクセスしてみましょう。


ちゃんと表示されました!/sayはどうでしょうか?


完璧です。たった3〜5行ぐらい書いただけでサーバアプリが完成しました。

現在のコードはこんな感じになっています。

#!/usr/bin/env python

import webapp2

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

class SayHandler(BaseHandler):
    def get(self):
        self.response.write('Say hello!')

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


#4-7: ログを確認しよう


アプリ開発から少し脱線します。
プログラムが正しく動作しない場合、エラーログを確認して問題の手がかりを得て修正します。
App Engineでもエラーログを確認する方法がちゃんと提供されています。
ランチャーのLogsがそれです。


Logsをクリックするとログコンソールが表示されます。


デバッグする時、ログコンソールに文字を出力したい場合が多々あると思います。
その方法も簡単。以下の様なコードで出力できます。

import logging
class SayHandler(webapp2.RequestHandler):
    def get(self):
        logging.info("============= /say ==============")
        self.response.write('Say hello!')

「/say」にアクセスすると以下のようにログが出力されます。


ちゃんとログが出力されています。
デプロイしたApp Engineアプリのログは、App Engineのコンソールにアクセスし、左にあるメニューの「Logs」で確認できます。


現在App EngineのコンソールはUI改善中のようです。ページの上部に「Try the new Logs Viewer」をクリックすると、下記のような新しいログ表示画面に移動できます。ちょっと見やすくなっています。



#5: HTMLを表示しよう!


Hello world!だけだと味気ないので、HTMLを表示してみましょう。
直接self.response.write()メソッドでHTMLを書いても良いのですが、実用向けじゃないのでJinja2というテンプレートエンジンを使います。

いきなり難しそうですが、たいして難しくありません。
しかも、一度書いてしまえば、ずっと使いまわせるので、この際やっちゃいましょう。

まずはアプリにライブラリを追加します。app.yamlのlibrariesという項目にjinja2を追加して以下のように記述します。2行追加しただけ。

libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

main.pyの先頭にJinja2の設定を記入します。

import os
import jinja2
JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

なにやらちょっとややこしそう。。。
いつか、設定を変更したいということがあったら、このプログラムの周辺を調べてみてください。いろいろ設定できて便利です。

今回は、この設定で十分なので、このままコピペしちゃいましょう。
テンプレートでHTMLを表示する時は

template = JINJA_ENVIRONMENT.get_template("file.html")
values = { 'var1': 1 }
self.response.write(template.render(values))
などと、同じことを何度も書くことになるので、使いまわせるようにテンプレートを使うクラスBaseHandlerを定義しておきましょう。

class BaseHandler(webapp2.RequestHandler):
    def render(self, html, values={}):
        template = JINJA_ENVIRONMENT.get_template(html)
        self.response.write(template.render(values))

これで準備完了です。
MainHandlerはwebapp2.RequestHandlerを継承していましたが、先ほど定義したBaseHandlerを継承するように変更します。
そしてHTMLを表示するように変更します。

class MainHandler(BaseHandler):
    def get(self):
        self.render("main.html")

アプリフォルダに以下のmain.htmlを作ります。

<html>
  <body>
    <div>Say hello!</div>
  </body>
</html>


完成です。
アプリフォルダの中身はこんな感じになっています。


10行もプログラムかいちゃいましたね。
さっそく開発サーバでアプリを動かしてみます。


ちゃんと動いていますね!
でも表示が同じなのでHTMLなのかあやしいです。

HTMLを覗いてみるとどうなっているでしょうか?
ブラウザで開いているページを右クリックして「ページのソースを表示」を選択するとHTMLを見ることができます。


これでHTMLを表示させる方法が習得できました!

今現在のソースコードはこんな感じになっていますよ。
#!/usr/bin/env python

import webapp2
import os
import jinja2
import logging

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

class BaseHandler(webapp2.RequestHandler):
    def render(self, html, values={}):
        template = JINJA_ENVIRONMENT.get_template(html)
        self.response.write(template.render(values))

class MainHandler(BaseHandler):
    def get(self):
        self.render('main.html')

class SayHandler(webapp2.RequestHandler):
    def get(self):
        logging.info("============= /say ==============")
        self.response.write('Say hello!')

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

ちなみに、app.yamlはこんな感じです。
application: helloworld-adamrocker
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: latest
- name: jinja2
  version: latest

#6: データを保存しよう!


モバイルアプリの場合、サーバをデータの保存、共有目的で使うことが多いと思います。
またWebアプリでも、やはり、データを保存、共有することが多いでしょう。
なので、さっそくデータを保存できるようにしましょう。

#6-1: データ入力フォームを作る


先ほどのmain.htmlを修正してデータを入力できるフォームを作ります。

<html>
  <body>
    <form action="/" method="POST">
      name: <input name="name" type="text" /><br />
      age: <input name="age" type="text" /><br />
      <button type="submit">Save</button>
    </form>
  </body>
</html>

nameに名前を入れ、ageに年齢を入れて保存するだけの投稿フォームです。
methodがPOSTで、actionが「/」になっているので、「/」に対してHTTP POSTリクエストを要求するフォームです。

さっきまではブラウザにURLを直接入力アクセスしていたのでHTTP GETリクエストでしたが、今回はPOSTリクエストを使います。

GETとPOSTリクエストの違いは色々あるのですが、一番わかり易い違いは、URLに表示されるか否かです。
POSTリクエストはURLをブラウザに入力しても表示されませんし、リクエストでサーバに渡すパラメータ(今回だとnameとかage)もURLには表示されません。

#6-2: データを受け取って保存する


サーバ側でのデータ保存にはDatastoreを使います。
画像や大きなファイルを保存するには別の方法(BlobstoreとかGoogle Cloud Storage)を使ったりするので興味があったら発展として調べてみてください。

さっそく何を保存するのかを定義します。
と言っても特別な設定ファイルが必要なわけではなく、プログラム上でクラスを定義するだけです。
今回はnameという文字列とageという数字(整数)を保存します。

from google.appengine.ext import ndb
class UserData(ndb.Model):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)


文字列や整数以外の値を保存したい場合は、本家のPropertiesというところを参考に、必要なデータを追加してみてください。

あとは、「/」にPOSTリクエストが届くはずなので、まずはそのリクエストを受け付けてデータを保存する処理を書きます。

class MainHandler(BaseHandler):
    def get(self):
        self.render('main.html')

    def post(self):
        name = self.request.get('name')
        age_str = self.request.get('age')
        if name is None or age_str is None:
            self.redirect('/')

        user = UserData()
        user.name = name
        user.age = int(age_str)
        user.put()
        self.redirect('/')

MainHandlerにpostというメソッドを追加しています。
これがPOSTリクエストを受け付けた時に実行されるメソッドです。

ここでは、POSTリクエストのnameとageというパラメータを受取り、UserDataのインスタンス(user)の各変数に格納してputメソッドでDatastoreに保存しています。保存が終わったら「/」にリダイレクト(移動)しています。


たったこれだけでサーバにデータを保存できます。簡単ですね。

ただ、これだとデータを保存しただけで、取り出すことができません。
最後に、データを取り出す方法を学んでこのエントリーを終わりにしましょう。

#6-3: データを取り出す


MainHandlerのgetメソッドでデータを取り出せるようにします。
取り出し方もかなり簡単です。

users = UserData.query().order(-UserData.date).fetch(10)

たったこれだけでUserDataに保存したデータを新しい順に10個取り出せます。
fetchメソッドの引数が取り出す個数です。最大1000個まで同時に取り出せます。

orderメソッドは並び順です。
取り出したデータをdataの大きい順に取り出しています。
いわゆる降順と呼ばれる並び順です。

反対にデータの小さい順(昇順)に取り出すには。
order(UserData.date)

と書きます。プラスかマイナスを指定するだけです。

queryメソッドで取り出すデータの条件を指定しています。
今回は何も指定していないので、全てのデータです。
(特定の条件に合致するデータを取得する方法は本エントリー下部の発展#1,2を参考にして下さい)

これをMainHandlerのgetメソッドの中に仕込み、main.htmlの中で表示するようにします。
class MainHandler(BaseHandler):
    def get(self):
        users = UserData.query().order(-UserData.date).fetch(10)
        values = { 'users':users }
        self.render('main.html', values)

<html>
  <body>
    <form action="/" method="POST">
      name: <input name="name" type="text" />
      age: <input name="age" type="text" />
      <button type="submit">Save</button>
    </form>
<hr />
    {% for user in users %}
    <p>{{ user.name }} = {{ user.age }}</p>
    {% endfor %}
</body>
</html>

HTMLの中にHTMLらしからぬコードが入っていますね。
これがテンプレートエンジンの便利なところです。
HTMLを自動で作ってくれるのです。

どういうことかと言うと、
{% for ... %}がプログラムでよく書くfor文ですね。
{% endfor %}までの間を繰り返してくれます。

繰返す内容は
<p>{{ user.name }} = {{ user.age }}</p>
です。
つまり、データベースから取得した最大10個のデータ(users)を一つづつ<p>で囲ったHTMLを作成しています。

テンプレートHTMLにデータを渡す方法はMainHandlerのvalues変数がそれです。
サーバのデータを使って、HTMLが自動で作れるなんて便利ですね!

さぁ、完成です。
開発サーバで「/」にアクセスしてみます。

ちゃんと、保存したデータが表示されました。
これで自分が作ったサーバにデータを入力して取り出せるようになりました!
お疲れ様でした。これでApp Engineを使って自分だけのサーバを作り、公開できるようになりました。

#7: おわりに


最後に、Google App Engineアプリのサーバ上での状況はコンソールから確認できます。
サーバの使用状況、Datastoreの中身、ログを確認したり。
課金もコンソールから設定できます。

ちょっとでも興味を持たれた方は、本家のチュートリアルにチャレンジして下さい。
本エントリにも発展として#1〜#3までを準備していますが、おさらいにもなるので、本家のチュートリアルはオススメです。

さらに、色々なAPIを使う練習ができるトレーニングコースもあります。App Engineで何ができるのかを勉強するにはもってこいです。
もっと発展して勉強したい方は、本家が公開している、サンプルコードを覗いてみても良いでしょう。

では。お疲れ様でした!

最後のソースコードはこんな感じです。
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import webapp2
import jinja2
import logging
from google.appengine.ext import ndb

JINJA_ENVIRONMENT = jinja2.Environment(
    loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
    extensions=['jinja2.ext.autoescape'],
    autoescape=True)

class UserData(ndb.Model):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

class BaseHandler(webapp2.RequestHandler):
    def render(self, html, values={}):
        template = JINJA_ENVIRONMENT.get_template(html)
        self.response.write(template.render(values))

class MainHandler(BaseHandler):
    def get(self):
        users = UserData.query().order(-UserData.date).fetch(10)
        values = {
            'users':users
        }
        self.render('main.html', values)

    def post(self):
        name = self.request.get('name')
        age_str = self.request.get('age')
        if name is None or age_str is None:
            self.redirect('/')

        user = UserData()
        user.name = name
        user.age = int(age_str)
        user.put()
        self.redirect('/')

class SayHandler(webapp2.RequestHandler):
    def get(self):
        logging.info("============= /say ==============")
        self.response.write('Hello world!')

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

app.yamlはこんな感じです。
application: helloworld-adamrocker
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: latest
- name: jinja2
  version: latest

#発展1: 特定の条件でデータを取得する


Datastoreでデータを取得する方法は以下のコードでした。
users = UserData.query().order(-UserData.date).fetch(10)

queryメソッドに条件を加える事で、特定のデータだけを取り出すことができます。
例えば、ageが18以上のデータを取り題したい場合は
query(UserData.age >= 18)

とします。
ただ、これだとエラーになります。
App EngineのDatastoreの特徴で、2つ以上の変数で並び替えができなくなっています。
変数ageでは並び替えをしていないように見えますが、実は、内部のデータとして、ageで並び替えをしているのです。そのおかげで「>=」という範囲指定のフィルタリングができています。
queryメソッドのageにくわえて、orderメソッドでdataについて並び替えをしていますので、2つ以上の変数で並び替えをしているためエラーになります。

回避方法は簡単です。orderメソッドを外すだけです。
users = UserData.query().fetch(10)

さらに、dataで並び替えしたい場合は、取得したusersを並び替えれるプログラムを組めばOKです。
sorted_users = sorted(users, key=lambda u:u.age)

値が大きい順にソートしたい場合は、
sorted_users = sorted(users, key=lambda u:u.age, reverse=True)

#発展2: 値を指定してデータを取得する


では、ageの値を指定して取得したらどうなるでしょう?
こんな感じ、
query(UserData.age == 18)

これは大丈夫。ちゃんと動きます。
ただし、ちょっと注意が必要です。
アプリを作った時に自動で作られたファイルにindex.yamlがありました。
いままで、なににも使っていなかったのですが、このファイルをおもむろに開いてみましょう
indexes:

- kind: UserData
  properties:
  - name: age
  - name: date
    direction: desc

自動で何かが追加されています。
これはどう読みとくかというと、
UserDataに対して、ageとdataというパラメータで条件をつけてデータを取り出すので予め準備しておいてね。
特にdataは降順(desc)でとり出すからそこんところよろしく。
ということです。
Datastoreの場合は、事前にアクセスする方法をデータベースに教えて必要があります。
ちょっと煩わしいですが、自動で追加されるので、便利ですね。
開発サーバで事項しないとindex.yamlは自動で追加されないので、その時は自分で記入することになります。
自動で追加されるということは、自分の知らない間にドンドン増えていく事になります。
何が起きているかだけでも把握しておくと良いでしょう。

#発展3: Androidからデータを保存する


AndroidでApp Engineのサーバにデータを保存してみましょう。

    private void access() {
        ArrayList params = new ArrayList();
        params.add(new BasicNameValuePair("name", "ADAM"));
        params.add(new BasicNameValuePair("age", "1"));
        HttpPost httpPost = new HttpPost("http://helloworld-adamrocker.appspot.com/");
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        DefaultHttpClient client = new DefaultHttpClient();
        try {
            HttpResponse httpResponse = client.execute(httpPost);
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    new AsyncTask() {
        @Override
        protected String doInBackground(String... params) {
            access();
            return null;
        }
    }.execute();

ブラウザで結果を確認してみます。
ちゃんとADAMと1が入っていました!
これでAndroidからクラウドにデータを保存することができました。
これを取り出してアプリに反映させることが出来れば、、、
他の人とデータを共有できれば、、、
アプリの可能性がドンドン広がりそうですね^^


2014/06/18

AppEngine(Python)でDatastoreのユニットテストする方法

AppEngineはプロダクションサーバ(いわゆる運用サーバ)上でDatastoreなどを用いたのテストができません。データを完璧に切り分けて自由にいじり倒してテストできるような環境を作ってくれたりすると嬉しいのですが、そういう感じには進んでいないようです。
ということで、似たような環境をローカルに構築してテストをするしかありません。
今回はその方法をメモしておきます。

STEP1: Datastoreのデータをローカルに構築
前回のエントリーを参考にしてAppEngine上で運用されているDatastoreをダウンロードして、ローカルの開発サーバにアップロードします。一度アップロードしておけば、次からは既にデータが構築された状態になっているので、構築作業は初めの1回だけです。

STEP2(a): Unitテストを記述する
AppEngine/pでUnitテストを書く方法は簡単です。unittest.TestCaseクラスを継承するだけ。
import unittest
from entity import ShopData

class TestShopData(unittest.TestCase):
    def setUp(self):
        #do something before testing each test method

    def tearDown(self):
        # do something after testing each test method

    def test_check_name(self):
        shop = ShopData.all().fileter('email =', 'adamrocker@gmail.com').get()
        self.assertEqual(shop.name, 'adam')

STEP2(b): サンドボックス環境でUnitテストする
AppEngineのUnitテストにはtestbedというサンドボックス環境が提供されています。
testbedを簡単に説明するなら、各テストで副作用の有るような処理(例えばmemcacheの登録・削除など)を各テストが終了した時になかった事にしてくれる便利なテスト環境です。
testbedを使うには以下のようにsetUpで準備しtearDownで後処理を加えます。

import unittest
from google.appengine.ext import testbed
from entity import ShopData

class TestShopData(unittest.TestCase):
    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self.testbed.init_datastore_v3_stub(datastore_file='/tmp/gae.datastore', use_sqlite=True)

    def tearDown(self):
        self.testbed.deactivate()

    def test_put_shop(self):
        shop = ShopData()
        shop.email = 'eve@gmail.com'
        shop.name = 'eve'
        shop.put()
        count = ShopData().all().filter('email =', 'eve@gmail.com').count()
        self.assertEqual(count, 1)

init_datastore_v3_stubメソッドの引数datastore_fileでローカルの開発サーバが保持しているDatastoreのファイルの位置を示しています。
開発サーバを起動するときに指定したオプション(--datastore_path)のファイルパスです。datastore_file引数を持たない場合は何も格納されていないDatastoreがメモリ上に作られます。既存のDatastoreを用いてテストしたい場合はdatastore_fileを利用するとよいでしょう。
UnitテストでShopDataを追加していますが、setUpで設定したtestbedがコレを受け止め、test_put_shopメソッドが終了するとtearDownメソッドでtestbedがdeactivateし、Datastoreの変更がなかったことになります。
なので、test_put_shopテストを何度実行してもTrueになります。

AppEngineのDatastoreをローカルの開発サーバで使う方法

AppEngineのテスト環境を整えている時に欠かせないのがDatastoreなどのテストです。
実行環境と同じような環境をローカルで構築してテストしたいので、すでにオンラインにあるDatastoreを取り出してローカルの開発サーバに入れて試したのでメモしておきます。

STEP1: AppEngineアプリにリモートアクセスできるように設定する
app.yamlに以下のディレクティブを追加する。
builtins:
- remote_api: on
この状態でデプロイしておく。

STEP2: オンラインのDatastoreをダウンロードする
remote_apiがONになっていればダウンロード用のコマンドでDatastoreのデータを取得できるよう。例えば以下のコマンドを実行するだけでOK。
% appcfg.py download_data --kind=ShopData --url=http://{app-id}.appspot.com/_ah/remote_api --filename=ShopData.dump 

{app-id}はAppEngineのアプリIDです。このコマンドによりShopDataというエンティティをダウンロードできます。ローカルにShopData.dumpというファイルで保存され瑠葉に指定していますがファイル名は任意です。
ダウンロードするにはEmailとPasswordが聞かれるのでAppEngineの管理者(or 開発者)アカウントのEmailとPasswordを入力すればOKです。

STEP3: 開発サーバにDatastoreを格納する
こちらもコマンドが準備されているので以下のコマンドを実行するだけ。もちろんローカルサーバが立ち上がっている必要がある。今回は8080ポートで起動しているとします。
% appcfg.py upload_data --url=http://localhost:8080/_ah/remote_api --application=`dev~{app-id}` --filename=ShopData.dump --num_threads=5
--applicationというオプションでapp-idを指定しますが、その前に「dev~」を追加するのをお忘れなく。
アップロードにも同じようにEmailとPasswordが聞かれますのでダウンロードした時のEmailとPasswordを入力します。

これでローカルの開発環境でDatastoreを使えるようになりました。
たとえば、
shop = ShopData.all().filter('email =', 'adamrocker@gmail.com').get()
logging.info(shop.name)
などというプログラムがローカルの開発サーバでも動きます。

2014/06/12

Google AppEngineのアクセスログをBigQueryに流し込む

前回のエントリでBigQueryのStream APIを使ってAppEngineからデータを流し込む事ができたので、AppEngineのリクエストログをBigQueryに流し込んでみます。
これでAppEngineのアクセスをBigQuery基盤でビッグデータ解析してます!と言えますw

STEP0: 事前準備
前回のエントリでBigQueryとAppEngineが連携できている事を前提とします。

STEP1: AppEngineのログを取得する
AppEngine(Python)のLogServiceを使ってリクエストログを抜き取り、BigQuery Stream APIで直接BigQueryにデータを流し込みます。
import time
from google.appengine.api import memcache
from google.appengine.api.logservice import logservice
import httplib2
from apiclient import discovery
from oauth2client import appengine

PROJECT_ID = 'my-project'
SCOPE = 'https://www.googleapis.com/auth/bigquery'
LAST_TIME = 'last_time'

start_time = memcache.get(LAST_TIME)
end_time = time.time()
body = []
for log in logservice.fetch(start_time=start_time,
                                        end_time=end_time,
                                        offset=None,
                                        minimum_log_level=logservice.LOG_LEVEL_INFO,
                                        include_incomplete=True,
                                        include_app_logs=True):
    acc = {}
    acc['ip'] = log.ip
    acc['method'] = log.method
    acc['user_agent'] = log.user_agent
    # add info you need
    body.append( {'json':acc} )

credentials = appengine.AppAssertionCredentials(scope=SCOPE)
http = credentials.authorize(httplib2.Http(memcache))
bigquery = discovery.build('bigquery', 'v2', http=http)
response = bigquery.tabledata().insertAll(projectId=PROJECT_ID,
                                                  datasetId='AppEngine',
                                                  tableId='MyTable',
                                                  body=body).execute()

memcache.set(LAST_TIME, end_time, time=3600)

これで完了。logserviceに関してReferenceを参照するとどういう情報が取得できるのかが分かりますので、必要なデータをBigQueryに流し込むと良いと思います。
start_timeとend_timeは指定しておくと、重複なくアクセスログを回収できると思います。

2014/06/10

Google AppEngineからBigQueryのStream APIにデータを流しこむ方法

BigQueryはビッグデータ解析基盤としてかなり優秀だと思っていました。
ただ、データのアップロードがかなり面倒だった(一度Google Cloud StorageにアップロードしてBigQueryからそのデータを引っ張る)ので二の足を踏んでいました。
最近、Stream APIの制限がかなり緩和されたので、これを期にGoogle AppEngine(Python)と連携させてみた。

STEP1: BigQueryのセットアップ
まずはBigQueryを使える状況にする必要がある。
Google Developers Consoleからプロジェクトを作り、プロジェクトメニューの「APIs」からBigQuery APIをONにする。プロジェクト自体のBilling設定が必要なので注意。といってもかなりの量は無料で使える。

STEP2: クラウドのパーミッション設定
Google Developers Consoleのプロジェクトの「権限(Permissions)」メニューの中の「サービス アカウント」にApp Engineのサービスアカウントを追加する。
AppEngineのサービスアカウントはAppEngineのコンソールの「Application Settings」メニューの中の「Service Account Name」の内容。xxxx@appspot.gserviceaccount.comの様なフォーマットになっているのがそれ。
これで、AppEngineから同一プロジェクトのクラウドサービスへ簡単に(&安全に)接続できるようになります。

STEP3: Google AppEngineにライブラリーをインストール
Google APIを簡単に使うためのライブラリを幾つかインストールしておきます。Pure PythonなのでファイルをコピーするだけでOK。
Downloadはgoogle-api-python-clientからgoogle-api-python-client-1.2.zipをダウンロードしました。
ZIPファイル内の
・apiclientフォルダ
・oauth2clientフォルダ
・uritemplateフォルダ
の3つのフォルダをAppEngineのプロジェクトにコピーします。
私はライブラリ関係をまとめておくため、AppEngineのプロジェクトフォルダ内にlibフォルダを作り、そこに上記3つのフォルダーをコピーしています。

STEP4: AppEngineのからデータをBigQueryに流し込む
BigQueryのStream APIを使う。
Pythonのソースコードは以下の通り
if 'lib' not in sys.path:
    sys.path[0:0] = ['lib']

PROJECT_ID = 'my-project'
SCOPE = 'https://www.googleapis.com/auth/bigquery'

class BqHandler(webapp.RequestHandler):
  def get(self):
    import httplib2
    from apiclient import discovery
    from oauth2client import appengine
    from google.appengine.api import memcache

    credentials = appengine.AppAssertionCredentials(scope=SCOPE)
    http = credentials.authorize(httplib2.Http(memcache))
    bigquery = discovery.build('bigquery', 'v2', http=http)

    body = { 'rows':[{'json':{'id':1, 'name':'Larry'}}, {'json':{'id':2, 'name':'Sergey'}}] }
    response = bigquery.tabledata().insertAll(projectId=PROJECT_ID,
                                                                     datasetId='AppEngine',
                                                                     tableId='MyTable',
                                                                     body=body).execute()

    return

application = webapp.WSGIApplication(
                                     [('/bq', BqHandler)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

BigQueryに流し込むデータはbodyです。BigQueryはCSV形式とJSON形式をサポートしていますので、ここではJSON形式にします。
insertAllメソッドでStream APIを使っています。
datasetIdとはデータテーブルを纏める上位構造で、実際のデータが格納されるのはtableIdのテーブルです。

STEP5: BigQueryにDatasetとテーブルを作る
STEP4のプログラムを実行しても、まだBigQuery側に対象となるDatasetとテーブルが存在しないのでデータが保存できません。
そこで、BigQueryのコンソールからまず、DatasetとTableを作ります。

プロジェクト名の右側のプルダウンメニューから「Create new dataset」を選択します。
Dataset IDを「AppEngine」にします。
つぎに作られた「AppEngine」のデータセットの右側のプルダウンメニューから「Create new table」でデータを格納するテーブルを作成します。
「Table ID」を「MyTable」にし、Source FormatをJSONとし、サンプルとなるファイルをアップロードします。
例えば以下の様なJSONファイルをアップロードします。

{"id":0,"name":"test"}

次にテーブルのスキーマを指定します。
今回はidとnameで、それぞれの型が「integer」と「string」なので、以下のように設定します。
id:integer,name:string
これで設定が完了です。アップロードしたJSONファイルと設定したスキーマからテーブルが自動で作成されれば成功です(数十秒かかるようです)。
これでSTEP4のプログラムをAppEngine上で動かせば、bodyの内容がAppEngineデータセットのMyTableにデータが格納されます。
これで、AppEngineからBigQueryに色々なデータを格納できるようになり、そこで解析できるようになりました。