Property クラスは、サブクラス化するように設計されています。ただし、通常は既存の Property サブクラスをサブクラス化するほうが簡単です。
特別な Property 属性はすべて、公開用と考えられる場合でも、名前をアンダーバーで始めます。StructuredProperty は、アンダーバー以外の属性の名前空間を使用して、ネストされた Property 名を参照します。これは、サブプロパティにクエリを指定するうえで非常に重要です。
Property クラスと事前定義のサブクラスを使用すると、積み重ね(スタック)可能な検証と変換 API でサブクラス化を行うことができます。このためには、次のように用語を定義する必要があります。
- ユーザー値。エンティティの標準属性を使用したアプリケーション コードによって設定、アクセスが可能な値です。
- ベース値。データストアに対してシリアル化または非シリアル化された値です。
Property サブクラス。ユーザー値とシリアル化可能な値の間で特定の変換を行います。_to_base_type() と _from_base_type() の 2 つのメソッドを実装する必要があります。これらのメソッドからその super() メソッドを呼び出すことはできません。積み重ね可能(スタック可能)な API というのは、このようなことを意味します。
API はこれまで以上に高度なユーザー / ベース間変換によってクラスの積み重ねをサポートします。ユーザー / ベース間変換はより高度なものからあまり高度ではないものに変わり、ベース / ユーザー間変換はあまり高度ではないものからより高度なものに変わります。たとえば、BlobProperty、TextProperty、StringProperty の関係を見てみましょう。TextProperty は BlobProperty から継承します。必要な動作の大半は継承されるので、コードは非常に簡単になります。
_to_base_type() と _from_base_type() に加えて、_validate() メソッドも積み重ね可能な API です。
検証 API は、緩いユーザー値と厳格なユーザー値を区別します。緩い値のセットは、厳格な値のセットのスーパーセットです。_validate() メソッドは緩い値を受け取り、必要に応じて厳格な値に変換します。つまり、プロパティ値を設定する際は、緩い値が承認されますが、プロパティ値を取得する際には、厳格な値のみが返されます。変換が不要な場合、_validate() は None を返します。引数が、許容される一連の緩い値の範囲外に存在する場合は、_validate() で例外(TypeError または datastore_errors.BadValueError が考えられます)が発生します。
_validate()、_to_base_type()、_from_base_type() は以下の対象を処理する必要はありません。
None:Noneでは呼び出されません(また、None を返す場合、値は変換の必要がないことを意味します)。- 繰り返し値: インフラストラクチャが繰り返し値のリスト項目ごとに
_from_base_type()または_to_base_type()の呼び出しを処理します。 - ユーザー値とベース値の区別: インフラストラクチャは積み重ね可能な API を呼び出して処理を行います。
- 比較: 比較演算でオペランドの
_to_base_type()が呼び出されます。 - ユーザー値とベース値の区別: インフラストラクチャは、ラップ解除されたベース値で
_from_base_type()が呼び出され、ユーザー値で_to_base_type()が呼び出されることを保証します。
たとえば、非常に長い整数値を保存する必要があるとします。標準の IntegerProperty は、符号付きの 64 ビット整数のみをサポートします。プロパティで長い整数値を文字列として格納する可能性があるため、変換を処理するプロパティ クラスの設定をおすすめします。プロパティ クラスを使用するアプリケーションは次のようになります。
...
...
...
...
これは非常にシンプルです。ここでは、LongIntegerProperty の作成者に標準のプロパティ オプション(デフォルト、繰り返し)を使用しています。これらを機能させるために、ボイラープレートを作成する必要はありません。別のプロパティのサブクラスも簡単に定義できます。
エンティティにプロパティ値を設定する(たとえば ent.abc = 42)際には _validate() メソッドが呼び出され、例外が発生しなければ、値がエンティティに格納されます。エンティティをデータストアに書き込む際は _to_base_type() メソッドが呼び出され、値が文字列に変換されます。この値は、ベースクラス StringProperty でシリアル化されます。エンティティが Datastore から読み取られると、これとは逆の順番でイベントが発生します。StringProperty クラスと Property クラスは、これ以外の処理を行います。たとえば、文字列のシリアル化とシリアル化の解除、デフォルトの設定、プロパティの繰り返し値の処理などを行います。
この例で不等式を使用するには(<、<=、>、>= を使用したクエリなど)、さらに作業が必要になります。次の例では、整数の最大サイズを設定し、固定長の文字列として値を格納しています。
これはプロパティ コンストラクタにビット数を渡す必要がある点を除いて、LongIntegerProperty と同じ方法で使用できます(例 BoundedLongIntegerProperty(1024))。
他のプロパティ タイプも同じようにサブクラス化できます。
構造化データを保存する際にも同じアプローチが利用できます。日付範囲を表す FuzzyDate Python クラスがある場合は、次のようにフィールド first と last を使用して日付範囲の開始日と終了日を格納します。
...
StructuredProperty から派生する FuzzyDateProperty を作成できます。ただし、後者は古い Python クラスで機能しません。Model サブクラスが必要になります。中間表現として Model サブクラスを定義します。
次に、StructuredProperty のサブクラスを作成して FuzzyDateModel として modelclass 引数をハードコードし、FuzzyDate と FuzzyDateModel 間の変換を行う _to_base_type() メソッドと _from_base_type() メソッドを定義します。
アプリケーションでは、このクラスを次のように利用できます。
...
FuzzyDateProperty の値として、FuzzyDate オブジェクトに加えてプレーンな date オブジェクトを受け入れるとします。この処理を行うため、_validate() メソッドを次のように変更します。
上の例のように FuzzyDateProperty._validate() を使用すると、次のように FuzzyDateProperty をサブクラス化することもできます。
MaybeFuzzyDateProperty フィールドに値を割り当てると、MaybeFuzzyDateProperty._validate() と FuzzyDateProperty._validate() の両方がこの順番で呼び出されます。_to_base_type() と _from_base_type() についても同様です。スーパークラスとサブクラスのメソッドが暗黙的に統合されます。継承した動作を制御する目的で super を使用しないでください。この 3 つのメソッドの場合、相互作用は希薄で、super は想定した動作になりません。