Pemodelan Data di Python

Catatan: Developer yang membangun aplikasi baru sangat dianjurkan untuk menggunakan Library Klien NDB, yang memiliki beberapa manfaat dibandingkan dengan library klien ini, seperti menyimpan entity dalam cache secara otomatis melalui Memcache API. Jika saat ini Anda menggunakan Library Klien DB versi lama, baca Panduan Migrasi DB ke NDB

Ringkasan

Entity datastore memiliki kunci dan kumpulan properti. Sebuah aplikasi menggunakan datastore API untuk menentukan model data, dan membuat instance dari model tersebut untuk disimpan sebagai entity. Model memberikan struktur umum untuk entity yang dibuat oleh API, dan dapat menentukan aturan untuk memvalidasi nilai properti.

Class Model

Class Model

Aplikasi menjelaskan jenis data yang digunakannya dengan model. Model adalah class Python yang diwarisi dari class Model. Class model menentukan Jenis entity datastore baru dan properti yang diharapkan untuk diambil oleh Jenis. Nama Jenis ditentukan oleh nama class ter-instance yang diwarisi dari db.Model.

Properti model ditentukan menggunakan atribut class pada class model. Setiap atribut class adalah instance subclass dari class Properti, biasanya salah satu dari class properti yang disediakan. Instance properti menyimpan konfigurasi untuk properti, seperti apakah properti diperlukan agar instance valid atau tidak, atau nilai default yang akan digunakan untuk instance jika tidak ada yang disediakan.

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()

Entity dari salah satu jenis entity yang ditentukan diwakili dalam API oleh instance class model terkait. Aplikasi dapat membuat entity baru dengan memanggil konstruktor class. Aplikasi mengakses dan memanipulasi properti entity menggunakan atribut instance. Konstruktor instance model menerima nilai awal untuk properti sebagai argumen kata kunci.

from google.appengine.api import users

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

Catatan: Atribut class model adalah konfigurasi untuk properti model, yang nilainya adalah instance Properti. Atribut instance model adalah nilai properti sebenarnya, yang nilainya adalah jenis yang diterima oleh class Properti.

Class Model menggunakan instance Properti untuk memvalidasi nilai yang ditetapkan ke atribut instance model. Validasi nilai properti terjadi ketika instance model pertama kali dibuat, dan saat atribut instance diberi nilai baru. Hal ini memastikan bahwa properti tidak boleh memiliki nilai yang tidak valid.

Karena validasi terjadi saat instance dibuat, setiap properti yang dikonfigurasi agar diperlukan harus diinisialisasi dalam konstruktor. Dalam contoh ini, name dan type adalah nilai yang diperlukan sehingga nilai awalnya ditentukan dalam konstruktor. weight_in_pounds tidak diperlukan oleh model, jadi model akan memulai tidak ditetapkan, lalu diberi nilai nanti.

Instance model yang dibuat menggunakan konstruktor tidak ada di datastore hingga "dimasukkan" untuk pertama kalinya.

Catatan: Seperti halnya semua atribut class Python, konfigurasi properti model diinisialisasi saat skrip atau modul pertama kali diimpor. Karena App Engine meng-cache modul yang diimpor di antara permintaan, konfigurasi modul dapat diinisialisasi selama permintaan untuk satu pengguna, dan digunakan kembali selama permintaan untuk pengguna lainnya. Jangan lakukan inisialisasi konfigurasi properti model, seperti nilai default, dengan data khusus untuk permintaan atau pengguna saat ini. Lihat Pembuatan Cache Aplikasi untuk informasi selengkapnya.

Class Expando

Model yang ditentukan menggunakan class Model menetapkan kumpulan properti tetap yang harus setiap instance class miliki (mungkin dengan nilai default). Ini adalah cara yang berguna untuk membuat model objek data, tetapi datastore tidak mengharuskan setiap entity jenis tertentu memiliki kumpulan properti yang sama.

Terkadang, memiliki properti yang tidak selalu seperti properti entity lain dari jenis yang sama akan berguna bagi entity. Entity tersebut diwakili dalam datastore API oleh model "expando". Class model expando membuat subclass dari superclass Expando. Setiap nilai yang ditetapkan ke atribut instance model expando menjadi properti entity datastore, menggunakan nama atribut tersebut. Properti ini dikenal sebagai properti dinamis. Properti yang ditentukan menggunakan instance class Properti di atribut class merupakan properti tetap.

Model expando dapat memiliki properti tetap dan dinamis. Class model hanya menetapkan atribut class dengan objek konfigurasi Properti untuk properti tetap. Aplikasi membuat properti dinamis saat menetapkan nilai ke properti tersebut.

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

Properti dinamis tidak memiliki definisi properti model, maka properti dinamis tidak divalidasi. Semua properti dinamis dapat memiliki nilai dari salah satu jenis dasar datastore, termasuk None. Dua entity jenis yang sama dapat memiliki jenis nilai yang berbeda untuk properti dinamis yang sama, dan satu entity dapat membiarkan properti yang tidak ditetapkan yang ditetapkan lainnya.

Tidak seperti properti tetap, properti dinamis tidak harus ada. Properti dinamis dengan nilai None berbeda dengan properti dinamis yang tidak ada. Jika instance model expando tidak memiliki atribut untuk suatu properti, entity data yang sesuai tidak akan memiliki properti tersebut. Anda dapat menghapus properti dinamis dengan menghapus atribut tersebut.

Atribut yang namanya diawali dengan garis bawah (_) tidak disimpan ke entity datastore. Hal ini memungkinkan Anda menyimpan nilai pada instance model untuk penggunaan internal sementara tanpa memengaruhi data yang disimpan dengan entity.

Catatan: Properti statis akan selalu disimpan ke entity datastore, terlepas dari apakah properti tersebut adalah Expando, Model, atau dimulai dengan garis bawah (_).

del p.chess_elo_rating

Kueri yang menggunakan properti dinamis dalam filter hanya menampilkan entity yang nilainya untuk properti dari jenis yang sama dengan nilai yang digunakan dalam kueri. Demikian pula, kueri hanya menampilkan entity dengan kumpulan properti tersebut.

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

Catatan: Contoh di atas menggunakan kueri di seluruh entity group, yang mungkin menampilkan hasil yang tidak berlaku. Untuk hasil yang sangat konsisten, gunakan kueri ancestor dalam entity group.

Class Expando adalah subclass dari class Model, dan mewarisi semua metodenya.

Class PolyModel

Python API menyertakan class lain untuk pemodelan data yang memungkinkan Anda menentukan hierarki class, dan melakukan kueri yang dapat menampilkan entity class tertentu atau salah satu subclass-nya. Model dan kueri semacam itu disebut "polimorfik", karena memungkinkan instance dari satu class menjadi hasil untuk kueri class induk.

Contoh berikut menentukan class Contact, dengan Person dan Company subclass:

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()

Model ini memastikan bahwa semua entity Person dan semua entity Company memiliki properti phone_number dan address, dan kueri untuk entity Contact dapat menampilkan entity Person atau Company. Hanya entity Person yang memiliki properti mobile_number.

Instance untuk subclass dapat dibuat seperti class model lainnya:

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()

Kueri untuk entity Contact dapat menampilkan instance Contact, Person, atau Company. Kode berikut mencetak informasi untuk kedua entity yang dibuat di atas:

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

Kueri untuk entity Company hanya menampilkan instance Company:

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

Untuk saat ini, model polimorfik tidak boleh diteruskan ke konstruktor class Query secara langsung. Sebagai gantinya, gunakan metode all(), seperti dalam contoh di atas.

Untuk mengetahui informasi selengkapnya tentang cara menggunakan model polimorf dan cara menerapkannya, lihat Class PolyModel.

Class dan Jenis Properti

Datastore mendukung serangkaian jenis nilai yang tetap untuk properti entity, termasuk string Unicode, bilangan bulat, angka floating point, tanggal, kunci entity, string byte (blob), dan berbagai jenis GData. Setiap jenis nilai datastore memiliki class Properti yang sesuai yang disediakan oleh modul google.appengine.ext.db.

Jenis dan Class Properti menjelaskan semua jenis nilai yang didukung dan class Propertinya yang sesuai. Beberapa jenis nilai khusus dijelaskan di bawah.

String dan Blob

Datastore mendukung dua jenis nilai untuk menyimpan teks: string teks pendek dengan panjang hingga 1.500 byte dan string teks yang panjang dengan panjang hingga satu megabyte. String pendek diindeks dan dapat digunakan dalam kondisi filter kueri dan tata urutan. String panjang tidak diindeks dan tidak dapat digunakan dalam kondisi filter atau tata urutan.

Nilai string pendek dapat berupa nilai unicode atau nilai str. Jika nilainya adalah str, encoding 'ascii' akan digunakan. Guna menentukan encoding yang berbeda untuk nilai str, Anda dapat mengonversinya menjadi nilai unicode dengan konstruktor jenis unicode(), yang menggunakan str dan nama encoding sebagai argumen. String pendek dapat dimodelkan menggunakan class 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")

Nilai string panjang diwakili oleh instance db.Text. Konstruktornya mengambil nilai unicode, atau nilai str, dan secara opsional nama encoding yang digunakan dalam str. String panjang dapat dimodelkan menggunakan class 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")

Datastore juga mendukung dua jenis serupa untuk string byte nonteks: db.ByteString dan db.Blob. Nilai-nilai ini berupa string byte mentah, dan tidak diperlakukan sebagai teks yang dienkode (seperti UTF-8).

Seperti nilai db.StringProperty, nilai db.ByteString akan diindeks. Seperti properti db.TextProperty, nilai db.ByteString dibatasi hingga 1.500 byte. Instance ByteString mewakili string byte pendek, dan menggunakan nilai str sebagai argumen untuk konstruktornya. String byte dimodelkan menggunakan class ByteStringProperty.

Seperti db.Text, nilai db.Blob dapat sebesar satu megabyte, tetapi tidak diindeks, dan tidak dapat digunakan dalam filter kueri atau tata urutan. Class db.Blob menggunakan nilai str sebagai argumen untuk konstruktornya, atau Anda dapat menetapkan nilai secara langsung. Blob dimodelkan menggunakan class BlobProperty.

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

obj = MyModel()

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

Daftar

Properti dapat memiliki beberapa nilai, yang diwakili dalam datastore API sebagai list Python. Daftar dapat berisi nilai dari salah satu jenis nilai yang didukung oleh datastore. Satu properti daftar bahkan dapat memiliki nilai dari jenis yang berbeda.

Urutan umumnya dipertahankan, sehingga saat entity ditampilkan oleh kueri dan get(), nilai properti daftar berada dalam urutan yang sama seperti saat disimpan. Ada satu pengecualian untuk hal ini: nilai Blob dan Text dipindahkan ke akhir daftar; namun, mereka mempertahankan urutan aslinya relatif terhadap satu sama lain.

Class ListProperty membuat model daftar, dan menerapkan bahwa semua nilai dalam daftar berasal dari jenis tertentu. Untuk memudahkan, library ini juga menyediakan StringListProperty, mirip dengan ListProperty(basestring).

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.

Kueri dengan filter pada properti daftar menguji setiap nilai dalam daftar satu per satu. Entity akan cocok dengan kueri hanya jika beberapa nilai dalam daftar meneruskan semua filter di properti tersebut. Lihat halaman Kueri Datastore untuk informasi selengkapnya.

# 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")

Filter kueri hanya beroperasi pada anggota daftar. Tidak ada cara untuk menguji dua daftar untuk kesamaan dalam filter kueri.

Secara internal, datastore mewakili nilai properti daftar sebagai beberapa nilai untuk properti. Jika nilai properti daftar adalah daftar kosong, berarti properti tersebut tidak memiliki representasi di datastore. Datastore API memperlakukan situasi ini secara berbeda untuk properti statis (dengan ListProperty) dan properti dinamis:

  • ListProperty statis dapat diberi daftar kosong sebagai nilai. Properti tidak ada di datastore, tetapi instance model berperilaku seolah-olah nilainya adalah daftar kosong. ListProperty statis tidak boleh memiliki nilai None.
  • Properti dinamis dengan nilai list tidak dapat diberi nilai daftar kosong. Namun, class dapat memiliki nilai None, dan dapat dihapus (menggunakan del).

Model ListProperty menguji bahwa nilai yang ditambahkan ke daftar adalah jenis yang benar, dan menampilkan BadValueError jika bukan. Pengujian ini terjadi (dan berpotensi gagal) bahkan saat entity yang disimpan sebelumnya diambil dan dimuat ke dalam model. Karenastr nilai dikonversi menjadi nilai unicode (seperti teks ASCII) sebelum penyimpanan, ListProperty(str) diperlakukan sebagaiListProperty(basestring), jenis data Python yang menerima nilai str dan unicode. Anda juga dapat menggunakan StringListProperty() untuk tujuan ini.

Untuk menyimpan string byte nonteks, gunakan nilai db.Blob. Byte string blob dipertahankan saat disimpan dan diambil. Anda dapat mendeklarasikan properti yang merupakan daftar blob sebagai ListProperty(db.Blob).

Properti daftar dapat berinteraksi dengan tata urutan melalui cara yang tidak biasa; lihat halaman Kueri Datastore untuk mengetahui detailnya.

Referensi

Nilai properti dapat berisi kunci entity lain. Nilainya adalah instance Key.

Class ReferenceProperty membuat model nilai kunci, dan menerapkan bahwa semua nilai merujuk ke entity jenis tertentu. Untuk memudahkan, library juga menyediakan SelfReferenceProperty, setara dengan ReferenceProperty yang merujuk pada jenis yang sama seperti entity dengan properti.

Menetapkan instance model ke properti ReferenceProperty akan otomatis menggunakan kuncinya sebagai nilai.

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()

Nilai properti ReferenceProperty dapat digunakan seolah-olah merupakan instance model entity yang direferensikan. Jika entity yang direferensikan tidak ada dalam memori, penggunaan properti sebagai instance akan otomatis mengambil entity dari datastore. ReferenceProperty juga menyimpan kunci, tetapi penggunaan properti akan menyebabkan entity terkait dimuat.

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

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

Jika kunci mengarah ke entity yang tidak ada, maka mengakses properti akan menghasilkan error. Jika aplikasi memperkirakan bahwa referensi dapat menjadi tidak valid, aplikasi dapat menguji keberadaan objek menggunakan blok try/except:

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

ReferenceProperty memiliki fitur berguna lainnya: referensi kembali. Jika model memiliki ReferenceProperty ke model lain, setiap entity yang direferensikan akan mendapatkan properti yang nilainya adalah Query yang menampilkan semua entity model pertama yang mereferensikannya.

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

Nama properti referensi kembali ditetapkan secara default ke modelname_set (dengan nama class model dalam huruf kecil, dan "_set" ditambahkan ke akhir), dan dapat disesuaikan menggunakan argumen collection_name ke konstruktor ReferenceProperty.

Jika Anda memiliki beberapa nilai ReferenceProperty yang merujuk ke class model yang sama, konstruksi default properti referensi kembali akan menghasilkan error:

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)

Untuk menghindari error ini, Anda harus menyetel argumen collection_name secara eksplisit:

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")

Referensi dan dereferensi otomatis instance model, pemeriksaan jenis, dan referensi balik hanya tersedia menggunakan class properti model ReferenceProperty. Kunci yang disimpan sebagai nilai properti dinamis Expando atau nilai ListProperty tidak memiliki fitur ini.