アプリケーションの作成
今日まで家族で旅行に行ってました。家族が寝静まってから勉強しようと思っていたんですが、やっぱネットがつながらないとやる気半減ですね。ちょっと本気でモバイルWiFiルータの購入を検討しています。それでは先日の続きです。
アプリケーションの開発
Pythonアプリケーションの開発
先日作成したclockアプリケーションは標準出力に最小限のHTTPレスポンスを返すだけの超シンプルなものでした。今回はApp Engineに標準で備わっているフレームワークwebappを使ってみます。いろいろなところでDjangoやらPylonsなどが紹介されていますが、私はまだwebappしか使ったことがありません。ちなみに本書の最終章ではDjangoを使います。楽しみです。
class MainPage(webapp.RequestHandler): def get(self): # ... application = webapp.WSGIApplication([('/', MainPage)], debug = True) def main(): run_wsgi_app(application) if __name__ == '__main__': main()
※あんまりベタにコードを書いてしまうとまずそうなので、適当にはしょって書いています。
ここではMainPageクラスをURL「/」に関連付けて定義しています。MainPageクラスのget()メソッドはHTTPのGETメソッドに対応しており、HTTPリクエストやレスポンスにアクセスすることが出来ます。基本的な動きとしては、HTTPリクエストのパラメータを取得して、HTTPレスポンスを生成する、というところでしょうね。
ここで少しだけ疑問が残ります 。URLとスクリプトの関係はapp.yamlで定義しています。なぜ、プログラム上でURLとクラスの対応関係を指定する必要があるのでしょうか?たとえばapp.yaml上で以下のように定義したらどうなるのでしょうか?
# ... handlers: - url: /test/.* # ここを変更 script: main.py
あれれ、ダメですね。これはapp.yamlでスクリプトと「/test/」を関連付けているのにスクリプト上では「/」と関連付けているためです。app.yamlで定義したURLとスクリプトの関係がまずあり、その中で枝分かれするURLをプログラム上で指定するのです。なので、このように書くことは可能です。
application = webapp.WSGIApplication([('/test', MainPage)], debug = True)
※app.yamlは元に戻します。
app.yamlでの指定はワイルドカードになっているので、何でもOK。その状況でスクリプト内で対応関係を指定するのです。こうするとhttp://localhost:8080/testにアクセスすることが出来ます。
次にユーザごとに時刻表示を切り替えます。こんな感じでHTTPリクエストを出したユーザがGoogleアカウントにログインしているかどうかをチェック出来ます。
user = users.get_current_user() if not user: # ログインしていない時の処理 else: # ログインしている時の処理
Googleアカウントを使うとこんな簡単にユーザ管理をすることが出来てしまいます。
さらにユーザごとにタイムゾーンの設定を保存します。保存にはもちろんデータストアを使います。まずはデータストアに保存するエンティティの種別を定義します。
class UserPrefs(db.Model): # タイムゾーン設定 (オフセット) tz_offset = db.IntegerProperty(default=0) # ユーザ情報 user = db.UserProperty(auto_current_user_add=True)
db.Modelクラスのサブクラスとして種別を定義することでこのクラスのインスタンスがエンティティにマッピングされます。もちろんクラスのプロパティがエンティティのプロパティに当たります。このクラスを使ってデータストアからユーザに紐づいたタイムゾーンの設定を取り出します。
# エンティティ種別名とユーザIDからKeyを生成 key = db.Key.from_path('UserPrefs', user_id) # ユーザIDに紐づいたタイムゾーン設定を取得 (Fetch) userprefs = db.get(key) # もしデータストアから取得出来なければ if not userprefs: # エンティティを生成する userprefs = UserPrefs(key_name=user_id)
データストアに保存されるエンティティには固有のID (Keyと言う) が割り振られます。自分で割り振ることも出来れば、App Engineに自動で割り振ってもらうことも可能です。↑の例ではuser_idをKeyにしてデータストアから読み込みますが、見つからなければ新たにエンティティモデルを生成しています。なお、データストアから情報を取得する方法には、上述のようにKeyを指定して取得する方法と、クエリ (条件などを付ける) を発行して取得する方法がありますが、Keyを指定する方法の方がはるかに高速なのです。元々のBigTableがそのような構造になっていますからね。
そしてタイムゾーンを設定するためのFormを作成します。本書のとおり作成すると次のようにユーザログイン後、Formを表示して設定を促します。
このFormはPOSTメソッドを使ってSubmitします。ですのでこのPOSTに対するRequestHandlerを定義する必要があります。ここでは新たにスクリプトを作成して、その中でRequestHandlerを定義しています。
class PrefsPage(webapp.RequestHandler): def post(self): userprefs = models.get_userprefs() # ... userprefs.put() # ... self.redirect('/')
エンティティ (db.Modelのサブクラスのインスタンス) のput()メソッドで、このエンティティをデータストアに保存します。最後に「/」にリダイレクトしています。そしてこのRequestHandlerを「/prefs」に関連付けます。もちろんapp.yamlを編集します。
# ... handlers: - url: /prefs script: prefs.py login: required # 本アドレスにアクセスするためにはGoogle Accountにログインしていることが必要
最後に常にユーザ設定をデータストアから読み出すのではなく、メモリにキャッシュする方法を導入します。以前も書いたようにApp Engine上のアプリは異なるHTTPリクエストが同じサーバ上で実行される保証はありません (その時々で最適なサーバが選ばれます) 。そのため、グローバル領域に情報を保存していても意味がありません。なのでデータストアなどを使って情報を永続化するのですが、何もかもデータストアに保存しないといけないのはちょっと使いにくい (そもそもデータストアは遅いのでアプリの状態などを保存するには適さないのです) 。そこで登場するのがmemcacheです。memcacheを使うとデータを (データストアではなく) メモリ上にキャッシュすることが出来ます。もちろん異なるHTTPリクエスト間で共有することが出来ます。
class UserPrefs(db.Model): # ... def cache_set(self): memcache.Client().set(self.key().name(), self, namespace=self.key().kind()) def put(self): self.cache_set() db.Model.put(self)
ここでは先ほど登場したエンティティの種別クラスにメソッドを追加しています。一つはcache_setメソッド。これがmemcacheにエンティティをキャッシュする処理の本体です。次にputメソッド。こちらはdb.Modelクラスのputメソッドをオーバーライドしてデータストアに保存する際は必ずキャッシュするようにしています。後はエンティティをputすればキャッシングされますので、エンティティを取り出す処理をちょっと変更してやればOKです。
def get_userprefs(user_id=None): # ... userprefs = memcache.Client().get(user_id, namespace='UserPrefs') if not userprefs: # キャッシュにエンティティがない場合の処理 return userprefs
開発コンソール
開発用サーバには開発を行う上で便利な各種ツールを持っています。まずは以下のURLにアクセスしてみましょう。
http://localhost:8080/_ah/admin
ここからデータストアやメモリキャッシュの状況を見たりすることが出来ます。またInteractive Consoleでは直接Pythonのコードを実行することが可能です。
これは強力なツールなのですが、(当然ですが) コード補完はしてくれません (当たり前ですが・・・) 。