Python でのデータ モデリング

注: 新しいアプリケーションを作成する際は、NDB クライアント ライブラリを使用することを強くおすすめします。NDB クライアント ライブラリには、Memcache API によるエンティティの自動キャッシュをはじめ、このクライアント ライブラリにはないメリットがあります。古い DB クライアント ライブラリを現在使用している場合は、DB から NDB への移行ガイドをお読みください。

概要

データストア エンティティには、1 つのキーと一連のプロパティがあります。アプリケーションは、Datastore API を使用してデータ モデルを定義し、エンティティとして格納されるモデルのインスタンスを作成します。モデルによって、API で作成するエンティティに共通の構造が提供されるほか、プロパティの値を検証するためのルールを定義できます。

モデル クラス

Model クラス

アプリケーションで使用するデータの種類は、モデルを使用して記述します。モデルとは、Model クラスから継承される Python のクラスです。モデルクラスでは、データストア エンティティの新しい種類と、その種類が受け取ることが想定されるプロパティを定義します。種類名は、db.Model から継承した、インスタンス化されたクラス名によって定義されます。

モデルのプロパティは、モデルクラスのクラス属性を使用して定義します。各クラス属性は Property クラスのサブクラスのインスタンスで、通常は提供されているプロパティ クラスの 1 つです。プロパティ インスタンスには、インスタンスが有効であるためにはプロパティが必要かどうかや、値が指定されていない場合に使用するインスタンスのデフォルト値など、プロパティの設定が保持されます。

from google.appengine.ext import db

class Pet(db.Model):
    name = db.StringProperty(required=True)
    type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
    birthdate = db.DateProperty()
    weight_in_pounds = db.IntegerProperty()
    spayed_or_neutered = db.BooleanProperty()

エンティティ(定義されているエンティティの種類のいずれかに属する)は、API では対応するモデル クラスのインスタンスによって表されます。アプリケーションで新しいエンティティを作成するには、クラスのコンストラクタを呼び出します。アプリケーションでエンティティのプロパティにアクセスしたり、プロパティを操作したりするには、インスタンスの属性を使用します。モデルのインスタンスのコンストラクタは、プロパティの初期値をキーワード引数として受け取ります。

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat")
pet.weight_in_pounds = 24

注: モデル クラスの属性はモデルのプロパティの設定であり、その値は Property インスタンスです。モデル インスタンスの属性は実際のプロパティ値であり、その値は Property クラスが受け付ける型です。

Model クラスは、Property インスタンスを使用して、モデル インスタンスの属性に割り当てられている値を検証します。プロパティ値の検証は、モデル インスタンスを初めて作成する際や、インスタンス属性に新しい値を割り当てる際に行われます。これにより、プロパティに無効な値が設定されることがなくなります。

インスタンスの作成時に検証が行われるため、必須として構成されるプロパティをコンストラクタで初期化する必要があります。この例では、nametype は必須の値であるため、コンストラクタで初期値が指定されています。weight_in_pounds はモデルで必須ではないため、値が割り当てられずに開始され、後で値が割り当てられます。

コンストラクタを使用して作成するモデルのインスタンスは、初めて「プット」されるまでデータストア内に存在しません。

注: すべての Python クラス属性と同様に、モデル プロパティの設定は、スクリプトやモジュールが初めてインポートされるときに初期化されます。App Engine ではインポートしたモジュールが複数の要求にわたってキャッシュされるため、あるユーザーの要求時にモジュールの設定が初期化され、別のユーザーの要求時に再利用される可能性があります。モデル プロパティの設定(デフォルト値など)を、要求や現在のユーザーに固有のデータで初期化しないでください。詳しくは、アプリケーション キャッシュをご覧ください。

Expando クラス

Model クラスを使用して定義するモデルでは、クラスの各インスタンスに(おそらくはデフォルト値として)指定する必要のある一連の固定プロパティを設定します。これはデータ オブジェクトをモデル化するうえで便利な方法ですが、データストアでは、同じ種類のすべてのエンティティで同じ組み合わせのプロパティを使用する必要はありません。

同じ種類のエンティティでも、異なるプロパティを使用するほうが便利な場合もあります。こうしたエンティティは、Datastore API では「expando」モデルで表されます。expando モデル クラスは、Expando スーパークラスのサブクラスです。expando モデルのインスタンスの属性に割り当てられた値は、属性の名前を使用する、データストア エンティティのプロパティになります。これらのプロパティは動的プロパティと呼ばれます。クラス属性で Property クラス インスタンスを使用して定義されるプロパティは、固定プロパティです。

expando モデルでは固定プロパティと動的プロパティの両方を使用できます。モデル クラスは、単に固定プロパティの Property 設定オブジェクトでクラス属性を設定します。アプリケーションは、値を割り当てるときに動的プロパティを作成します。

class Person(db.Expando):
    first_name = db.StringProperty()
    last_name = db.StringProperty()
    hobbies = db.StringListProperty()

p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

p.chess_elo_rating = 1350

p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"]
p.travel_trip_count = 13

動的プロパティにはモデル プロパティの定義がないため、動的プロパティは検証されません。動的プロパティは、任意のデータストア基本型None など)の値を取ることができます。同じ種類の 2 つのエンティティにおいて、同じ動的プロパティに対して異なる型の値を持つことができます。また、1 つにプロパティを設定し、もう 1 つにはプロパティを設定しないでおくこともできます。

固定プロパティとは異なり、動的プロパティが存在することは必須ではありません。値が None の動的プロパティは、動的プロパティ自体が存在しないこととは異なります。expando モデル インスタンスでプロパティに属性がない場合、対応するデータ エンティティにはそのプロパティはありません。動的プロパティは、属性を削除することで削除できます。

名前がアンダースコア(_)で始まる属性は、データストア エンティティに保存されません。これにより、エンティティで保存されるデータに影響を与えることなく、一時的な内部使用のためにモデル インスタンスに値を格納できます。

注: 静的プロパティは、Expando であるか、Model であるか、またはアンダースコア(_)で始まるかどうかにかかわらず、常にデータストア エンティティに保存されます。

del p.chess_elo_rating

フィルタで動的プロパティを使用するクエリは、クエリで使用されている値と同じ型のプロパティ値を持つエンティティのみを返します。同様に、クエリは、同じ組み合わせのプロパティを持つ同じエンティティのみを返します。

p1 = Person()
p1.favorite = 42
p1.put()

p2 = Person()
p2.favorite = "blue"
p2.put()

p3 = Person()
p3.put()

people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50)
# people has p1, but not p2 or p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people has no results

注: 上記の例では複数のエンティティ グループにわたるクエリを使用していますが、この方法では最新でない結果が返される可能性があります。強整合性を備えた結果を得るには、エンティティ グループ内で祖先クエリを使用します。

Expando クラスは Model クラスのサブクラスで、すべてのメソッドを継承します。

PolyModel クラス

Python API に含まれる別のデータ モデリング用クラスを使用すると、クラスの階層を定義して、特定のクラスまたはそのすべてのサブクラスのエンティティを返すことのできるクエリを実行できます。このようなモデルとクエリは「ポリモーフィック(多形)」と呼ばれます。これは、こうしたモデルとクエリでは、1 つのクラスのインスタンスを、親クラスのクエリに対する結果とすることができるためです。

次の例では、サブクラス ContactPerson を持つ Company クラスを定義しています。

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
    phone_number = db.PhoneNumberProperty()
    address = db.PostalAddressProperty()

class Person(Contact):
    first_name = db.StringProperty()
    last_name = db.StringProperty()
    mobile_number = db.PhoneNumberProperty()

class Company(Contact):
    name = db.StringProperty()
    fax_number = db.PhoneNumberProperty()

このモデルでは、すべての Person エンティティとすべての Company エンティティが phone_number プロパティと address プロパティを持ち、Contact エンティティに対するクエリで Person エンティティまたは Company エンティティのいずれかを返すように設定できます。mobile_number プロパティを持つエンティティは Person のみです。

他の任意のモデルクラスと同様に、サブクラスをインスタンス化できます。

p = Person(phone_number='1-206-555-9234',
           address='123 First Ave., Seattle, WA, 98101',
           first_name='Alfred',
           last_name='Smith',
           mobile_number='1-206-555-0117')
p.put()

c = Company(phone_number='1-503-555-9123',
            address='P.O. Box 98765, Salem, OR, 97301',
            name='Data Solutions, LLC',
            fax_number='1-503-555-6622')
c.put()

Contact エンティティに対するクエリでは、ContactPerson、または Company のいずれかのインスタンスを返すことができます。次のコードは、上記の例で作成した両方のエンティティの情報を出力します。

for contact in Contact.all():
    print 'Phone: %s\nAddress: %s\n\n' % (contact.phone_number,
                                          contact.address)

Company エンティティに対するクエリでは、Company のインスタンスのみを返します。

for company in Company.all()
    # ...

現時点では、ポリモーフィック モデルを Query クラスのコンストラクタに直接渡さないでください。代わりに、前の例で示されているように all() メソッドを使用します。

ポリモーフィック モデルの使用方法とその実装方法の詳細については、PolyModel クラスをご覧ください。

プロパティのクラスと型

データストアでは、Unicode 文字列、整数、浮動小数点数、日付、エンティティ キー、バイト文字列(BLOB)、各種 GData 型など、一定の型のエンティティのプロパティ値がサポートされます。データストアの値の型にはそれぞれ、google.appengine.ext.db モジュールで指定される、対応する Property クラスがあります。

サポートされているすべての値の型とそれらに対応する Property クラスは、型とプロパティ クラスに記載されています。以下では一部の特殊な値の型について説明します。

文字列と BLOB

データストアでは、テキストを格納するために 2 種類の値の型がサポートされています。1 つは長さが 1500 バイトまでの短いテキスト文字列で、もう 1 つは長さが 1 メガバイトまでの長いテキスト文字列です。短い文字列はインデックスが付けられ、クエリのフィルタ条件や並べ替え順序で使用できます。長い文字列はインデックスに登録されず、フィルタ条件や並べ替え順序では使用できません。

短い文字列値は、unicode 値または str 値のいずれかに設定できます。値が str の場合、エンコードは 'ascii' とみなされます。str 値に別のエンコードを指定するには、unicode() 型コンストラクタを使用して値を unicode 値に変換します。このコンストラクタは str とエンコードの名前を引数として取ります。短い文字列は、StringProperty クラスを使用してモデル化できます。

class MyModel(db.Model):
    string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u"kittens"

# unicode() converts a byte string to a Unicode string using the named codec.
obj.string = unicode("kittens", "latin-1")

# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj.string = "kittens"

# Short string properties can be used in query filters.
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

長い文字列値は、db.Text インスタンスで表されます。このインスタンスのコンストラクタは、unicode 値か str 値のいずれかを取ります。また、オプションでその str 値で使用されるエンコードの名前を取ります。長い文字列は、TextProperty クラスを使用してモデル化できます。

class MyModel(db.Model):
    text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode string.
obj.text = u"lots of kittens"

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text("lots of kittens", "latin-1")

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = "lots of kittens"

# Text properties can store large values.
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

また、データストアでは、非テキストバイト文字列用の同様の 2 つの型もサポートされています。db.ByteStringdb.Blob です。これらの値は未加工バイトの文字列で、エンコードされたテキスト(UTF-8 など)としては扱われません。

db.StringProperty 値のように、db.ByteString 値にはインデックスが付けられます。db.TextProperty プロパティと同様に、db.ByteString 値は 1,500 バイトに制限されます。ByteString インスタンスは短いバイト文字列を表し、そのコンストラクタへの引数として str 値を取ります。バイト文字列は、ByteStringProperty クラスを使用してモデル化されます。

db.Text のように、db.Blob 値のサイズは最大 1 メガバイトです。ただし、インデックスは付けられず、クエリのフィルタや並べ替えの順序付けには使用できません。db.Blob クラスは、そのコンストラクタの引数として str 値を取ります。または、ユーザーが値を直接割り当てることもできます。BLOB は、BlobProperty クラスを使用してモデル化されます。

class MyModel(db.Model):
    blob = db.BlobProperty()

obj = MyModel()

obj.blob = open("image.png").read()

リスト

1 つのプロパティに複数の値を指定することができ、Datastore API では Python list で表されます。リストには、データストアでサポートされている任意の型の値を含めることができます。1 つのリスト プロパティに異なる複数の型の値を指定することもできます。

通常、順序は維持されるため、クエリや get() によってエンティティが返される際は、リスト プロパティの値は格納されたときと同じ順序になります。これには例外が 1 つあり、Blob 値と Text 値はリストの末尾に移動されます。ただし、相互の相対的な順序は元のとおり維持されます。

ListProperty クラスはリストをモデル化し、リスト内のすべての値に指定した型を適用します。利便性のため、ライブラリには ListProperty(basestring) と類似した StringListProperty も用意されています。

class MyModel(db.Model):
    numbers = db.ListProperty(long)

obj = MyModel()
obj.numbers = [2, 4, 6, 8, 10]

obj.numbers = ["hello"]  # ERROR: MyModel.numbers must be a list of longs.

リスト プロパティに対するフィルタを含むクエリは、リスト内の値を個別にテストします。エンティティがクエリと一致するのは、そのプロパティに対するすべてのフィルタを通過する値がリスト内にある場合のみです。詳細については、データストア クエリをご覧ください。

# Get all entities where numbers contains a 6.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6")

# Get all entities where numbers contains at least one element less than 10.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")

クエリのフィルタはリストのメンバーに対してのみ適用されます。クエリのフィルタで 2 つのリストが同一かどうかをテストする方法はありません。

内部的には、データストアではリストのプロパティ値がそのプロパティに対する複数の値として表されます。リストのプロパティ値が空のリストの場合、そのプロパティを表す値はデータストア内にありません。Datastore API では、この状況の処理が静的プロパティ(ListProperty を使用)と動的プロパティで異なります:

  • 静的 ListProperty には、値として空のリストを割り当てることができます。プロパティはデータストア内に存在しませんが、モデル インスタンスは値が空のリストであるかのように動作します。静的 ListProperty に None の値を指定することはできません。
  • list 値の動的プロパティには、空のリスト値を割り当てることはできません。ただし、None の値を割り当てることは可能で、(del を使用して)削除することもできます。

ListProperty モデルは、リストに追加される値が正しい型かどうかをテストし、正しくない場合は BadValueError をスローします。このテストは、以前に格納されたエンティティを取得してモデルに読み込む際にも行われます(そして失敗することもあります)。str 値は保存の前に unicode 値に(ASCII テキストとして)変換されるため ListProperty(str)ListProperty(basestring) として扱われます。これは、strunicode の両方の値を受け入れる Python データ型です。この目的で StringListProperty() を使用することもできます。

非テキストバイト文字列を格納するには、db.Blob 値を使用します。BLOB 文字列のバイトは、格納時と取得時に保持されます。BLOB のリストであるプロパティを ListProperty(db.Blob) として宣言できます。

リスト プロパティと並べ替え順序のやり取りは通常とは異なります。詳しくは、データストア クエリをご覧ください。

参照

プロパティ値には、別のエンティティのキーを含めることができます。値は Key インスタンスです。

ReferenceProperty クラスがキー値をモデル化し、すべての値が指定した種類のエンティティを参照するようにします。利便性のため、ライブラリには SelfReferenceProperty も用意されています。これは、プロパティを持つエンティティと同じ種類を参照する ReferenceProperty に相当します。

モデル インスタンスを ReferenceProperty プロパティに割り当てると、そのキーが値として自動的に使用されます。

class FirstModel(db.Model):
    prop = db.IntegerProperty()

class SecondModel(db.Model):
    reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity's key as the value.
obj2.reference = obj1
obj2.put()

ReferenceProperty プロパティの値は、参照されているエンティティのモデル インスタンスと同じように使用できます。参照されているエンティティがメモリ内に存在しない場合、プロパティをインスタンスとして使用すると、エンティティがデータストアから自動的に取得されます。ReferenceProperty にもキーが格納されますが、このプロパティを使用すると、関連するエンティティが読み込まれます。

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

キーの参照先のエンティティが存在しない場合、このプロパティにアクセスするとエラーが発生します。アプリケーションで、参照が無効である可能性が想定される場合は、try/except ブロックを使用してオブジェクトの存在を確認できます。

try:
  obj1 = obj2.reference
except db.ReferencePropertyResolveError:
  # Referenced entity was deleted or never existed.

ReferenceProperty には、後方参照というもう 1 つの便利な機能があります。モデルに別のモデルに対する ReferenceProperty がある場合、参照されている各エンティティは、値が Query のプロパティを取得します。このプロパティは、そのエンティティを参照している最初のモデルのすべてのエンティティを返します。

# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for obj in obj1.secondmodel_set:
    # ...

後方参照プロパティのデフォルトの名前は modelname_set(小文字のモデルクラス名の後ろに「_set」を追加したもの)で、ReferenceProperty コンストラクタに対する collection_name 引数を使用して調整できます。

同じモデルクラスを参照する複数の ReferenceProperty 値がある場合、後方参照プロパティのデフォルトの構造ではエラーが発生します。

class FirstModel(db.Model):
    prop = db.IntegerProperty()

# This class raises a DuplicatePropertyError with the message
# "Class Firstmodel already has property secondmodel_set"
class SecondModel(db.Model):
    reference_one = db.ReferenceProperty(FirstModel)
    reference_two = db.ReferenceProperty(FirstModel)

このエラーを防ぐには、collection_name 引数を明示的に設定する必要があります。

class FirstModel(db.Model):
    prop = db.IntegerProperty()

# This class runs fine
class SecondModel(db.Model):
    reference_one = db.ReferenceProperty(FirstModel,
        collection_name="secondmodel_reference_one_set")
    reference_two = db.ReferenceProperty(FirstModel,
        collection_name="secondmodel_reference_two_set")

モデル インスタンスの自動的な参照と逆参照、型の確認、後方参照は、ReferenceProperty モデル プロパティ クラスを使用した場合にのみ利用できます。Expando 動的プロパティの値や ListProperty 値として格納されたキーには、これらの機能はありません。