SyntaxHighlighter

2013年1月13日日曜日

ブログ移動しました

今後はAbove the cloudsに書いていきます。
テーマはWeb全般です。




2012年12月2日日曜日

Google App Engine Python NDB を使ってみた。(7)


NDB Transactions

トランザクションとは一連のオペレーションのセットについて完全に成功かまたは完全に失敗のどちらかにする制御のことである。アプリケーションは複数の計算とオペレーションを一つのトランザクションで行う事が出来る。NDBのAsynchronous APIを使用すると、独立している処理であれば、複数のトランザクションを同時に管理する事が出来る。Synchronous APIは@ndb.transactional()デコレータを使用する事によって簡単にTransactionを制御できる。
競合が発生した場合は、失敗するが、NDBはその失敗したトランザクションを何回か自動的にリトライを行う。従って、その関数はリトライによって複数回呼び出される事がある。リトライの回数はデフォルトで3回である。もしそれでもトランザクションが失敗する場合は、NDBがTransactionFailedErrorを送出する。リトライ回数はtransactional()のretries=Nで設定することが出来る。リトライ回数を0にすると、それは一回だけ試みる事になり、リトライは行わない。リトライ回数のNはトランザクションが試みるであるトータルのN+1回の事である。
トランザクションでは、先祖クエリだけを許している。デフォルトでは、トランザクションは同じエンティティグループに所属するエンティティにのみ作用する。(同じ先祖キーを持つエンティティ達)

また、xg=Trueを設定することで、cross-group("XG")transactionsも使う事が出来る(最大5エンティティグループまで)
もし関数が例外を送出した場合はトランザクションは即時に中断され、NDBはそれを見れるようにする為にもう一度例外を送出する。暗黙にトランザクションを失敗されるにはndb.Rollback例外を送出することで、可能である。(関数の戻り値はNoneになる)これはリトライの機構は持っていない。

常にトランザクション内の処理にしたくない場合もあるが、代わりにデコレート関数の@ndb.transactionalの代わりにcallback関数のndb.transaction()を使う事ができる。

コードがトランザクション内で実行されているかどうかをテストする場合はin_transaction()関数を使えば可能である。

関数が呼びだされた時のトランザクションの振る舞いをどのようにしたら良いかを指定する事が出来る。@ndb.non_transactionalデコレータを指定するとトランザクション内で実行しない方がいい処理となり、トランザクション内で呼び出された場合は、トランザクション外で実行される事になる。@ndb.transactionalデコレータとndb.transaction関数にpropagationキーワード引数をもった関数がある。例えば、関数が呼び出された場合に新しく、独立したトランザクションにしたい場合は、デコレータを下記のようにする。
propagationについては[Context Options and Transaction Options]にリストアップされている。

トランクションの振る舞いとNDBのキャッシュの振る舞いはどのようになるか分からない場合に混乱を招く事がある。例えば、エンティティをトランザクション内で更新しているが、コミットしていない場合に、NDBのキャッシュ上は更新されているが、Datastoreでは更新されていないのである。

今回は、トランザクションについて紹介した。
トランザクションの振る舞いをデコレータで制御できるのは便利である。

次回は、NDB Administrationについて。

2012年11月25日日曜日

Google App Engine Python NDB を使ってみた。(6)

NDB Queries(2)


前回の続き。
Structured Propertyに対するフィルタリング

QueryはStructed Propertyのフィールドの値に対して直接フィルタリングする事が出来る。
例えば、cityがAmsterdamのContactオブジェクトに対してQueryを定義すると下記のとおりになる。
複合したフィルタリングを用いたい場合は、以下のように定義する。
上記で抽出されるContactはcityがAmsterdamかほかのcityでadressがSpear Stになる。しかし、少なくとも等価のフィルターになる。もし単一の結果を返却したい場合は以下のように定義する。
このテクニックを使うと、プロパティのサブエンティティがNoneと等価の場合はクエリで無視される。プロパティがデフォルト値を持っている場合にQueryで無視したい場合は明示的にNoneを設定する必要がある。それか、Queryで指定しているフィルターをデフォルト値と一致させる必要がある。例えば、Addressモデルがcoutryプロパティのデフォルト値をdefault='us'と設定している場合、上記例では、countryが'us'のContactのみを返す事になる。他のcountryを持つContactを抽出したい場合は、filterをAddress(city='San Francisco', street='Spear St', country=None).のようにする必要がある。

サブエンティティのプロパティのどこかにNOneが設定されている場合はそれらは無視される。従って、Noneが設定されてるサブエンティティへのfilterは意味をなさない。

・Projection Queries

Queryにprojectionを指定することができる。これは検索したいプロパティのリストで、もしprojectionを指定した場合は、NDBはそれぞれのEntityから全ての値は取得しない。
projectionで指定したプロパティの値しか取得しない。これはQueryのindexより取得するものなので、indexされている必要がある。数個の大きいEntityから小さいプロパティのいくつかが抽出したい場合に有用であり、Fetchが効率的に行われる。もしprojectionを指定せずに行うとndb.UnprojectedPropertyError.例外を送出することになる。

データストアよりArticleを取得する例では、分類学者は彼らの記事に適用されているタグの作成者を知りたいと仮定すると、必要なのはauthorとtagsの情報だけなので、projectionを以下のように指定する。
Repeated Properties:例では、Aritcle.tagsはrelated propertyとなっている。これは、repeated property はインデックスされていて、(projectionはindexから取得する)projectionクエリは格納されたentityへ複数のentityをフェッチする。もしArticle(author='Guido', tags=['python', 'jython'])のようなEntityが存在した場合に、projectionクエリはArticle(author='Guido', tags=['python'])とArticle(author='Guido', tags=['jython'])の二つのEntityを返却する。

Structured Properties:インデックスされたstructured propertyのサブプロパティを射影する事ができる。下記のように射影を指定することができる。


・String値によってPropertyを指定する

時々String値によってフィルター、順序するプロパティをクエリーで指定したい場合があるが、例えば、ユーザーが指定したtags:pythonのようなサーチクエリを使いたい場合は以下のようにする。

もし、ModelがExpndo Modelの場合は、GenericPropertyを使ってフィルタリングする事ができる。Expandoは動的プロパティを使うので、
このGenericPropertyはExpando Modelでなくても使用する事ができるが、もし自分で確保したプロパティのみを指定させたい場合は、_properties属性を使用することもできる。
または、getattr()を使用する事もできる。
getattr()と_propertiesの違いは、getattr()はPython上のproperyの名前を使い、_propertiesはインデックスされたデータストアのプロパティの名前を使用する。下記のように定義した場合のみ、そのような挙動をする。

これは、Python上ではtitleだが、データストア上はtとなる。

以下のようなアプローチでも使用することができる。
・Query Iterators

Queryが処理されている間は、iteratorオブジェクトとして保持されている。(アプリケーションでこれらが使用される場合はほとんどない。fetch(20)のような方が普通かもしれない)基本的には以下の2点でiteratorオブジェクトを取得することができる。

・QueryからPythonのiter()関数を呼んで使う場合
・Queryオブジェクトのiter()メソッドを呼ぶ場合

一つ目は、 PythonのforループをつかってQueryが終わるまでループする方法
二つ目は、Queryオブジェクトのiter()メソッドを使用する方法で、iteratorの振る舞いに影響を与える為にiteratorにオプションを渡す事ができる。例えば、keyのみのクエリーをforループで使用する場合。
Query iteratorsは他に以下のような便利なメソッドがある。
__iter__(), next(), has_next(), probably_has_next(), cursor_before(), cursor_after()

・Query Cursors

クエリカーソルは、クエリ内の再開ポイントを表す小さな不透明なデータ構造です。これは、その時点での結果をユーザーに見せる場合に便利である。また、長いデータなどで、一時停止して処理したい場合などにも使える。典型的な方法では、Queryのfetch_page()メソッドを使用する方法で、fetch()と同じように動作するが、返却値としてresults, cursor, moreを返却する。moreフラッグは更に結果が存在する事を示し、UIはこれを、例えばNextPageボタンをリンクとして設置したりできる。後続のページをリクエストする場合は、返却値のcursorのfetch_page()を呼ぶ。

したがって、ユーザーにその時点の全ての検索結果を表示する場合に以下のようにコーディングすることができる。

urlsafe()とCursor(urlsafe=s)をシリアライズとでシリアライズに使用する場合には注意が必要。これは、クライアントに一回のリクエストでcursorを渡すことができるのと、後のリクエストにデシリアライズして使用することができる。

注意:fetch_page()メソッドは結果がそれ以上無い場合もcursorを返却するが、それは保証がない。Noneが返却されるだろう。moreフラッグも注意が必要でこれは、iteratorのprobably_has_next()を使用しているので、時々、Trueでも次のページが空の場合もある。

いくつかのNDB Queryは、cursorをサポートしていないが、これを解消することができる。QueryでIN,ORまたは!=を使用すると、キーで順序指定されない限りcursorとして動作しない。
もしアプリケーションで、順序を指定しないでfetch_page()を呼んだ場合は、BadArgumentErroが送出される。User.query(User.name.IN(['Joe', 'Jane'])).order(User.name).fetch_page(N)これではエラーになるので、User.query(User.name.IN(['Joe', 'Jane'])).order(User.name, User.key).fetch_page(N)とすれば問題ない。

pagingの代わりにqueryの結果を取得する場合は、queryのiter()メソッドを正確なポイントで使用するとよい。ただ、produce_cursor=Trueをiter()に渡して上げる必要がある。正しい場所で、iteratorを使用した後は、cursor_afterを呼ぶ必要がある。(または、同様に、cursor_beforeをcursorの前に呼ぶ)cursor_after()またはcursor_before()を呼び出すと、cursorを抽出する為に、クエリの一部を再実行すると、ブロックされるかもしれないので、
注意が必要。

cursorをさかのぼって結果取得する場合は以下のようにする。
・それぞれのEntityへ呼び出す関数をMappingする

Account Entityに関連するMessageをQueryによって取得したいと仮定すると以下のようにコーディングすることができる。
しかしながら、これは非効率である。以下のようにcallback関数を使用することができる。

このバージョンでは、並列化することができるので、forループよりすこし早くすることができる。しかし、get()メソッドでcallback()を使用しても同期処理になってしまうので、asyncronous getを使用するといい。

・GQL

NDBで以下のようにGQLを使用することもできる。
または、

bind()関数は元と同じ新しいqueryを返却する。

SQLに慣れている場合、GQLを使用して誤った仮定には注意。 GQLはNDBのネイティブクエリAPIに変換される。これは、それらがデータベース·サーバーに送信される前にAPIの呼び出しがSQLに変換され、典型的なオブジェクト·リレーショナル·マッパー(SQLAlchemyのか、Djangoのデータベースのサポートなど)とは異なる。 GQLは、データストアの変更(挿入、削除または更新)をサポートしていないので、クエリのみをサポートしている。

今回は、Queryについて2回にわたって紹介した。
しかし、翻訳がつたないのと理解不足があるので、
いずれは、実際に使用してみた例を紹介したいと思う。

次回は、Transactionsについて。

2012年11月18日日曜日

Google App Engine Python NDB を使ってみた。(5)

NDB Queries

アプリケーションはクエリーを使用したフィルターの基準によって指定した検索にマッチするEntityをDatastoreから検索できる。

・概要
アプリケーションはクエリーを使用したフィルターの基準によって指定した検索にマッチするEntityをDatastoreから検索できる。例えば、アプリケーションはクエリーを使ってTrackとGuestbooksを一つのguestbookから日付順に検索できるようにしたままに出来る。

いくつかのクエリーは他より更に複合にできるが、Datastoreはインデックスを事前に構築しておく必要がある。これらの事前に構築するindexはindex.yamlというconfiguration fileで定義できる。開発サーバーでは、クエリーを実行する為に必要なindexを指定していなくても動作するのだが、これは開発サーバーが自動的にindex.yamlを追加するからである。しかし実際のWebSiteでは指定していないindexが必要になると失敗する。したがって、典型的な赤井発サイクルでは、開発サーバーで新しいクエリを試して自動的に更新されたindex.yamlをWebSiteのindex.yamlとして更新する。 
また、index.yamlをアプリケーションとは別に更新できる。
もしDatastoreにたくさんのEntityが存在するとき、インデックスを作成するのに長い時間がかかる。今回の場合、新しいindexを使用するコードをアップデートかける前に、index定義を更新するのが賢明。アドミンコンソールからindex構築の状況を確認することができる。

Datastoreは一致条件に (the==operator)と比較に(<,<=,> and >= operatros)をサポートしている。複数のフィルターをANDを使用する事によって結合できるが、いくつかの制限がある(以下を参照)

さらに、APIは!=とグループのフィルタリングをORで結合できるのと、INが使える。INはPythonのinのようにlistの中で一致する要素を検査する。これらは1対1でDatastoreのオペレーションではない。従って、相対的に少し風変わりで遅い。これらは結果のストリームをインメモリでマージする実装をしている。p!=vは実装上p<v OR p>vとなっている。

制限:データストアはいくつかの制限を強制している。これらを違反すると例外を送出する原因となる。例えば、複数のプロパティーに対する不等号のフィルタリングをたくさん結合したり、異なるプロパティーでソートを行う不等号の結合フィルターは現在全て禁止されている。複数のプロパティを参照するフィルターもまた時々セカンダリーindexの設定が必要になる。

非サポート:Datastoreは一部の文字を使用する検索、大文字小文字区別なし、また全文検索はサポートしていない。これらの大文字小文字区別なしと全文検索でさえ実装するにはcomputedプロパティーを使用する。

・Propertyの値でフィルタリングする
通常は与えられたkindの全てのEntityを検索したくない。だいたいはいくつかのプロパティーに対して範囲を指定したい。
プロパティはフィルタ
ーを表すクエリーによって操作できる。例えば、useridが42をもつEntityを抽出すると以下のような表現になる。

もしuseridがAccount内で一つである事が確実であれば、useridはキーとして扱うだろう。Account.get_by_id()にした方が早いからである。
NDBは以下のオペレーションをサポートしている。
不等のフィルタリングを行う場合は、以下のような文法を使える。
これはuseridが40以上のEntityを探すクエリーとなる。

!=とINは実装上、他のオペレーションとの複合である。また、これらは少し風変わりな説明である。

複合フィルターは以下のように指定する。

この複合フィルターの引数は、useridが40以上から50未満のEntityを返すクエリーとなるが、前述のとおりDatastoreは、複数のプロパティで不等号を使用したクエリーを拒否する。

代わりに全体のクエリーフィルターを一つで表現する。多分もっと便利にクエリーを積み上げることが出来る事を見つけるであろう。
qry3は前述の例のqrtと等価である。このクエリーオブジェクトは不変であり、qry2はqry1に影響を与えず、また、qry3もqry1とqry2に影響は与えない。

・!= と IN のオペレーション
!=(不等)とIN(メンバーシップ)オペレーションはORを使った他のオペレーションとの複合である。まずは以下。
は実装上
例えば、
これは以下と等価

Note:多分驚くであろうが、このクエリーは、perlタグを含まないEntityを探すよりむしろ最低でも一つのタグがperlでない全てのエンティティーを探している。例えば次のエンティティはそのタグの一つとしてperlを持っているのみも関わらず、結果に含まれるだろう。
しかしながら、これは含まれないであろう。
perlと同じタグが含まれていないエンティティを抽出する為の方法はない。
また、INオペレーションについては、以下の表現ができる。

これはリストの値のメンバーシップを検索する。実装上は以下の通り
例は下記
以下とも等価である。

ORをしようすると重複した結果は得られない。

・Repeated Propertiesへのクエリー
Articleクラスはrepeatedプロパティへのクエリの例としても先行したセクションとして定義した。特にフィルターのように、

上記Article.tagsはrepeatedプロパティにも関わらず、一つの値を使った。すると、このプロパティはlistオブジェクトと比べることが出来なくなる。また、フィルターのように、

この場合は、tagsのプロパティーがlistの['python','ruby','php']を持っているEntityを探す事とは全く違う。これはtagsの値が['python','ruby','php']のうち最低でも一つが含まれるEntityを探す。
Noneはクエリーで検索する事が出来ない。

・ANDとORを複合して使う
ANDとORオペレーションをネストして使う事が出来る。


ORの実装の為に、ORを複合しすぎたクエリーは例外とともに失敗するであろう。これらのフィルターは下記のように、一つのレベルのANDとネストされたクエリーの最上位にあるOR
を表現する為に正規化される。この拡張は、既に!=とINに与えられた拡張とともに、ブール式のための論理和標準形を得るための標準的な規則を使用する。要するに、上記の例の正規化された形は、(非公式の表記法を使用する)


注意:
この正規化は複合の爆発の可能性がある。

・ソート順序を指定する
order()メソッドを使用してクエリーの結果の順序を指定する事が出来る。このメソッドはlist引数を受け取り、それぞれ、プロパティーのオブジェクトかまたは、それの昇順降順を指定できる。

この検索は、messageプロパティーの値で昇順にしたGreeting Entityを検索する。
連続したmessageプロパティをuseridを降順にソートした結果を返却するためには、複合のorderを呼び出せば可能である。
order()でフィルタを組み合わせたとき、データストアは、特定の組み合わせを拒否する。特に、不等号フィルタで最初の並べ替え順序を(もしあれば)を使用した場合、フィルタと同じプロパティを指定する必要がある。また、時々セカンダリインデックスを設定する必要がある。

・先祖クエリ
先祖クエリはクエリの結果を先祖から制約する。
ソート順とフィルターの複合で使用できる。
同じ先祖のEntityへの操作となるので、これは特にtransaction内で便利である。

・Queryオブジェクトの属性
Queryオブジェクトは以下のような読み込み専用の属性を持っている。
kind str None Kind name (usually the class name)
ancestor Key None Ancestor specified to query
filters Filter Node None Filter expression
orders Order None Sort orders

str()とかrepr()を呼んで内容を表示するときに表現できる。


今回はQueriesについての前半を記載した。
ここまでは特にNDBに特化した機能などはほとんどなかったが、
次回もQueriesについてだが、NDB特有のmapなど登場する予定。

2012年11月11日日曜日

Google App Engine Python NDB を使ってみた。(4)

プロパティーのサブクラスを書く(Writing Property Subclasses)


プロパティクラスはサブクラス化できるよう設計されている。しかし普通は既存のプロパティクラスの方が簡単にサブクラス化できる。
全ての特別なプロパティ属性とpublicと考えられる属性もアンダースコアから始まる名前を持っている。この理由はStructuredPropertyがネストされたプロパティーのアンダースコアの付いていない属性を使用するからで、これはサブプロパティにクエリを指定する為に不可欠である。

プロパティクラスと既存のサブクラスは構成可能な(またはスタッカブル)検証および変換APIを使用してサブクラス化する事が出来る。これらはいくつかの用語定義を必要とする。

・ユーザーの値(user value)は、アプリケーションが使用する標準の属性を持つエンティティに設定されるまたは、アクセスされるような値。
・基本の値(base value)はデータストアからシリアライズまたはでシリアライズされるような値。

シリアライズ可能な値とuser valueの間で特定の変換を実装する場合は二つのメソッドを実装した方がよい。to_base_type()と_from_base_type()がそれで、これらは構成可能なAPIという意味でsuper()メソッドは呼ばない方がよい。

APIは今までよりも洗練されたuser-baseな変換を行えるstacking classesをサポートしている。user-to-base変換はbase-to-user 変換でより洗練から更に洗練へ変換している間に更に洗練からより洗練へ行く。例えば、BlobProperty,TextPropertyとStringPropertyの関係や、例えば、TextPropertyはBlobPropertyを継承している。それは必要な動作のほとんどを継承しているので、そのコードは簡単なもの。
加えて、_to_base_type()と_from_base_type(),_validate()メソッドもまた、変換APIである。

バリデーションAPIはuser valuesへの緩い制約と厳密な制約とを区別する。 緩い制約の値は厳密な値の集合の上位集合である。_validate()メソッドは緩い値をとり、また必要であれば、厳密な値へ変換する。これは、プロパティの値を取得する場合に、唯一厳密な値が返されるときにプロパティー値を設定するときは、緩い制約の値が受け入れられていることを意味する。もし変換が必要ない場合は、_validate()は多分Noneを返す。もし外部からの引数が、緩い値を設定した場合は、_validate()は例外を送出すべきでTypeErrorかdatasore_error.BadValueErrorが好ましい。

_validate(),_to_base_Type(),_from_base_type()の操作が必要が無い場合:
・None:これらはNoneと一緒に呼び出せない。(また、Noneを返却する場合、それは変換が必要ない事を意味する)
・Repeated values:基盤で_from_base_type()か_to_base_typeをそれぞれのアイテム毎に呼ぶ。
・base valuesからuservaluesを区別する場合:基盤では変換APIがよびだされる。
・比較する場合:比較演算は、オペランドに_to_base_type()を呼び出す。
・base valueとuser valueを区別する場合:基盤で_from_base_type()がbase valueと一緒に呼び出される事と、_to_base_type()がuser valueと一緒に呼び出される事を保証している。

例えば、本当に長いintegerを必要とすると仮定する。標準のIntegerPropertyは64bitのintegerしかサポートしていない。長いintegerを保存する場合はStringとして格納しなくてはならない。変換を操作するpropertyクラスが良い。アプリケーションでそのプロパティを使うと恐らくこのようになるであろう。

これはシンプルで素直だ。またこのデモは標準のプロパティのオプションも使用している。
LongIntegerPropertyクラスの所有者はこれらの作業を取得する任意の定型を記述するひつようがないので、喜んでいるでしょう。他のプロパティのサブクラスを定義するのは簡単だ。下記が例

例えば、ent.abc = 42をエンティティのプロパティに設定した際_validate()メソッドが呼びだされて、値はエンティティに格納される。また、エンティティをDatastoreに格納する際は_to_base_type()が呼び出され、string値に変換する。そして、その値はStringProperyによってシリアライズされる。Datastoreからエンティティを読み戻した場合に逆算が起こる。
StringPropertyとPropertyクラスはシリアライズやデシリアライズ、デフォルト値の設定、repeated propertyの値を操作するような他の世話を一緒にする。

不等号を扱うにはさらに仕事とが必要である。下記の例では、固定長文字列として整数を格納した値の最大サイズを課している。

これは、LongIntegerPropertyと同じ方法で使用され、整数をプロパティのコンストラクターで設定されることを期待している。BoudedLongIntegerProperty(1024)のように。

他のプロパティの型も同じような方法でサブクラスにできる。

このアプローチはstructured dataでも動作する。日付の範囲を表すPythonのFuzzyDateクラスを持っていると仮定する。それはfistとlastのフィールドを持っていて、日付の範囲を開始と終了で格納している。

StructuredPropertyから派生しているFuzzyDateProperyを作成することが出来る。しかし残念ながら、後者は昔ながらのPythonクラスでは動作しない。Modelのサブクラスが必要である。中間モデルとしてModelのサブクラスを定義しよう。

次に、FuzzyDateとFuzzyDataModelを変換する為にFuzzyDateモデルのmodelclassの属性のコードと、_to_base_type()と_from_base_typeメソッドを定義したSturucturedProperyのサブクラスを定義する。

アプリケーションでは以下のように使用する。


FuzzyDateProperyにFuzzyDateオブジェクトのようにdateオブジェクトを格納したいと仮定すると、_validate()メソッドは以下のように変更する。

以下のようにFuzzyDateProperyクラスの代わりに使用する事も出来る。


MaybeFuzzyDateProperyフィールドを割り当てるときにMaybeFuzzyDateProperty._validate()とFuzzyDateProperty._validate()が両方呼び出される。同じ事が_to_base_type()と_from_base_type()に適用される。スーパークラスとサブクラス内のメソッドは暗黙的に結合される。

今回は自前でプロパティを用意したい場合の方法を紹介した。
次回はNDBのQueryについて。

2012年11月3日土曜日

Google App Engine Python NDB を使ってみた。(3)

【NDB プロパティについて】


NDBのエンティティはプロパティの定義ができる。
エンティティはデータを保持するために用いるPythonのクラスと似ている。
それらはデータベースのスキーマにも似ている。

・最初に
典型的なアプリケーションはデータモデルをModelクラスを継承したクラスにプロパティを定義する。

上記はusername, userid,emailがAccountのプロパティとなっている。
いくつか違う型のプロパティを用いているが、手軽に日付、時間を表す事が出来て、自動更新機能も付いるプロパティもある。
アプリケーションはプロパティに特別な振る舞いを指定でき、それらは簡易な検証や、デフォルト値設定、インデックスの変更などができる。
モデルは複合プロパティを持つ事が出来る。また、リストのようなRepeated propertiesも設定できる。構造体プロパティはオブジェクトのように、読み込み専用計算プロパティは関数により定義できる。プロパティに多方面の他のプロパティを定義することが簡単にできる。Expandoモデルは動的にプロパティを設定できる。

・プロパティの型

NDBは以下のプロパティの型をサポートしている。

IntegerProperty:64-bitのInteger型
FloatProperty:倍精度のfoating-point numberの型
BooleanProperty:Boolean値
StringProperty:Unicodeの文字列で500文字以内。インデックスされる。
TextProperty:Unicodeの文字列で長さに制限はないが、インデックスできない。
BlobProperty:byte配列を格納する。もし500文字以内の文字列であれば、インデックスされる。インデックスしないのであれば、長さに制限はない。圧縮もできる。
DateTimeProperty:日付や時間
DateProperty:日付
TimeProperty:時間
GeoPtProperty:位置情報。これはbdb.GeoPtオブジェクトでlatとlonをどちらもfloatで持っている。ndb.GeoPt(52.34, 4.88)やndb.GeoPt("52.37, 4.88")などで生成できる。
KeyProperty:DatastoreのKey、kind="カインド"を指定すれば、キーの割当にいつもカインドを示すことが必須にできる。それは文字列かModelのサブクラスであろう。
BlobKeyProperty:古いdb API(BlobReferenceProperty)に対応しているがプロパティの値はBlobKeyの代わりにBlobInfoとなる。BlobInfoは使用しているBlobInfo(blobkey)から構築できる。
UserProperty:ユーザーのプロパティ
StructuredProperty:値により内部的に別のモデルのカインドを含む
LocalStructuredProperty:StructuredPropertyに似ているが、ディスク上では不透明なblobとして表現され、インデックスはされない。圧縮可能。
JsonProperty:Pythonのjsonモジュールを使ってシリアライズしたオブジェクトを設定できる。JSONシリアライズしてblobとしてdatastoreに格納される。デフォルトでインデックスはされない。圧縮可能。
PickleProperty:pickleプロトコルを使ってシリアライズされたPythonのオブジェクトを設定できる。pickleシリアライズされたデータをblobとしてdatastoreへ格納する。デフォルトでインデックスはされない。圧縮可能。
GenericProperty:汎用的な値で、主にExpandoクラスで使われる。ただし、明示的に使用可能な型はint,long,float,bool,str,unicode,datetime,Key,BlobKey,GeoPt,User,Noneとなっている。
ComputedProperty:UDFによって他のプロパティより計算された値を設定できる。

いくつかのプロパティはオプションの引数のcompressedを使用する事ができる。もし、プロパティがcompressed=Trueで設定されていた場合はデータはgzip圧縮されてディスク上に格納される。これにより格納容量は小さくできるが、エンコードとデコードにCPUを消費する。

・プロパティのオプション

ほとんどのプロパティの種類は、いくつかの標準的な引数をサポートしている。
第一引数はDatastoreネームを指定する任意の引数。これによってカプリケーションの観点よりもデータストアに別の名前を与える事ができる。一般的な使用方法はデータストアのスペースを減らす目的で使う。データストアは短縮されたプロパティ名を使う。以下が参考。
意味を持つ一文字など設定するとよい。


これは特にrepeated property でEntityごとに多くの値を期待している場合
に有効。
さらに、ほとんどのプロパティはkeyword argumentsをサポートしている。

詳細はArgument&Typeを参照

・Repeated Properties

どのプロパティもrepeated = Trueにするとrepeated propertyになる。
そのプロパティは基クラスのリストの値になる。
下記が参考。プロパティの値はIntegerProperty のリストとなる。
データストアはそのようなプロパティに複数の値が表示される場合がある。独立したインデックスレコードは各値の為に作成されるこれはクエリセマンティクスに影響を与える。
以下が例。

以下がEntity


tagsプロパティの検索をする場合にこのEntityはpythonとrubyどちらも満たす。

もし、repeated propertyを更新する場合は、新しいリストを割り当てるかその場で既存のリストを変更する事が出来る。新しいリストを割り当てた場合は即時に型チェックが行われる。例えば[1,2]をart.tagsに割り当てると例外が起こる。もし、既存のリストを更新した場合は即時に型チェックは行われない。代わりにDatastoreへ書き込むときに型チェックが行われる。
データストアはrepeated propertyのリストの順番を保持する。

・日付と時間のプロパティ

日付と時間は3つのプロパティの型が有効である。
・DateProperty
・TimeProperty
・DateTimeProperty

これらの値はPythonのdatetimeモジュールのdata, time, datetime,クラスに対応する型である。3つの中で最も一般的なのはDateTimePropertyでカレンダーの日付と日付の時間を意味する。また時折便利な特別な使い方として、ちょうどの時間が必要な場合(例えば誕生日、ミーティングの時間)がある。技術的な理由としてDatePropertyとTimePropertyはDateTimePropertyのサブクラスである。ただこの継承関係に依存しない方がよい。また、この継承関係は基本になっているクラスdatetimeの継承関係とは違う。

Note:App EngineのクロックタイムはUTCで設定されている。もしPOSIX timestampsかtime tuplesに変換したデータで現在時間を使おうとする事に関係する。明白なタイムゾーンの情報をDatasotereに格納する際に与えられない。もし、ローカル時間などで現在時刻を使うときは注意が必要である。

それぞれのプロパティは下記のkeyword argmentを使う事ができる。
auto_now_add: Entityが作られた際に自動的に現在時刻が設定される。
auto_now: Entityが更新された際に自動的に現在時刻が設定される。
これらのオプションはrepeated=Trueと複合することができない。どちらもデフォルト値はFalseでもし両方Trueにした場合はauto_nowが優先される。auto_now_add=Trueは上書きすることが出来るが、auto_now=Trueはできない。自動的な値はEntityが書き込まれるまで設定されない。これらのオプションはdynamic defaultsを提供していない。

・Structured プロパティ

構造化されたプロパティを設定する事が出来る。以下が例である。
Contactモデルクラスは住所をリストで持っている。

以下のような一つのEntityが作成される。


Entityを読み直すとContactエンティティを正確に再構築する。しかし、Adressインスタンスはモデルクラスと同じ構文を使用して定義されているが、Entityではない。これらはKeyを持っていない。Contactエンティティから独立して取得することができない。個々のフィールドへのクエリーとしてならアプリケーションで可能である。(Structured プロパティへのフィルタリングを参照)adress.type, address.streedとaddress.cityはDatastoreの観点では並行だが、NDBはこの側面を隠している。NDBでは関連するAdressインスタンスのリストとして構築している。
プロパティオプションを指定する事も出来る(indexedなど)この場合は、第二引数にDatastoreの名前を指定している。
もしStruectureプロパティへのクエリが必要ない場合はLocalStructuredPropertyを代わりに使用する事が出来る。Pythonコードとしては同じであるが、データストアは各AdressのBlobを見ている。例で作成したEntityは以下のようになる。

Entityは正しく読み戻される。このタイプのプロパティは常にインデックスは無いので、Adressをクエリで検索することは出来ない。

・Computed プロパティ

Computedプロパティは読み込み専用で、アプリケーションが提供する関数で計算した結果を設定することが出来る。計算された値はクエリとDatastoreビュアーの為に書き込まれるが、格納された値はDatastoreから読み戻された際は無視される。値は、関数が呼びだされた際に再計算される。以下が例。

格納されたEntityのプロパティの値は以下のとおり

もしnameをNickieに変更した場合にname_lowerはnickieを返す。

Note:もしクエリーで計算した値を使いたい場合は、ComputedPropertyを使う。もし派生バージョンのPythonコードを使いたい場合はregular methodを定義するか@propertyを使うとよい。

・ProtoRPC Message プロパティ

ProtoRPC API は 構造データの為にMessageオブジェクトを使用する。これらはRPCリクエスト、レスポンス、等を表現できる。NDBはMessageオブジェクトをEntityのプロパティとして定義できる。Messageサブクラスを定義すると仮定すると、

NDBのmsgprop APIを使用してDatastoreへEntityのプロパティとして格納できる。


もしクエリで検索したい場合はインデックスする必要がある。MessagePropertyへのindexed_fieldsを指定する事が出来る。
MessagePropertyは通常のプロパティオプションの全てはサポートしていない。
以下がサポートしているオプション
・name
・repeated
・required
・default
・choices
・validator
・verbose_name

Message プロパティはそれ自体にindexを使う事は出来ない。(フィールド名をしていすることで、indexできる)

ネストしたmessageも使用できる。


MessagePropertyは特別なオプションprotocolを指定できる。これはdatastoreへmessageオブジェクトをどのようにシリアライズして格納するかを指定できる。protocolの値はprotorpc.remote.Protocolsクラスの名前を使用できる。サポートするprotocolはprotobufとprotojsonでデフォルトはprotobuf
msgpropはEnumPropertyも定義できる。このプロパティはprotorpc.messages.Enumの値をエンティティとして格納できる。以下が例。


EnumPropertyはintegerとして格納される。事実としてEnumPropertyはIntegerPropertyのサブクラスである。また、既に格納されているEnumの名前を変更することは出来るが、再度採番することはできない。

EnumPropertyは以下のオプションをサポートしている。
・name
・indexed
・repeated
・required
・default
・choices
・validator
・verbose_name

今回はプロパティについて紹介した。
構造化されたプロパティをそのまま格納できるのは便利だと思った。
実は実際に使った事がないので、近いうちに使ってみたい。
次回は、プロパティのサブクラスについて。

2012年10月14日日曜日

Google App Engine Python NDB を使ってみた。(2)

【NDBエンティティとキー】

Datastoreに格納されているオブジェクト(エンティティ)はndb.Modelのインスタンスで、アプリケーションではndb.Modelのサブクラスとして定義する。
エンティティはkeyで識別され、アプリケーションのDatastore内でユニークとなる。

・概要


以下のようなエンティティクラスを定義する。
それぞれのエンティティはアプリケーション内のDatastoreで一意のキーで識別される。
キーがカインドと識別子を構成しているのが最も簡単なフォームである。
普通はカインドの名前はモデルクラス名と同じにする。上記の例ではAccount
だが、method _get_kind()を上書きすることで、名前を変える事も出来る。
識別子は、アプリケーションまたはデータストアで自動的に生成された整数の数字IDか割り当てられたいずれかのキー名の文字列である。
エンティティのキーは他のキーを親のキーとして示す事が出来る。これを”エンティティのキーの親”と呼ぶ。よくエンティティーの親とも呼ばれる。
コンテキストに依存することはエンティティのキーの親を意味する事か、エンティティはキーのキーを持っている事になる。ルートのエンティティーを持っていないエンティティはそれは親のエンティティで再起的にそれは先祖である。
エンティティはデータストアにいるが、階層の構成はファイルシステムの階層と同様である。エンティティの連続処理はルートのエンティティから始まって、親から子へと続く。
特定のエンティティにつながる、そのエンティティの祖先パスを構成している。

完全な一意キーは、つまり連続するカインド識別子を自身のエンティティーまで指定したその先祖キーである。
Keyクラスのコンストラクターは連続するカインドと識別子を指定すすことを許可している。その返却値のキーがそのエンティティに対応するキーとなる。
下記の例はメッセージのリビジョンを示すもので、rev_keyが所有者に所属している事になる。
上記例で注意が必要なのがリストの最後で2という数字を使っているがこれは特殊で数字キーを使うことはできるが、これは少しトリッキーになる。詳細は数値キーを使うを参照。

ルートエンティティ用としては下記のよう先祖パスは空で、自身のカインドと識別子のみで構成されている。
代わり以下のようにモデルクラスを直接代入できる。
下記例では指定しているキーはすべて等価である。

・エンティティの作成


モデルクラスのコンストラクターを呼び出すことでエンティティを作成する事が出来る。
プロパティの設定はkeyword argumentsで指定する。
ここで作成したオブジェクトはput()メソッドを呼びだす事によりデータストアへ格納さる。返却値はkeyとなっている。
代わりに以下のようにプロパティを直接指定できる。
以下のようにpopulate()を使うこともできる。

また、プロパティのタイプはいろいろなタイプが使えるがタイプチェックが行われる。
下記の例だと、StringPropertyとIntegerPropertyである。


・キーからエンティティの取得


エンティティーのキーを与えれば、データストアからエンティティを取得できる。
キーのメソッドであるkind()とidはそれぞれカインドと識別子を返却する。
parent()メソッドはキーの親のエンティティを返却する。
また、URLに埋め込むためにキーをエンコードした形で取得できる。
この生成方法ではagVoZWxsb3IPCxIHQWNjb3VudBiZiwIMのようなキーを返却するが、そこからエンティティも取得できる。

注意:URL-safeは暗号化されない。以下のように簡単に複合化できてしまうので注意。
なので、e-mailアドレスとかは暗号化したもので使用する。


・エンティティの更新


エンティティの更新はデータストアから取得したエンティティの編集を行い、データストアに戻す。
この場合のput()の返却値は同じなので無視してよい。


・エンティティの削除


エンティティが必要なくなったらデータストアから削除する。keyのdelete()メソッドで
削除できる。
このメソッドは常にNoneを返却する。


・複数のキー、エンティティを処理する。


get(),put()はRPC呼び出しを行っているので、ループを使って処理するしかなく非効率だったが、下記のメソッドで速くできる。


【Expandoモデル】


時々事前にプロパティを指定したくないときがある。その場合に特別なクラスExpandoを使う。Expandoは、割り当てられた任意の属性(限り、それはアンダースコアで始まらないように)がデータストアに保存されるように、そのエンティティの動作を変更する。たとえば、以下のように。
このデータストアへの書き込みはfooプロパティはInteger値で1barプロパティはString値'blah'でtagsプロパティはString値の繰り返しで'exp','and','oh'となっている。プロパティはインデックスされそれを_propertiesで参照することが出来る。
データストアから値を取得する事によって作成されたExpandoはデータストアに保存されたすべてのプロパティーとプロパティー値を持っている。
アプリケーションはExpandoのサブクラスとして定義して前もってプロパティを持つ事もできる。
上記の例ではnameプロパティはSandyでageはNone、動的プロパティlocationは'SF'となる。
Expandoのサブクラスに_default_indexed = Falseを指定することでインデックスから外すことができる。
_default_indexedをExpandoエンティティにセットすることができる。この場合は事後に指定したプロパティすべてに適用される。
他の便利なテクニックとしてはクエリに動的プロパティを使う事ができる。
ただ、プロパティを持っていない場合もあるので、下記のように指定する。


【モデルのフック】


NDBは軽量なフック機構を提供している。フックを利用することで、形式の処理を実現する事ができる。例としてはModelはget()のあとで様々な処理をするが、それを同期的にまたは非同期、複数など特別なメソッドを作れる。以下の例では、フックで様々な取得方法を提供している。
フックは下記で便利
・クエリーのキャッシュ
・ユーザー毎のデータストア監視
・データストアへのトリガー
もし非同期APIをフックとして使う場合はcheck_result()かget_result()かTaskletによるyieldingメソッドを呼びだすことがトリガーになる。
事後フックはRPCが成功していることは確認しない。失敗しても実行される。
すべての事後フックはFuture属性を持つ。このFutureオブジェクトは動作の結果を保持している。get_result()メソッドを呼びだせば、結果を取得できる。フックが呼び出された時点でFutureは完了しているので、get_result()でブロックされない事を確認できる。
事前フックで例外が発生すると、その場所をとってから、要求を防ぐことになる。
フックは_asyncメソッド内でトリガされるが、事前にRPCフックでtasklets.Returnを上げることによって、RPCを先取りすることはできない。


【数値キーを使う】


キーはカインドとIDの直列だが、アプリケーションとネームスペースないで、一意のキーを持っているか確認したくなる。アプリケーションがID指定なしでエンティティを作成すると、自動で数値キーが割り当てられる。アプリケーションが手動でID(数値の)を取り出すと、データストアは自動でIDを生成するが、既に使用されているIDを選択する場合がある。
回避するためには、アプリケーションが予約した数値の範囲を使用するようにするとよい。
(数値IDを使わなければ問題ない)
下記のように予約IDの範囲を指定できる。下記は100個のIDを割り当てている。
親キーと一緒に指定もできる。
キーの取り出し方は下記の通りで、first, lastの範囲でキーが割り当てられている。
これらのキーはデータストアの内部的なID生成で既に割り当てられていないIDである事は保証していない。また将来的に生成されるIDも同様である。 しかし、allocate_ids()が返すIDは、データストア内で存在するかどうかは確認しない。 代わりに下記のように最大値からID割り当てることもできる。 このフォームはN以下のIDを確実に返す。返却されるfirstとlastは予約されたIDの範囲を示す。 アプリケーションはトランザクション内でallocate_ids()を呼ぶことができない。 まあ、数値IDはあんまり使わない方が良さそう(感想)
次回は、プロパティについて。