Kay-frameworkのテンプレートの読み込み問題

Kayでのテンプレート継承 – Blog@uchikoshi22」より。

「kay/templates/」に同名のファイルがあると「myapp/templates/」よりも優先して読み込まれてしまう問題。

たとえば次のテンプレートでは「myapp/templates/base.html」ではなく「kay/templates/base.html」が読み込まれてしまう。

myapp/templates/index.html

{% extends "base.html" %}
{% block hoge %} EXTENDED? {% endblock %}

この問題はなかなか気がつかないだろう。

回避策だが、「Kayでのテンプレート継承 – Blog@uchikoshi22」にあるようにsettings.pyのTEMPLATE_DIRSで指定する。

TEMPLATE_DIRS = (
  'myapp/templates',
)

あるいは、テンプレートの方で「myapp/base.html」と指定しても良さそうだ。

myapp/templates/index.html

{% extends "myapp/base.html" %}
{% block hoge %} EXTENDED? {% endblock %}

ドキュメントの「7.4. テンプレートの読み込み」では「なお APP_DIR/templates ディレクトリは自動的に読み込みの対象になります。」とあるので、てっきり「myapp/templates/」が優先的に読み込まれると思っていたけれど、そうではないらしい。

最新のKay-fwのソースコードを追いかけていないので、何とも言えないが、自分もはまりそうで気になる。

ぜひともMLに投げていただきたいと思う。

App Engine 1.4.1リリース。高い可用性を持つ新しいデータストア「High Replication Datastore」が登場。

Google App Engine SDK 1.4.1がリリースされた。

1.4.1では、高い可用性を持つ新しいデータストア「High Replication Datastore」が使用できるようになった。

High Replication Datastoreの公式ドキュメント(英語)。

既存のアプリケーションでHigh Replication Datastoreを使うには、移行作業が必要。

料金体系も変わるらしい。

Google App Engineでエンティティをデータストアから削除する方法

モデルインスタンスのdelete()メソッドを使うと、対応するエンティティをデータストアから削除できます。

from google.appengine.ext import db
entity = db.get(key) #モデルインスタンスを取得
entity.delete() #モデルインスタンスを削除

まとめて削除するときはdb.delete()を使用します。
db.delete()は引数に、モデルインスタンスやキー、モデルインスタンスやキーのリスト(またはiterable)を受け取ります。
モデルインスタンスのdelete()メソッドを1件ずつ使うよりも高速です。

from google.appengine.ext import db
q = MyModel.all(keys_only=True) #keys_onlty=trueをつけるとちょっと早い
results = q.fetch(10)
db.delete(results)

Google App Engine用フレームワークKayでパフォーマンス測定ツール「Appstats」を使用する

Google App Engineに用意されているパフォーマンス測定ツール「Appstats」をKayで使用する方法を紹介します。

app.yamlとsettings.pyを編集し、「Appstats」を有効にします。

app.yaml

handlers:
…
# 追加。普段のハンドラの上に書くこと
- url: /stats.*
  script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py

- url: /.*
  script: kay/main.py

settings.py

MIDDLEWARE_CLASSES = (
  'google.appengine.ext.appstats.recording.AppStatsDjangoMiddleware', #追加
  'kay.auth.middleware.AuthenticationMiddleware', #追加
  …
)

以上の設定で使用できるようになりました。簡単でしたね。

開発環境で動作を確認するには「http://localhost:8080/stats」にアクセスします。

もう少し、使いやすくしましょう。

Administration Console Custom Pagesの機能を使い、Adminコンソールに追加します。

app.yaml

# 追加
admin_console:
  pages:
  - url: /stats
    name: "Stats"

参考にしたページ

Google App Engineのアンチパターン(3) Greedy module loading(貪欲なモジュール読み込み)

Google App Engine Anti Patterns by Takashi Matsuo on Prezi」より。
なお、この資料はとても参考になるので、一度目を通しておくといいと思います。

Google App Engineのアンチパターン(3) Greedy module loading(貪欲なモジュール読み込み)

「一部でしか使わないモジュールは遅延ロードする」

from kay.utils import render_to_response

import hoge_utils
import fuga_utils

def index(request):
  return render_to_response('myapp/index.html', {'message': 'Hello'})

def hoge(request):
  entries = hoge_utils.get_entries()
  return render_to_response('myapp/hoge.html', {'entries': entries})

def fuga(request):
  entries = fuga_utils.get_entries()
  return render_to_response('myapp/hoge.html', {'entries': entries})

hoge_utils、fuga_utilsはそれぞれ関数hoge()、fuga()の中でしか使用されていません。
使用しないモジュールを読み込むという、不要な処理が行われています。

修正例

from kay.utils import render_to_response

def index(request):
  return render_to_response('myapp/index.html', {'message': 'Hello'})

def hoge(request):
  import hoge_utils
  entries = hoge_utils.get_entries()
  return render_to_response('myapp/hoge.html', {'entries': entries})

def fuga(request):
  import fuga_utils
  entries = fuga_utils.get_entries()
  return render_to_response('myapp/hoge.html', {'entries': entries})

一部でしか使わないモジュールは使用されるときに読み込むようにします。
こうすることで、使用されないモジュールを読み込むことがなくなりました。

アプリケーションが大きくなるにつれて、この差が大きくなっていくように思います。

Google App Engineのアンチパターン(2) 不要なインデックス

Google App Engine Anti Patterns by Takashi Matsuo on Prezi」より。
なお、この資料はとても参考になるので、一度目を通しておくといいと思います。

Google App Engineのアンチパターン(2) 不要なインデックス

「検索やソートをしないならインデックスを作らない」

class Foo(db.Model):
  prop1 = db.StringProperty()
  prop2 = db.StringProperty()
  prop3 = db.StringProperty()
  prop4 = db.StringProperty()
  prop5 = db.StringProperty()
  prop6 = db.StringProperty()

修正例

class Foo(db.Model):
  prop1 = db.StringProperty(indexed=False)
  prop2 = db.StringProperty(indexed=False)
  prop3 = db.StringProperty(indexed=False)
  prop4 = db.StringProperty(indexed=False)
  prop5 = db.StringProperty(indexed=False)
  prop6 = db.StringProperty(indexed=False)

indexedオプションは日本語のマニュアルには記載がありませんが、英語のマニュアルに記載がありました。
indexedオプションを指定すると、指定したプロパティをインデックスに含めるかどうかを指定できます。
検索やソートをしないプロパティはインデックスに含めないことで、データストアへの追加や削除の時にかかるコストを減らすことが出来ます。

「ソートもインデックスを使うので気を付けましょう」

q = MyModel.all().filter("contents =", "word1").order("-created")
entries = q.fetch(10)

上のコードではcreatedプロパティにもインデックスが必要です。

修正例

q = MyModel.all().filter("contents =", "word1").filter("created_month =", this_month)
entries = q.fetch(1000)
#メモリー上でソート
entries.sort(cmp=lambda x,y: cmp(x.created, y.created))

取得するデータを条件指定で絞り込み、ソートはメモリー上で行います。
こうすることでcreatedプロパティのインデックスが不要になります。

Google App Engineのアンチパターン(1) 正規化のし過ぎ

Google App Engine Anti Patterns by Takashi Matsuo on Prezi」より。
なお、この資料はとても参考になるので、一度目を通しておくといいと思います。

Google App Engineのアンチパターン(1) 正規化のし過ぎ

class Tag(db.Model):
  name = db.StringProperty()
  count = db.IntegerProperty()

class Post(db.Model):
  tags = db.ListProperty(db.Key)
  body = db.TextProperty(required=True)
  created = db.DateTimeProperty(auto_now_add=True)

PostはTagのキーを保持すると、データストアの操作が冗長になります。

t = Tag.get_by_key_name(tag_input)
if t is None:
  raise Http404
posts = Post.all().filter("tag =", t.key()).fetch(200)

修正例

class Tag(db.Model):
  name = db.StringProperty()
  count = db.IntegerProperty()

class Post(db.Model):
  tags = db.StringListProperty(db.Key)
  body = db.TextProperty(required=True)
  created = db.DateTimeProperty(auto_now_add=True)

PostはTagの文字列を保持します。
そうすることで、データストアの操作が単純で高速になります。

posts = Post.all().filter("tag =", tag_input).fetch(200)

「検索する場合は上長に持とう」

Google App Engineの初心者がリレーショナルデータベースと同じ感覚で設計してしまうと、このアンチパターンになりがちです。

Google App EngineのBigTableは、従来のリレーショナルデータベースとは異なる設計思想が必要です。

Google App Engine 1.3.7

Google App Engine SDK バージョン1.3.7がダウンロードできます。

SDK リリースノートには、まだバージョン1.3.7の情報がありません。

追記:
バグ修正が行われたようです。

Google App Engine Version 1.3.6の新機能

Google App Engine Version 1.3.6の新機能を開発環境で試しました。

サンプルコードはKay-Frameworkを使用しています。

(1)Results of datastore count() queries and offsets for all datastore queries are no longer capped at 1000.

データストアのクエリーとカウントの1000件の制限を解除されました。

データストアのクエリーとカウントの取得件数が1000件までに制限されていました。
この1000件の制限が解除されました。
CPUクオータとリクエスト時間の許す範囲でデータを取得できます。

QueryとGqlQueryの基底クラス(_BaseQuery)で、取得できる件数の初期値が1000件に設定されています。

class _BaseQuery(object):
  def count(self, limit=1000, **kwargs):

このため引数で制限を指定しない場合は、取得できる件数は従来と同じで1000件までです。

count = models.Comment.all().count() #=>最大1000

1000件以上取得したい場合は、引数で指定します。

count = models.Comment.all().count(99999) #=>最大99999

(2) Users can now serve custom static error pages for over_quota, dos_api_denial and default cases.

エラーページの設定が可能になりました。
参照:Custom Error Responses

app.yaml

error_handlers:
  # 標準のエラーページ
  - file: default_error.html

エラーごとに表示するエラーページを設定することもできます。

app.yaml

error_handlers:
  # 標準のエラーページ
  - file: default_error.html

  # リソースの割り当てを超えたとき
  - error_code: over_quota
    file: over_quota.html

  # DoS Protection
  - error_code: dos_api_denial:
    file: dos_api_denial.html

  # タイムアウトのとき
  - error_code: timeout:
    file: timeout.html

(3)Automatic image thumbnailing is now available in the Images API using get_serving_url().

get_serving_url()で画像のサイズ変更や切り抜きができるようになりました。
参照:get_serving_url(blob_key, size=None, crop=False)

Google App Engine 1.3.6 – Ian Lewis」によると、別のインフラを使うためにGoogleAppEngineのクオータの制限がかからないそうです。

以下のコードは、get_serving_url(blob_key, size=None, crop=False)を使用したサンプルコードです。
アップロードされ画像に対してサイズ変更したり、切り抜きをした画像のURLを作成します。

myapp/urls.py

view_groups = [
  ViewGroup(
    Rule('/', endpoint='index', view='myapp.views.index'),
    Rule('/upload', endpoint='upload', view=('myapp.views.UploadHandler', (), {})),
  )
]

myapp/views.py

from werkzeug import Response
from kay.utils import render_to_response, url_for
from kay.handlers import blobstore_handlers
from google.appengine.ext import blobstore
from google.appengine.api import images

def index(request):
    upload_url = blobstore.create_upload_url(url_for('myapp/upload'))
    blob_key = request.values.get('blob_key')
    d = {'upload_url': upload_url}
    if blob_key:
        from google.appengine.api.images import get_serving_url
        d['blob_key'] = blob_key
        # サイズ変更
        d['resize_url'] = get_serving_url(blob_key, 64)
        # 切り抜き
        d['crop_url'] = get_serving_url(blob_key, 160, True)

    return render_to_response("myapp/index.html", d)

class UploadHandler(blobstore_handlers.BlobstoreUploadHandler):
    def post(self):
        # 'file' is file upload field in the form                                                                                                              
        upload_files = self.get_uploads('file')
        blob_info = upload_files[0]
        headers = {'Location': url_for('myapp/index', blob_key=blob_info.key())}
        return Response(None, headers=headers, status=302)

myapp/templates/index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - myapp</title>
</head>
<body>

{% if blob_key %}
Upload succeeded.<br/>
<a href="{{ resize_url }}">View resizing</a><br />
<a href="{{ crop_url }}">View cropping</a><br />
{% endif %}

<form action="{{ upload_url }}" method="POST" enctype="multipart/form-data">
Upload File: <input type="file" name="file"><br>
<input type="submit" name="submit" value="Submit">
</form>

</body>
</html>

(4)Multitenancy is now supported in the datastore, allowing better compartmentalization of user data.

データストアの名前空間をサポートしました。
参照:Multitenancy and the Namespaces Python API – Google App Engine – Google Code

from google.appengine.api import namespace_manager
namespace = namespace_manager.get_namespace()

namespace_manager.set_namespace('A')  # 名前空間を「A」に設定
do_something() #名前空間「A」でデータストアを操作

namespace_manager.set_namespace('B')  # 名前空間を「B」に設定
do_something() #名前空間「B」でデータストアを操作

namespace_manager.set_namespace(namespace)  # 名前空間を元に戻す

以下のコードは、名前空間を使用したサンプルコードです。
名前空間(‘-global-‘, ‘foo’, ‘bar’)のいずれかのカウンターを1増加し、増加した名前空間とカウンターの値を表示します。

myapp/views.py

from werkzeug import Response
from kay.utils import render_to_response, url_for
from google.appengine.ext import db
from google.appengine.api import namespace_manager
import random

class Counter(db.Model):
    """カウンター"""
    count = db.IntegerProperty()

def update_counter(name):
    """nameのカウンターを1増加する"""
    def _update_counter(name):
        counter = Counter.get_by_key_name(name)
        if counter is None:
            counter = Counter(key_name=name);
            counter.count = 1
        else:
            counter.count = counter.count + 1
        counter.put()
        return counter.count
    return db.run_in_transaction(_update_counter, name)

def index(request):
    """
    名前空間('-global-', 'foo', 'bar')のいずれかのカウンターを1増加し、
    増加した名前空間とカウンターの値を表示する
    """
    # 名前空間のリスト
    ns_list = ('-global-', 'foo', 'bar')
    # 名前空間のリストからランダムに取得
    d = {'ns': random.choice(ns_list)}
    # 現在の名前空間
    namespace = namespace_manager.get_namespace()
    try:
        # 編集するデータストアの名前空間を設定
        namespace_manager.set_namespace(d['ns'])
        # カウンターを増加して結果を取得
        d['count'] = update_counter('SomeRequest')
    finally:
        # 名前空間を元に戻す
        namespace_manager.set_namespace(namespace)
    return render_to_response('myapp/index.html', d)

myapp/templates/index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Top Page - myapp</title>
</head>
<body>
<p>namespace = {{ns}}</p>
<p>count = {{count}}</p>
</body>
</html>

(5)Added a pause queue button to the task queue details page in the Admin Console.

管理画面の「Task Queues」に「Pause Queue」ボタンが追加されました。

(6)Historical graphs have been added to all of the dashboard graphs in the Admin Console.


このボタンが追加された?

(7)New method to allocate datastore ids in a given range: db.allocate_id_range().

与えられた範囲でデータストアのidを割り当てる新しいメソッド db.allocate_id_range() が追加されました。
参照:db.allocate_id_range()

(8)New db method is_in_transaction() determines if a transaction is still open.

トランザクション中かどうかを取得する新しいメソッド db.is_in_transaction() が追加されました。
参照:db.is_in_transaction()

(9)Increased several rate limited quotas for free applications.

無料枠が増えました。

(10) Remote API now supports the Blobstore API.

リモートAPIが新しくBlobstore APIをサポートしました。

(11) Content-range headers are supported on Blobstore downloads.

英語には自信がありません。(汗
間違いがあれば教えてください。

Google App Engine バージョン1.3.6リリース

Google App Engine バージョン1.3.6がリリースされています。

新機能をこれから少しずつ試していきたいと思います。

追記:書きました。