Google App Engine - Python Tutorial を和訳してみた

  • 117
    いいね
  • 5
    コメント

Google App EngineのPython TutorialのIntroductionを和訳しました。
誤訳・誤植の修正はご遠慮なく編集リクエストを送りつけてください。
原文ページ

はじめに

原文ページはこちら

ようこそ Google App Engine へ! App Engine アプリを作るのは簡単で,数分しか掛かりません。そして自由に初められますし,アップロードしたアプリはすぐにユーザと共有できて,無償で契約も必要ありません。

Google App Engine アプリケーションは Python2.7,Java,Go,PHP のいずれかのプログラミング言語で書けます。このチュートリアルは Python 2.7 が対象です。もし Java や Go,PHP でアプリケーションを組みたい場合は,JavaGoPHP のガイドを見てください。

このチュートリアルで次の方法が学べます。

  • App Engine アプリケーションをPythonで組む
  • webapp2 というアプリケーション・フレームワークを使う
  • App Engine データストアを Python モデリング API で扱う
  • App Engine アプリケーションと Google アカウントをユーザ認証のために統合する
  • Jinja2 テンプレートをアプリに適用する
  • アプリを App Engine にアップロードする

チュートリアルを終える頃には,実装したアプリケーションはユーザが公開掲示板にメッセージを書き込める簡単なゲストブックになります。

次は…

Google App Engine アプリ開発を始めるために,まず App Engine の Software Development Kit をダウンロード&セットアップします。

続きは「開発環境構築」で。

開発環境構築

原文ページはこちら

Google App Engine による Python アプリケーションの開発とアップロードには, Software Development Kit (SDK) を使います。

Python SDK に入っているのは,仮想的な App Engine 環境を提供する Web サーバ,ローカル用のデータストア,グーグルアカウント,それにURLの取得やコンピュータから直接 email の送信ができる App Engine の API です。Python SDK は Python 2.7 が入っているコンピュータで動作し,Windows ,Mac OS X と Linux の各 OS 向けのバージョンが提供されています。(この Python SDK は Python 3 に互換性が無いから注意してください。)

Windows と Mac 向けの Python SDK に入っている Google App Engine Launcher は,お使いのコンピュータで動作して多くの共通した App Engine 開発作業を簡素化する GUI を提供してくれるアプリケーションです。

必要であれば,自分の環境に Python 2.7 を Python の Web サイトからダウンロード&インストールしてください。Mac OS X 10.7 Lion のユーザなら Python 2.7 はインストール済みです。

Python 用 App Engine SDK を自分の OS にダウンロード&インストールしてください。

ここまでのチュートリアルで,2つのコマンドが SDK から利用できるはずです。

これらのコマンドはコマンドラインからアクセス可能になっています。

  • Windows ユーザ :Windows インストーラは環境変数にこれらのコマンドのパスを登録します。インストール後,これらのコマンドがコマンドプロンプトから利用可能になっているはずです。また,Python のインストールフォルダのパスを指定する必要があるので,メニューの Edit > Preference から python.exe絶対パス を入力してください。
  • Mac ユーザGoogleAppEngineLauncher のメニューから Make Symlinks... (シンボリックの作成)を選択することで,コマンドのパスを追加できます。あと,SDK のインストーラおよび/または Google App Engine Launcher の実行を手動で承認する必要があるかもしれません。

Zip アーカイブ版 SDK を使用する場合,これらのコマンドを google_appengine ディレクトリから探す必要があります。

注: コマンドラインからコマンドを使用しない場合,Windows と Mac ユーザは Google App Engine Launcher を起動して,SDKに含まれるアプリケーションをGUIで実行できます。 Google App Engine Launcher 起動後, Run または Deploy ボタンをクリックすることで,上記のコマンドの代わりに利用できます。

次は...

ローカルな開発環境では,世界に公開する前に完全な App Engine アプリケーションを開発,テストすることができます。それでは,いくつかコードを書いてみましょう。
続きは「Hello World!」で。

Hello, World!

原文ページはこちら

それでは短いメッセージを表示する小さなアプリケーションの実装を始めましょう。

簡単なリクエスト ハンドラの作成

helloworld という名前のディレクトリを作ってください。このアプリケーションのすべてのファイルをこのディレクトリに存在させます。

helloworld ディレクトリの中に helloworld.py という名前のファイルを作り,中身を次のようにします。

helloworld.py
import webapp2


class MainPage(webapp2.RequestHandler):

    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')


application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

この Python スクリプトはリクエストに対して Hello, World! というメッセージを記述する HTTP ヘッダを返します。

注: 作ったファイルがプレーンテキストで保存されているか注意してください。そうでなければエラーが発生する場合があります。

設定ファイルの作成

App Engine アプリケーションには app.yaml と呼ばれる設定ファイルがあります。とりわけ,このファイルではハンドラのスクリプトがどのURLを使用するかを記述します。

helloworld ディレクトリの中に, app.yaml という名前のファイルを作り中身を次のようにします。

app.yaml
application: your-app-id
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /.*
  script: helloworld.application

上から順に,この設定ファイルはアプリケーションについて次のことを言っています。

  • このアプリケーションの ID が your-app-id です 。すべての App Engine 上の新しいアプリケーションは唯一のアプリケーション識別子を持ちます。後の段階でアプリケーションのIDを登録したときにそのIDを選びます。ローカル開発時は重要ではないので,your-app-id のままにしておいて結構です。
  • このアプリケーションのコードのバージョン数が 1 です。新しいバージョンのアップロード後にアプリケーションを調整したい場合,App Engine は以前のバージョンを保存してくれて管理コンソールを使うことでロールバックできます。
  • このコードは python27 のランタイム環境で動作していて,APIのバージョンが 1 です。追加のランタイム環境およびプログラミング言語が将来的にサポートされる予定です。
  • このアプリケーションは threadsafe(スレッドセーフ) で同じインスタンスが複数の要求を同時に処理できます。スレッドセーフは高度な機能で,アプリケーションが具体的にスレッドセーフになる設計でない場合,異常な動作が発生することがあります。
  • /.* という正規表現に一致するパスのURL(すべてのURL)へすべてのリクエストは helloworld モジュールの application オブジェクトに振り分けられます。

このファイルの文法は YAML です。設定項目の完全な一覧については,app.yaml リファレンスを見てください。

訳者注: app.yaml の文字コードが UTF-8(No-Bom) ,インデントが 半角スペース2つ分 であることを確認してください。アプリケーションの実行時エラーを回避することができます。

アプリケーションのテスト

ハンドラのスクリプトとすべてのURLをハンドラに対応づける設定ファイルで,アプリケーションは完成です。App Engine SDK に入っている Web サーバで今テストできます。

Google App Engine Launcher を使う場合,アプリケーションの設定として File メニューの Add Existing Application... を選んで helloworld ディレクトリを選んでください。アプリ一覧からアプリケーションを選んだら,Run ボタンをクリックしてアプリケーションを開始して,Browse ボタンをクリックしてみましょう。Browse ボタンをクリックするとデフォルトの Web ブラウザで http://localhost:8080/ を単純に表示(またはリロード)します。

Google App Engine Launcher を使わない場合,次のようなコマンドで Web サーバを立ち上げて, helloworld ディレクトリのパスを与えよう。

google_appengine/dev_appserver.py helloworld/

この Web サーバが実行しているとき,ポート 8080 へのリクエストを受信します。ブラウザから次のようなURLに繋いでアプリケーションをテストできます。

http://localhost:8080/

開発用 Web サーバの実行の詳細は,使用ポート番号の変更方法を含めて,Python による開発用 Web サーバのリファレンスを見るか,コマンドを --help オプション付きで実行してください。

反復的な開発

Web サーバを実行したままアプリケーションの開発ができます。Web サーバがソースファイルの変更を監視して,必要に応じてリロードするようになっているからです。

やってみよう:Web サーバを動かしたまま, helloworld.pyHello, World! を何か他のメッセージに変えてみてください。http://localhost:8080/ を更新するか Google App Engine Launcher の Browse をクリックして変更を観察してみましょう。

Web サーバをシャットダウンするには,ターミナルのウィンドウがアクティブ(選択されている)なことを確認してから,Ctrl - C (あるいはコンソールで "break" に相当するキー)を押すか,Google App Engine Launcher の Stop ボタンをクリックしてください。

このチュートリアルの残りのために引き続きサーバを実行したままにできます。もし止める必要がある場合,上記のコマンドを実行してもう一度再起動できます。

次は...

App Engine アプリケーションが完成しました!この簡単な挨拶アプリをデプロイして今すぐ世界中のユーザと共有可能です。でも,デプロイする前に webapp2 フレームワークを詳しく見てもっといくつかの興味深い機能を追加してみましょう。
続きは「webapp2 フレームワークの説明」で。

webapp2 フレームワークの説明

原文ページはこちら

WSGI 規格はシンプルですが,全てのコードを手で書くのは面倒になることでしょう。Web アプリケーション フレームワークがこういう細かい事を扱う代わりをしてくれるので,アプリケーション機能の開発に神経を集中するだけでよくなります。Google App Engine はDjangoCherryPyPylonsweb.py そして web2py を含めた WSGI を話す純粋な Python で書かれたフレームワークをサポートしています。必要なコードを自分のアプリケーションのディレクトリの中にコピーすることで,好きなフレームワークをバンドル(筆者注:フレームワークの使用準備を完了させること)できてしまうのです。

App Engine には webapp2 と呼ばれる Web アプリケーション フレームワークが入っています。webapp2 フレームワークは App Engine 環境と SDK に既にインストール済みで,アプリケーションで使う時に用意する必要はありません。ここからのチュートリアルでは webapp2 を使っていきます。

Hello, webapp2!

webapp2 アプリケーションは2つの部分に分かれます。

  • 1つまたは複数のリクエストを処理してレスポンスを生成する RequestHandler クラス
  • 受信したリクエストを URL に基いてハンドラにルーティングする WSGI アプリケーションのインスタンス

それではもう一度おなじみの挨拶アプリケーションを見てみましょう。

helloworld.py
import webapp2


class MainPage(webapp2.RequestHandler):

    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')


application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

webapp2 がやること

このコードはリクエストハンドラの 1 つである MainPage を定義していて,ルートな URL ( / ) に対応づけています。webapp2 が URL / に対する HTTP GET リクエストを受け取ったとき,MainPage クラスをインスタンス化してインスタンスの get メソッドを呼びます。メソッドの中では,リクエストに関する情報が self.request を通して知ることができます。

通常,このメソッドはレスポンスの準備をする self.response にプロパティを設定して終了します。webapp2 はMainPage インスタンスの最終状態に基づいてレスポンスを送信します。

アプリケーション自身は webapp2.WSGIApplication インスタンスで表現されます。そのコンストラクタに渡されるパラメータ debug=true はハンドラにエラーが発生したかキャッチされない例外が発生した場合,webapp2 にブラウザ出力へのスタックトレースの発行を指示します。

この後のチュートリアルでさらにいくつかの webapp2 の機能を使用します。webapp2 の詳細については,webapp2 のドキュメントを見てください。

次は...

フレームワークは Web アプリケーション開発をより易しくより速くしてエラーの発生を抑えてくれます。webapp2 は多くのフレームワークの中でただひとつの Python を利用可能なものです。

続きは「ユーザーサービスの利用」で。

ユーザーサービスの利用

原文ページはこちら

Google App Engine はいくつかの便利なサービスを Google のインフラに基いて提供し,アプリケーションはSDKに含まれるライブラリを通して利用可能です。そのようなサービスのひとつにはユーザーサービスがあって,君のアプリケーションに Google のユーザアカウントを統合できます。ユーザーサービスを使えば,アプリのユーザーはアプリケーションにサインインする Google アカウントを利用できます。

それでは,ユーザーサービスをこのアプリケーションの挨拶を個別化するために使ってみましょう。

ユーザーの使用

helloworld/helloworld.py をもう一度編集して,中身を次のように置き換えてみてください。

helloworld.py
from google.appengine.api import users

import webapp2


class MainPage(webapp2.RequestHandler):

    def get(self):
        user = users.get_current_user()

        if user:
            self.response.headers['Content-Type'] = 'text/plain'
            self.response.write('Hello, ' + user.nickname())
        else:
            self.redirect(users.create_login_url(self.request.uri))


application = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

ブラウザのページを更新してください。アプリケーションはテストするのに適したローカル版の Google のサインインのページにリダイレクトしてくれます。君は好きなユーザ名をこの画面で入力でき,アプリケーションは仮のユーザをユーザ名に基いて表示してくれます。

アプリケーションが App Engine で動くときは,ユーザは Google アカウントのサインインのページにリダイレクトされて,サインインに成功するかアカウントを作成したあとにアプリケーションにコールバックします。

Users API

そうしたら新しい部分を見ていきましょう。

user = users.get_current_user()

もしユーザが既にサインイン済みなら,get_current_user() は User オブジェクトをユーザに返します。そうでなければ None を返します。

if user:
    self.response.headers['Content-Type'] = 'text/plain'
    self.response.write('Hello, ' + user.nickname())

もしユーザがサインイン済みなら,ユーザアカウントに紐付けられたニックネームを使用して個別のメッセージを表示します。

else:
   self.redirect(users.create_login_url(self.request.uri))

もしユーザがサインインしていなければ,webapp2 にユーザのブラウザを Google アカウントのサインイン画面にリダイレクトするように伝えます。リダイレクトにはそれ自身のページ ( self.request.uri ) への URL が含まれ,Google アカウントのサインイン機構はユーザがサインインもしくは新規アカウントを登録後ここに送ります。

Users API の詳細は,Users リファレンスを見てください。

次は...

いま私たちのアプリケーションは名前でアクセスするユーザに挨拶できるようになりました。そうしたら,ユーザがお互いに挨拶できる機能を追加してみましょう。

続きは「webapp2でフォームを扱う」で。

webapp2 でフォームを扱う

原文ページはこちら

ユーザが自分で挨拶を投稿できるようにしたい場合,Web フォームを通じてユーザが送信した情報を処理する方法が必要になります。webapp2 フレームワークはフォームデータの処理を簡単にしてくれます。

Hello World からゲストブックへ

これまで作成した Hello World アプリを準備するために,次のように変更しましょう。

  • 一番上の helloworld のディレクトリを guestbook にリネームしてください。
  • helloworld.py を guestbook.py にリネームしてください。
  • app.yaml の handlers の項目を以下に置き換えてください。
app.yaml
handlers:
- url: /.*
  script: guestbook.application

開発用サーバを再起動して新しい guestbook ディレクトリを使っていこう。

webapp2 で Web フォームを扱う

app.yaml の libraries の項目にこれを追加して webapp2 の使用を宣言してください。

app.yaml
libraries:
- name: webapp2
  version: latest

guestbook/guestbook.py の内容を次のように置き換えてください。

guestbook.py
import cgi

from google.appengine.api import users

import webapp2


MAIN_PAGE_HTML = """\
<html>
  <body>
    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>
  </body>
</html>
"""


class MainPage(webapp2.RequestHandler):

    def get(self):
        self.response.write(MAIN_PAGE_HTML)


class Guestbook(webapp2.RequestHandler):

    def post(self):
        self.response.write('<html><body>You wrote:<pre>')
        self.response.write(cgi.escape(self.request.get('content')))
        self.response.write('</pre></body></html>')


application = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', Guestbook),
], debug=True)

フォームを表示するにはページを更新して,メッセージを送ってみてみましょう。

このバージョンは2つのハンドラを持ちます。MainPage は URL / に対応して Web フォームを表示します。Guestbook は URL /sign に対応してWeb フォームによって送信されたデータを表示します。

Guestbook ハンドラは post() メソッドを get() メソッドの代わりに持ちます。これは MainPage 表示されるフォームが HTTP POST メソッド (method="post") を使用してフォームデータを送信していることによるものです。何らかの理由でひとつのハンドラが GET と POST の両方を同じ URLで 扱うとき,同じクラスの中で互いに違う動きのメソッドを定義できます。

post() メソッドによるコードは self.request からのフォームデータを取得します。それがユーザに表示される前に,cgi.escape() によって HTML の特殊記号を実体参照文字にエスケープします。cgi は Python の標準ライブラリで,詳しくは cgi のドキュメンテーションを読んでください。

注:App Engine の環境は Python 2.7 の標準ライブラリを包括しています。しかし,すべての動作が許可されているわけではありません。App Engine アプリケーションは App Engine が安全に拡張できる制限された環境で実行されます。たとえば,低レイヤで呼ばれる OS,ネットワークとファイルシステムは許可されておらず,試すとエラーが発生します。詳細は Python ランタイム環境を見てください。

次は...

いま私達はユーザから収集できる情報を保管する場所とそれを取り戻す方法が必要です。

続きは「データストアの利用」で。

データストアの利用

原文ページはこちら

拡張可能な Web アプリケーションでのデータの保管は厄介です。ユーザは所定の時間に数十台の Web サーバのいずれかと対話して,ユーザの次のリクエストは直前のリクエストと異なるサーバに届くでしょう。すべての Web サーバが恐らくは世界中の様々な場所にある数十台のマシン間に分散されたデータと対話する必要があります。

Google App Engne では,そんな心配をする必要はありません。App Engine のインフラは全ての配布形態とレプリケーションを処理し,単純な API の裏でデータを負荷分散します。強力なクエリエンジンとトランザクションを手にしましょう。

App Engineのデータリポジトリであるハイレプリケーションデータストア(HRD)は Paxos アルゴリズムを用いて複数のデータセンター間でデータのレプリケーションを行います。データはデータストア内のエンティティと呼ばれるオブジェクトへ書き込まれ,各エンティティはユニークな識別子となるキーを持ちます。エンティティは必要に応じて別のエンティティを親に指定でき,最初のエンティティは親エンティティの子です。データストアのエンティティはこのように階層構造をとっていて(訳者注:"space"が謎),ファイルシステムのディレクトリ構造に似ています。エンティティの親,親の親などは再帰的に先祖となり,子,子の子などは子孫となります。

データストアは致命的な障害に直面しても非常に弾力性がありますが,その整合性の保障は貴方の精通しているものとは異なる場合があります。共通の祖先からの子孫エンティティは同じエンティティのグループに属すると言われていて,子孫で共通するキーはグループの親のキーで,それはグループ全体を特定するのに役立ちます(訳者注:親のキーと親の子孫グループのキーが同一ということ)。単一のエンティティグループに対する問い合わせは先祖クエリと呼ばれ,特定のエンティティのキーの代わりに親のキーを参照します。エンティティのグループは整合性とトランザクション性の両方の単位です。複数のエンティティグループに対する問い合わせは古くとも最終的な整合性を持つ結果を返す場合がある一方で(訳者注:"stale"の意味が取れない),それは単一のエンティティグループに限られたものは常に最新の強い整合性を持つ結果を返す。

このガイドのコードサンプルはエンティティグループに関連するエンティティを整理して,それらのエンティティグループの先祖問い合わせを強い整合性のある結果を返すために使います。サンプルコードのコメントでは,いくつかアプリケーションの設計に影響を与える可能性のある,いくつかの方法を強調しています(訳者注:"this"は"that"の誤植と判断)。詳細は,強い整合性のあるデータ構築を見てください。

データストアを利用した完全な例

ここに新しいバージョンの guestbook/guestbook.py があり,ページのフッターにデータストアの挨拶を格納しています。残りのページでは新しいことを議論します。

guestbook.py
import cgi
import urllib

from google.appengine.api import users
from google.appengine.ext import ndb

import webapp2


MAIN_PAGE_FOOTER_TEMPLATE = """\
    <form action="/sign?%s" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>

    <hr>

    <form>Guestbook name:
      <input value="%s" name="guestbook_name">
      <input type="submit" value="switch">
    </form>

    <a href="%s">%s</a>

  </body>
</html>
"""

DEFAULT_GUESTBOOK_NAME = 'default_guestbook'


# We set a parent key on the 'Greetings' to ensure that they are all in the same
# entity group. Queries across the single entity group will be consistent.
# However, the write rate should be limited to ~1/second.

def guestbook_key(guestbook_name=DEFAULT_GUESTBOOK_NAME):
    """Constructs a Datastore key for a Guestbook entity with guestbook_name."""
    return ndb.Key('Guestbook', guestbook_name)


class Greeting(ndb.Model):
    """Models an individual Guestbook entry with author, content, and date."""
    author = ndb.UserProperty()
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)


class MainPage(webapp2.RequestHandler):

    def get(self):
        self.response.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)

        # Ancestor Queries, as shown here, are strongly consistent with the High
        # Replication Datastore. Queries that span entity groups are eventually
        # consistent. If we omitted the ancestor from this query there would be
        # a slight chance that Greeting that had just been written would not
        # show up in a query.
        greetings_query = Greeting.query(
            ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
        greetings = greetings_query.fetch(10)

        for greeting in greetings:
            if greeting.author:
                self.response.write(
                        '<b>%s</b> wrote:' % greeting.author.nickname())
            else:
                self.response.write('An anonymous person wrote:')
            self.response.write('<blockquote>%s</blockquote>' %
                                cgi.escape(greeting.content))

        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'

        # Write the submission form and the footer of the page
        sign_query_params = urllib.urlencode({'guestbook_name': guestbook_name})
        self.response.write(MAIN_PAGE_FOOTER_TEMPLATE %
                            (sign_query_params, cgi.escape(guestbook_name),
                             url, url_linktext))


class Guestbook(webapp2.RequestHandler):

    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each Greeting
        # is in the same entity group. Queries across the single entity group
        # will be consistent. However, the write rate to a single entity group
        # should be limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user()

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))


application = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', Guestbook),
], debug=True)

guestbook/guestbook.py をこれと置き換えて,お使いのブラウザで http://localhost:8080/ をリロードしてください。メッセージが正しく保存,表示されるのを確認するために短いメッセージを投稿してください。

警告! ローカルでのアプリケーション内の問い合わせの実行は App Engine に index.yaml を作成または更新させます。もし index.yaml が欠けているか不完全な場合,アップロードしたアプリケーションが問い合わせを実行するとき,必要なインデックスが指定されていないためにインデックスエラーが出ます。制作中にインデックスエラーを見逃さないように,常に新しい問い合わせを最低1回ローカルでテストしてからアプリケーションをアップロードしましょう。詳細は Python データストアインデックス設定を見てください。

送信した挨拶の保存

App Engine は Python 用のデータモデリング API が入っています。それは Django のデータモデリング API と似ていますが,背後では App Engine の拡張可能なデータストアを利用しています。

ゲストブックアプリケーションで,ユーザが投稿した挨拶を保存したいです。各挨拶は投稿者名,メッセージ内容,日付と時間を含んで投稿され,時系列にメッセージを表示可能です。

データモデリング API を使うには,google.appengine.ext.ndb モジュールをインポートしてください。

?from google.appengine.ext import ndb

次のように挨拶のデータモデルを定義します。

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with author, content, and date."""
    author = ndb.UserProperty()
    content = ndb.StringProperty(indexed=False)
    date = ndb.DateTimeProperty(auto_now_add=True)

これは3つのプロパティと共に Greeting モデルを定義します。author の値は string,content の値は string,date の値は datetime.datetime です。

いくつかのプロパティのコンストラクタは追加の動作設定のために引数をとります。ndb.StringProperty のコンストラクタに indexed=False の引数を与えるとプロパティの値はインデックスされなくなります。このようにすることで、そのプロパティはクエリで使われなくなるので、不要な書き込みが省けます。ndb.DateTimeProperty のコンストラクタに auto_now_add=True のパラメータを与えると,アプリケーションが値を与えない場合,モデルが新しいオブジェクトにオブジェクト作成時のタイムスタンプ datetime を自動で与えるよう設定します。完全なプロパティの種類とオプションの一覧については,NDB プロパティを見てください。

いま挨拶のデータモデルを持ち,アプリケーションは新しい Greeting オブジェクトを作るモデルを使用でき,データストアに保存できます。次にある新しいバージョンの Guestbook ハンドラは新しい挨拶を作ってデータストアに保存します。

class Guestbook(webapp2.RequestHandler):

    def post(self):
        # We set the same parent key on the 'Greeting' to ensure each Greeting
        # is in the same entity group. Queries across the single entity group
        # will be consistent. However, the write rate to a single entity group
        # should be limited to ~1/second.
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greeting = Greeting(parent=guestbook_key(guestbook_name))

        if users.get_current_user():
            greeting.author = users.get_current_user()

        greeting.content = self.request.get('content')
        greeting.put()

        query_params = {'guestbook_name': guestbook_name}
        self.redirect('/?' + urllib.urlencode(query_params))

この新しい Guestbook ハンドラは新しい Greeting オブジェクトを作って,author と content プロパティをユーザが投稿したデータと共に設定します。Greeting の親は Guestbook エンティティです。別の親エンティティを設定する前に Guestbook エンティティを作る必要はありません。この例では,親はプレースホルダとして使われ,トランザクションと整合性が目的です。トランザクションのページに詳細があります。共通の先祖を共有するオブジェクトは同じエンティティグループに属します。それは date プロパティを設定しないので,上記のように構成した auto_now_add=True を使用することで date は自動的に親を設定します。

最後ですが,greeting.put()は新しいオブジェクトをデータストアに保存します。問い合わせからこのオブジェクトを取得すると,put()は既存のオブジェクトを更新します。オブジェクトをモデルのコンストラクタと共に作るので,put()は新しいオブジェクトをデータストアに追加します。

ハイレプリケーションデータストアの問い合わせは強い整合性をエンティティグループでのみ持つので,すべてのゲストブックの挨拶を同じエンティティグループに,この例ではそれぞれの挨拶に同じ親を設定することで割り当てている。これは,ユーザが常に挨拶を書き込んだ直後に表示していることを意味します。しかし,同じエンティティグループに書き込める速さは毎秒1つのエンティティグループへの書き込みで制限されています。本当のアプリケーションを設計する際は,この事に留意する必要があります。Memcache のようなサービスを使うことで,書き込み直後のエンティティグループ間の問い合わせ時にユーザが新鮮な結果を見られない現象を軽減できることに注意してください。

送信した挨拶の検索

App Engine のデータストアはデータモデル用に洗練されたクエリエンジンがあります。App Engine のデータストアは伝統的なリレーショナル・データベースではないため,問い合わせに SQL の使用は指定されていません。その代わり,データは2つの方法のいずれかで問い合わせします。データストア問い合わせの経由,または,GQL と呼ばれる SQL ライクな問い合わせ言語です。データストアの問い合わせ機能の全てを呼び出すには,GOL によるデータストア問い合わせを推奨します。

MainPage ハンドラは以前送信した挨拶を検索,表示します。データストア問い合わせはここで起こります。

?greetings_query = Greeting.query(
    ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
greetings = greetings_query.fetch(10)

データストアインデックスに関する用語

すべての App Engine のデータストア内の問い合わせは1つまたは2つのインデックスから計算されています。インデックスはエンティティのキーにプロパティの値を順番づけたマップのテーブルです。これは App Engine が結果を素早くアプリケーションのデータストアの大きさに関係なく提供できる方法です。多くの問い合わせは組み込みのインデックスから計算可能ですが,データストアはいくらか複雑な問い合わせ用のカスタムインデックスの指定を求めます。カスタムインデックス無しでは,データストアはそれらの問い合わせを効率良く実行できません。

以下のゲストブックの例は,ゲストブックによるフィルタリングと date によるソート,先祖クエリとソート順序の使用です。この問い合わせはアプリケーションの index.yaml ファイルで指定されたカスタムインデックスを要求します。アプリケーションをアップロードする際は,カスタムインデックスの定義も自動的にアップロードされます。この問い合わせのために index.yaml ファイルに項目を次のように追加する必要があります。

index.yaml
indexes:
- kind: Greeting
  ancestor: yes
  properties:
  - name: date
    direction: desc

データストアインデックスのすべてについてはデータストアインデックスページで読めます。index.yaml ファイルの適切な仕様についてはPython データストアインデックスの設定で読めます。

次は...

いま動いているゲストブックアプリケーションは Google アカウントでユーザを認証してメッセージを送信させ,他のユーザが残したメッセージを表示します。App Engine は自動的にスケーリング処理するので,アプリケーションが普及するにつれてコードを再検討する必要はありません。

最新バージョンは MainPage ハンドラのコードが HTML コンテンツと一緒になっています。それはアプリケーションの外観の変更が,特にアプリケーションが大きく複雑になるにつれて,難しくなります。

続きは「テンプレートの利用」で。

テンプレートの利用

原文ページはこちら

コードが埋め込まれた HTML は乱雑でメンテナンスを難しくします。テンプレートシステムを利用するのが良く,HTML は別のファイルで保存して,特別な構文を使用することでアプリケーションからのデータが現れる場所を示します。多くのPython用テンプレートシステムがあり,EZTCheetahClearSilverQuixoteDjangoJinja2はごく一部です。

便利なことに,App Engine は Django と Jinja2 テンプレートエンジンが入っています。

Jinja2 テンプレートの利用

まず guestbook/app.yaml の一番下にある libraries の項目を修正します。

app.yaml
libraries:
- name: webapp2
  version: latest
- name: jinja2
  version: latest

この設定は最新のサポートバージョンの Jinja2 をアプリケーションで利用可能にします。互換性の問題をできるだけ避けるなら,深刻なアプリケーションほど latest よりも実際のバージョン番号を用いるべきです。

さて guestbook/guestbook.py の一番上の文を修正します。

guestbook.py
import os
import urllib

from google.appengine.api import users
from google.appengine.ext import ndb

import jinja2
import webapp2


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

MainPage ハンドラを次のようなコードに置き換えます。

guestbook.py
class MainPage(webapp2.RequestHandler):

    def get(self):
        guestbook_name = self.request.get('guestbook_name',
                                          DEFAULT_GUESTBOOK_NAME)
        greetings_query = Greeting.query(
            ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
        greetings = greetings_query.fetch(10)

        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,
            'guestbook_name': urllib.quote_plus(guestbook_name),
            'url': url,
            'url_linktext': url_linktext,
        }

        template = JINJA_ENVIRONMENT.get_template('index.html')
        self.response.write(template.render(template_values))

最後に,新しいファイルを guestbook ディレクトリに作って index.html と名づけ,次のような内容にします。

index.html
<!DOCTYPE html>
{% autoescape true %}
<html>
  <body>
    {% for greeting in greetings %}
      {% if greeting.author %}
        <b>{{ greeting.author.nickname() }}</b> wrote:
      {% else %}
       An anonymous person wrote:
      {% endif %}
      <blockquote>{{ greeting.content }}</blockquote>
    {% endfor %}

    <form action="/sign?guestbook_name={{ guestbook_name }}" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Sign Guestbook"></div>
    </form>

    <hr>

    <form>Guestbook name:
      <input value="{{ guestbook_name }}" name="guestbook_name">
      <input type="submit" value="switch">
    </form>

    <a href="{{ url|safe }}">{{ url_linktext }}</a>

  </body>
</html>
{% endautoescape %}

ページを更新して,使ってみてください。

JINJA_ENVIRONMENT.get_template(name)はテンプレートファイル名を取得して,テンプレートのオブジェクトを返します。template.render(template_values)は辞書の値を取得して,レンダリングされたテキストを返します。テンプレートは Jinja2 テンプレート構文を値に対する呼び出しとイテレートに用いて,それらの値のプロパティを参照できます。多くの場合,データストアモデルのオブジェクトを直接の値として渡して,テンプレートからプロパティを呼び出せます。

豆知識:App Engine アプリケーションは読み取り専用でアップロードされたプロジェクト,ライブラリモジュール,それ以外のファイルの全てにアクセスできます。現在の作業ディレクトリはアプリケーションのルート・ディレクトリで,index.html へのパスはシンプルに"index.html"です。

次は...

すべてのWebアプリケーションは動的に生成されたHTMLをテンプレートや何か別の仕組みを通してアプリケーションのコードから返します。多くのWebアプリケーションは画像,CSSスタイルシート,JavaScriptファイルのような静的なコンテンツを与える必要があります。App Engineの静的ファイルの機能によってCSSスタイルシートをアプリケーションに提供できます。

続きは「静的なファイルの利用」で。

静的なファイルの利用

原文ページはこちら

伝統的な Web ホスティング環境とは異なり,Google App Engine はアプリケーションのソースディレクトリの外では,そのように設定しない限り,ファイルを直接提供しません。テンプレートファイルを index.html と名づけましたが,自動的にファイルを index.html の URL で利用可能にはなりません。

しかし,多くの場合で静的なファイルを直接 Web ブラウザから提供します。画像,CSS スタイルシート,JavaScript コード,動画,Flash アニメーションなどは全て一般的に Web アプリケーションに保存されブラウザに直接提供されます。App Engine は特定のファイルを直接提供でき,独自のハンドラのコードは必要ありません。

静的なファイルの利用

guestbook/app.yaml を編集して内容を次のように置き換えてください。

app.yaml
application: your-app-id
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /stylesheets
  static_dir: stylesheets

- url: /.*
  script: guestbook.application

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

新しい handlers の項目は2つの URL のハンドラを定義します。App Engine が /stylesheets で始まる URL のリクエストを受け取ると,残りのパスを stylesheets ディレクトリ内のファイルに対応づけて,適切なファイルが見つかるとファイルの内容がクライアントに返されます。他のすべての URL は / パスに対応し,guestbook モジュール内の application オブジェクトによって処理されます。

デフォルトでは,App Engine は静的なファイルを MIME タイプを用いてファイル名の拡張子を元に提供します。例えば,.css で終わる名前のファイルは MIME タイプが text/css で与えられます。app.yaml 内のハンドラの設定の際に,明示的な MIME タイプを mime_type の設定を用いて設定できます。

URL ハンドラのパスのパターンは app.yaml 内で上から下へ出現する順番でテストされます。この場合,/stylesheets パターンは /.* パターンより前に適切なパスとしてマッチします。詳細な URL の対応付けと app.yaml で指定可能な他のオプションについては,app.yaml リファレンスを見てください。

注意:ハンドラによって返されたレスポンスの中でカスタムヘッダを供給するために,静的なディレクトリのハンドラを http_headers の設定で指定できます。これは便利で,例えば,'Access-Control-Allow-Origin'ヘッダを含むと CORS(訳者注:Cross-Origin-Resource-Sharing,他のサーバからデータを取得する仕組みのこと)のサポートを求めます。詳細は http_headers のドキュメントが静的なファイルのハンドラの下にあるので見てください。

guestbook/stylesheets/ ディレクトリを作ってください。この新しいディレクトリで,新しいファイルを作って main.css と名付け,中身を次のようにしてください。

main.css
body {
  font-family: Verdana, Helvetica, sans-serif;
  background-color: #DDDDDD;
}

最後に,guestbook/index.html を編集して<html>と<body>のタグの間の一番上に(訳者注:つまり<head>タグ内)次のように挿入してください。

index.html
  <head>
    <link type="text/css" rel="stylesheet" href="/stylesheets/main.css" />
  </head>

お使いのブラウザでページを更新してください。新しいバージョンがスタイルシートを使っています。

次は...

完成したアプリケーションを世界に明らかにする時がやって参りました。

続きは「アプリケーションのアップロード」で。

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

原文ページはこちら

アプリケーションを作って App Engine 内で管理コンソールを用いて管理します。アプリケーションのためのアプリケーションIDを登録したら,SDK が提供する app.cfg.py とコマンドラインツールをを用いてウェブサイトにアップロードします。もしくは Google App Engine Launcher を用いて,アプリケーションのアップロードが Deploy ボタンをクリックすることでできます。

注:アプリケーション ID の先頭は文字で始めること。アプリケーション ID を登録したら,それは削除できますが,削除後に同じ ID を再登録できません。この時 ID を登録したくない場合は次の段階へスキップできます。

注:App Engine プレミアムアカウントを持っている場合,新しいアプリケーションをアメリカ合衆国ではなく欧州連合に存在させるよう指摘できます(訳者注:事情がよく分からないので翻訳が適当かどうか謎)。プレミアムアカウントを持っていない開発者はアプリケーションを欧州連合に存在させるためには,課金を有効にする必要があります。

欧州連合のホストアプリケーションはユーザがアメリカ合衆国よりもヨーロッパに近い場合に便利です。ネットワークの遅延は少なく,エンドユーザーのコンテンツは欧州連合内で保存されます。アプリケーションの登録時には"Location Options"から"Edit"リンクをクリックすることで場所を指定しなければならず,後から変更できません。

アプリケーションの登録

App Engine の Web アプリケーションを作ったら次の URL で App Engine の管理コンソールから管理します。

https://appengine.google.com/

Google App Engine Launcher ユーザはダッシュボードボタンをクリックするとこの URL になります。

Google アカウントで App Engine にサインインしてください。Google アカウントが無ければ,E メールアドレスとパスワードで新しいGoogle アカウントを作れます

注:既に Google クラウドコンソールを用いてプロジェクトを作っている場合があります。この場合,新しいアプリケーションを作成する必要はありません。プロジェクトはタイトルと ID があります。下記の手順で,アプリケーションのタイトルと ID が記載されている限り,プロジェクトのタイトルと ID が使用可能です。それらは同一です。

新しいアプリケーションを作るには,"Create an Application"ボタンをクリックします。下記はアプリケーションID,このアプリケーションに一意な名前を登録する手順です。無料の appspot.com をドメイン名に使用する場合,アプリケーションの URL 全体は http://your-app-id.appspot.com/ となります。アプリのためのトップレベルドメイン名を購入するか,もしくは既に登録されているものを使用することもできます。

注:ハイレプリケーションデータストアは Python 2.7 の実行環境を使うために必要です。これは新しいアプリケーションを作る時のデフォルトです。

App Engine プレミアムアカウントを持っている場合,新しいアプリケーションをアメリカ合衆国ではなく欧州連合でホストできます。これはユーザがアメリカ合衆国よりも欧州連合に近い場合に,特に便利です。ネットワーク遅延が少なくエンドユーザーのコンテンツは欧州連合に保存されます。アプリケーションの登録の際はこの場所を指定しなければならず,場所のオプションでは,アメリカ合衆国または欧州連合のいずれかを選んでください。

app.yaml ファイルを編集したら,application: の値の設定を your-app-id から登録したアプリケーションIDに変えてください。

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

完成したアプリケーションを Google App Engine にアップロードするには,次のコマンドを実行してください。

appcfg.py update guestbook/

もしくは,Google App Engine Launcher で Deploy をクリックして Google ユーザー名とパスワードをプロンプトから入力してください。

Git のバージョン管理システムで作業する場合,リモートリポジトリを Google のクラウドに作り,開発環境を設定して最新版のコードをリポジトリにプッシュしたタイミングで展開できます。Git の Push と Deploy の利用を見てください。

http://your-app-id.appspot.com

注:データストアインデックスが時々アプリケーションが利用可能になる前に生成される場合があります。インデックスが生成途中の場合,NeedIndexError をアプリにアクセス時に受け取ります。これは一時的なエラーの例なので,最初にこの例外を受け取った場合は少し後に試してください。

おめでとうございます!

チュートリアルは完了しました。ここで扱った話題の詳細は,残りのApp Engineドキュメンテーションをご覧ください。