Google APP Engine Python入門(2010年2月版)

 Google APP Engineについては初期のころのまとめはあるのですが、Pythonですとリリースからそろそろ2年近くになり内容も大きく様変わりしています。最速マスターシリーズでもGoogle APP Engineについてのまとめが無く、そろそろアップデートの必要があると思いまとめてみました。
 基本的windows環境中心です。

最初に

 ドキュメントを見るときは必ず英語版を見ましょう。日本語版があるのはありがたいのですが、バージョンとしてはかなり古く、特に歴史の長いPython版では現行の内容とはかなりの隔たりがあります。

 Google APP EngineのドキュメントのURLは以下のようなパターンになっています。

http://code.google.com/intl/ja/appengine/docs/****
 
 これのうちjaが言語を表すコードになっていますので、これをenに変えて以下のような形にすることで英語版にアクセスできます。

http://code.google.com/intl/en/appengine/docs/****

環境構築からとりあえずローカルで動かすまでの流れ

環境構築

 Google APP Engine PythonはPython2.5を前提としていますので、まずそのインストールが必要です。WindowsMacならばこちらのダウンロードコーナーからインストーラーつきのファイルがダウンロードできます。ただしwindowsの場合、環境変数の設定が必要です
 linuxの場合はお約束のwgetしてのMakeです。

 また画像処理を行う場合、PILというライブラリが必要ですのでインストールしてください

 次にダウンロードページからSDKをダウンロードしてください。
 インストーラないしは単純な解凍でOKです。

アプリケーションの作成

 ただ作るだけならば、SDK内部にあるnew_projecte_templateをコピーしてリネームするか、MacとwindwosであればランチャーのfileメニューにあるCreate New Applicationで作るのが手っ取り速いです。
 直接作りたい場合、具体的な手順は以下のとおりです。

1.SDKの入ったディレクトリ/フォルダに移動/開く
2.その下にアプリケーションと同じ名前のディレクトリ/フォルダを作る
3..app.yamlを置く
4.適当な名前のPythonスクリプトファイルを作る

※app.yamlの最も単純な形。 詳しくはPython アプリケーション設定を参照してください

application: (アプリケーションの名前)
version: 1
runtime: python
api_version: 1
起動

 linuxの場合は、Google APP EngineのSDKを解凍したディレクトリに移動し、次のコマンドを入力します。

 dev_appserver.py (アプリケーションの名前)

 WindowsMacでは、ランチャーのcontrolメニューにある。ただし、データストアの情報をクリアしながら起動する場合には対応していません。この場合は以下のコマンドを入力する必要があります

 dev_appserver.py --clear_datastore (アプリケーションの名前)

 起動後はhttp://localhost:8080/からアクセスできます

アプリケーションのアップロード

 本番環境にアップロードにするには、まずGoogleアカウントにアクセスして、アプリケーションのアカウントを設定してください。
 https://appengine.google.com/にアクセスしてログインすると、アカウントの作成が可能になります。初回のみ、携帯での認証が必要になります。携帯電話のメールアドレスを入力してください。
 その後で以下のコマンドをSDKを解凍したフォルダ/ディレクトリに移動して入力するか、ランチャーのcotrollメニューからdeployを選んでアップロードしてください。
 なおアップロードの際にはパスワードを求められますので、googleアカウントのパスワードを入力してください。

appcfg.py update (アプリケーション名)

webappフレームワークの利用

 Google APP Engineにはwebappと呼ばれる簡単なフレームワークが添付されていて、これを使ってリクエストを処理できます。
 詳しくはwebapp フレームワークを参照してください

"""日本語用のエンコードとPython実行系の指定"""
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""必要なモジュールの取り込み"""
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

"""webapp.RequestHandlerを拡張したクラスを作る"""
class MainPage(webapp.RequestHandler):
    def get(self):"""リクエストメソッドがクラスのメソッドになる"""
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.out.write('Hello, webapp World!')

"""ルーティングの登録(正規表現を使用可能)"""
application = webapp.WSGIApplication([('/', MainPage)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

テンプレートの使用

 Google APP Engineには標準でDjangoというライブラリとしても使えるフレームワークが付属しており、このテンプレートエンジンを使用することができます。

 

"""日本語用のエンコードとPython実行系の指定"""
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""必要なモジュールの取り込み"""
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app
import os
from google.appengine.ext.webapp import template

"""webapp.RequestHandlerを拡張したクラスを作る"""
class MainPage(webapp.RequestHandler):
    def get(self):
    if users.get_current_user():
      url = users.create_logout_url(self.request.uri)
      url_linktext = 'Logout'
    else:
      url = users.create_login_url(self.request.uri)
      url_linktext = 'Login'
    """テンプレート変数の設定"""
    template_values = {
      'greetings': greetings,
      'url': url,
      'url_linktext': url_linktext,
      }
    """テンプレート'index.html'へのパスを取得"""
    path = os.path.join(os.path.dirname(__file__), 'index.html')
    """テンプレートから生成した値を出力する"""
    self.response.out.write(template.render(path, template_values))

"""ルーティングの登録(正規表現を使用可能)"""
application = webapp.WSGIApplication([('/', MainPage)],
                                     debug=True)

def main():
    run_wsgi_app(application)

if __name__ == "__main__":
    main()

 テンプレートファイルの文法などについてはDjangoのマニュアルを参照してください

データストア

 Google APP Engineでは、ファイルへのアクセスは読み出しのみとなっており、データはデータストアに保存されます。データストアには以下の特徴があります

  • データストアはスキマーレス
  • データの設定はgoogle.appengine.ext.dbモジュールにあるModelクラスを継承したクラスのプロパティの形で表現される
    • このクラスは、一般的にモデルと呼ばれている
  • クラス名がそのままデータベースでいうテーブル名に相当するkindになる
  • Modelクラスを継承したクラスのインスタンスがエンティティと呼ばれるデータの一単位
    • データの検索や取得は、コード上ではこのエンティティを得る形になる
  • データの更新・設定はプロパティに値を代入、もしくはインスタンスの作成時にPythonキーワード引数の形で渡す。
  • 保存はput()メソッド
  • 削除はdelete()メソッド

 詳しくはエンティティとモデルを参照してください

"""モジュールをインポート"""
from google.appengine.ext import db

class Pet(db.Model):
"""プロパティの宣言部分。プロパティはプロパティクラスのインスタンス"""
  name = db.StringProperty(required=True)
  type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
  birthdate = db.DateProperty()
  weight_in_pounds = db.IntegerProperty()
  spayed_or_neutered = db.BooleanProperty()
  owner = db.UserProperty(required=True)

pet = Pet(name="Fluffy",
          type="cat",
          owner=users.get_current_user())
pet.weight_in_pounds = 24

pet.put()
データストアのキー

データストアは基本的にキーバリューストアであり、以下のような特徴を持ちます

  • インスタンス作成時にパラメーターkey_nameでキーを設定できる
    • key_nameには以下の制限がある
      • 先頭は数字であってはいけない
      • 500バイト以内
      • __*__(2 つのアンダースコアで始まり、終了する)の形式を取ることもできない
  • key_nameが設定されない場合は連番のidが保存時に設定される
  • key_nameとは別に、キーそのものを表す文字列がある
  • キーそのものはKeyクラスとして表現されている

当然のことながら、パフォーマンス的にはキーでアクセスするのが一番です。

tama = Pet(key_name="Tama",
	   naem="Tama"
	   type="cat",
	   owner=users.get_current_user())
tama.put()
"""tamaをキーから取得"""
pet=Pet.get_by_key_name("Tama")
print pet.name //tama

"""tamaのキーを取得"""
key=tama.key()

"""key_nameを表示"""
print key.name()
"""文字列形式に変換(key_nameとは別物)"""
encoded_key=str(key)
"""文字列形式にしたキーは全kindでユニーク"""
decoded_key=Key(encoded_key)
pet=db.get(decoded_key)
print pet.key().kind()
エンティティグループ

 エンティティを作る際に「親」となる別のエンティティを指定することで、エンティティグループを作ることができます。このエンティグループには以下の特徴があります。

  • 同じエンティティグループに属するエンティティは、論理的、物理的に近い場所に配置される
  • トランザクション時のロックは、エンティティグループ単位で行われる
  • 親にもさらに親を設定できる(これを祖先という)
  • 親をクエリーとして検索できる

 詳しくはキーとエンティティ グループを参照してください

"""Ownerというモデルのエンティティを作る"""
jirou= Owner(name="jirou")
jirou.put()
"""jirouを「親」とするエンティティグループを作る"""
tama = Pet(parent=jirou)

 きわめて単純化していうと、エンティティグループというのはRDMSにおけるテーブル分割やデータベースの分割、分散に相当するものです。Webサービスにおける大規模分散系を考えた場合、もっとも難しいのはデータベースの負荷分散とそれにともなう管理なのですが、それを手軽に実現してくれるのんが、Google App Engineの最大の魅力といえます。

検索と制限

 データストアはクエリーを発行してプロパティの値を元に検索することができます。検索に使える演算子は以下の通りです。

  • < 未満
  • <= 以下
  • = 等しい
  • > より大きい
  • >= 以上
  • != 等しくない
  • IN 指定されたリスト([a,b,c]の形で指定)のいずれかの値に等しい

 以下の制限があります

  • 複数のプロパティに対して不等号クエリ(<、<=、>=、>、!=)をかけられない
  • プロパティを持たないエンティティに一致するフィルタはない
  • 他の並び替え順序より先に、不等式フィルタのプロパティを並び替える必要がある
  • JOINはできない
  • IN句は30件まで
  • Text,Blobにはインデックスが作成されないので検索できない
  • ソート(並べ替え)は他の並び替え順序より先に、不等式フィルタのプロパティを並び替える必要がある。

 詳しくはクエリに関する制限を参照してください

 なおソート、クエリ共に特殊なプロパティ__key__を指定することでエンティティのキーを元に検索、ソートができます。

検索のやり方

 検索をするにはSQLライクなGQLを使うものと、Queryクラスを使うものの2種類があります。ただし、GQLはsqlと違い、取得するカラムを指定できません。
 どちらも発行のやり方は似ていて、直接該当クラスのインスタンスを作るか、モデルからインスタンスを取得した後、同じメソッドを使ってデータを取得します。 

 GQLクエリーはGqlQueryクラスのインスタンスを使って発行します。

import google.appengine.ext.db 
"""GqlQueryクラスのインスタンスを直接作る"""
query = GqlQuery("SELECT * FROM Song WHERE composer = 'Lennon, John'")
"""モデルからGqlQueryクラスのインスタンスを直接作る。SELECT * FROM (モデル名)までを省略可能"""
query = Song.gqll("WHERE composer = 'Lennon, John'")

 詳しくはGQLリファレンスをご覧ください

 GQLに動的に変数を割り当てるには、:の後に番号、またはキーワードを指定します。番号であれば引数の順番に、キーワードであれば先ほど述べたPythonのキーワード引数の形で指定します。

import google.appengine.ext.db 
//"SELECT * FROM Song WHERE composer = 'Lennon, John'"というクエリを発行したい場合

//n番目の引数に、:n(nは番号)で割り当てた条件を入れる
query = db.GqlQuery("SELECT * FROM Song WHERE composer = :1", "Lennon, John")
query.bind("Lennon, John")

//キーワード引数を使う場合は「:(キーワード名)」で指定
query = db.GqlQuery("SELECT * FROM Song WHERE composer = :composer", composer="Lennon, John")
query.bind(composer="Lennon, John")


 Queryクラスは以下のように取得しします。

import google.appengine.ext.db
class Song(db.Model):
  title = db.StringProperty()
  composer = db.StringProperty()
  date = db.DateTimeProperty()

query = db.Query(Song)
query = Song.all()

 Queryクラスはfilterメソッドで検索パラメーターを、orderメソッドで並べ替え順を、ancestorで「親」または祖先要素をそれぞれ指定できます。
 

"""filterは第二引数に演算子を指定するやり方と
第一引数で演算子までまとめて指定する方法の二種類"""
query.filter("title","=","get up!")
"""第一引数でまとめて指定する場合、プロパティ名+半角スペース+演算子で指定"""
query.filter("title =","get up!")
"""演算子を省略すると=を指定したのと同じになる"""
query.filter("title","get up!")
"""並べ替えは基本は昇順。降順にしたい場合はクエリ名の前に-をつける必要がある"""
query.order("-pub_date")

 前述の通りデータの取得はGQL、Queryクラス共に共通で、3つのパターンがあります。

  • getメソッドで一つだけ取得
  • fetchメソッドで複数を取得
  • そのままイテレーターとしてループ
"""getで一つだけ取得"""
entitie=query.get()

"""fetchメソッドで複数を取得"""
entities=query.fetch(limit=10)
for entity in enties:
    ....
"""そのままイテレーターとしてループ"""
for entity in query:
    ....

 なお、最大取得数は1000件ですが、これはoffset分を含みます。これは内部でoffset分だけ空ループしているからです。

インデックス

 検索するにはインデックス(索引)が必要です。
 一部のプロパティ、およびプロパティの設定時に「indexed=False」を指定したプロパティを除いたすべてのクエリに対して保存時に自動的にインデックスが生成されます。

  • 等式、および祖先フィルタのみを使用するクエリ
  • 不等式フィルタのみを使用するクエリ(単一のプロパティを持つもののみ)
  • プロパティに昇順または降順のいずれかの並び替え順序が 1 つだけ設定されているクエリ

 これ以外の場合、app.yamlと同じ場所におくindex.yamlで指定される必要があります。それは、次のような形式のクエリですが、実際には開発サーバで動かしていると自動的にindex.yamlが生成されます。なので、テストの最終段階では何からかの形で開発サーバ経由でアクセスするようにする必要があると思われます。

  • 複数の並び替え順序を持つクエリ
  • キーに降順の並び替え順序の指定されているクエリ
  • 1つのプロパティに対して 1 つ以上の不等式フィルタを持ち、その他のクエリに対して 1 つ以上の等-式フィルタを持つクエリ
  • 不等式フィルタと祖先フィルタを持つクエリ

 詳しくはインデックスの概要インデックスの定義と設定を参照してください。

各種サービス

 Google APP Engineには自由にツール・ライブラリをインストールすることはできませんが、それを補うさまざまな機能があります。Memcashやメール受信アプリケーションがなどが手軽に作れるのも、Google APP Engineの魅力の一つです。 
 なおタスクキューとBlobstoreは実験段階です。

 

定期実行

 Google APP Engineでは、定期実行も設定できます。app.yamlと同じ場所にcron.yamlを置くことで設定でき、定期実行も設定できます。
 定期実行には、以下の制限があります。

  • 最短間隔は1分に一度
  • 登録できるタスクは20件まで

 詳しくはPython 用の cron を使用したスケジュール タスクを参照してください

cron:
- description: daily summary job
  url: /tasks/summary
  schedule: every 24 hours
- description: monday morning mailout
  url: /mail/weekly
  schedule: every monday 09:00
  timezone: Australia/NSW

無料分および課金

 Google APP Engineの料金体系は従量制と課金による枠の拡大の組み合わせですが、無料枠が存在します。
 この無料枠の内、サービスが大きくなる際に真っ先に引っかかりそうなところには以下のようなものがあります。詳しくは割り当て、およびリソースの課金と予算設定を参照してください。
 

  • CPUはインテルx86、1.2Ghz相当の仮想的なCPU一つ分の処理能力を基準にして一日あたり6.5時間相当、1分あたり15分相当まで
  • データストアは1GBまで

 CPU的にいって、さすがに月間500万pvを無料でこなすのはちょっと無理なんじゃないかな? と思います。

 また、送受信のデータ量についてはメールや外部サーバーとの通信の分も含まれるので課金の際に注意が必要です。