Class Property
didesain untuk dijadikan subclass.
Namun, biasanya lebih mudah untuk membuat subclass dari subclass
Property
yang sudah ada.
Semua atribut Property
khusus,
bahkan yang dianggap 'publik',
memiliki nama yang diawali dengan garis bawah.
Ini karena StructuredProperty
menggunakan namespace atribut non-garis bawah untuk merujuk ke nama
Property
bertingkat; ini penting untuk menentukan
kueri pada sub-properti.
Class Property
dan subclass-nya yang telah ditetapkan memungkinkan
subclassing menggunakan API validasi dan
konversi composable (atau stackable). Tindakan ini memerlukan beberapa definisi terminologi:
- Nilai pengguna adalah nilai yang akan ditetapkan dan diakses oleh kode aplikasi menggunakan atribut standar pada entity.
- Nilai dasar adalah nilai yang akan diserialisasi ke dan dideserialisasi dari Datastore.
Subclass Property
yang mengimplementasikan transformasi
tertentu antara nilai pengguna dan nilai yang dapat diserialisasi harus
mengimplementasikan dua metode, _to_base_type()
dan
_from_base_type()
.
Metode ini tidak boleh memanggil
metode super()
.
Inilah yang dimaksud dengan API composable (atau stackable).
API mendukung class stacking dengan konversi basis pengguna
yang lebih canggih: konversi pengguna ke basis
berubah dari lebih canggih menjadi kurang canggih, sedangkan
konversi basis ke pengguna berubah dari kurang canggih menjadi lebih canggih. Misalnya, lihat hubungan antara
BlobProperty
, TextProperty
,
dan StringProperty
.
Misalnya, TextProperty
mewarisi dari
BlobProperty
; kodenya cukup sederhana karena mewarisi
sebagian besar perilaku yang dibutuhkan.
Selain _to_base_type()
dan
_from_base_type()
, metode
_validate()
juga merupakan API composable.
API validasi membedakan antara nilai pengguna lax dan strict. Kumpulan nilai lax adalah superset dari kumpulan nilai
strict. Metode _validate()
mengambil nilai lax dan jika perlu
mengonversinya menjadi nilai strict. Ini berarti bahwa nilai lax diterima ketika menetapkan nilai properti, dan hanya nilai ketat yang akan ditampilkan ketika mendapatkan nilai properti. Jika tidak ada
konversi yang diperlukan, _validate()
dapat menampilkan Tidak ada. Jika argumen berada di luar kumpulan nilai lax yang diterima, _validate()
harus memunculkan pengecualian, sebaiknya TypeError
atau datastore_errors.BadValueError
.
_validate()
, _to_base_type()
,
dan _from_base_type()
tidak perlu menangani:
None
: Ketiganya tidak akan dipanggil denganNone
(dan jika menampilkan Tidak ada, artinya nilai tidak memerlukan konversi).- Nilai berulang: Infrastruktur akan menangani pemanggilan
_from_base_type()
atau_to_base_type()
untuk setiap item daftar dalam nilai berulang. - Membedakan nilai pengguna dari nilai dasar: Infrastruktur menangani hal ini dengan memanggil API composable.
- Perbandingan: Operasi perbandingan memanggil
_to_base_type()
pada operand-nya. - Membedakan antara nilai pengguna dan nilai dasar: Infrastruktur menjamin bahwa
_from_base_type()
akan dipanggil dengan nilai dasar (tidak digabungkan), dan_to_base_type()
tersebut akan dipanggil dengan nilai pengguna.
Misalnya, Anda perlu menyimpan bilangan bulat yang sangat panjang.
IntegerProperty
standar hanya mendukung bilangan bulat 64-bit (ditandatangani).
Properti Anda mungkin menyimpan bilangan bulat yang lebih panjang sebagai string; ada baiknya jika class properti
menangani konversi.
Aplikasi yang menggunakan class properti Anda mungkin terlihat seperti ini
...
...
...
...
Ini terlihat sederhana dan mudah. Contoh ini juga menunjukkan
penggunaan beberapa opsi properti standar (default, berulang). Sebagai
penulis LongIntegerProperty
, Anda akan senang
mengetahui bahwa Anda tidak perlu menulis "boilerplate" apa pun agar
berfungsi. Lebih mudah untuk mendefinisikan subclass dari properti lain, misalnya:
Saat Anda menetapkan nilai properti pada entity, misalnya ent.abc = 42
, metode _validate()
Anda akan dipanggil, dan (jika tidak memunculkan pengecualian) nilai tersebut disimpan di entity. Saat Anda menulis entity ke Datastore, metode _to_base_type()
akan dipanggil, dengan mengonversi nilai menjadi string. Kemudian, nilai tersebut akan diserialisasi oleh class dasar,
StringProperty
.
Rantai peristiwa terbalik terjadi saat entity dibaca kembali dari Datastore. Class StringProperty
dan Property
bersama-sama menangani detail lainnya, seperti melakukan serialisasi
dan deserialisasi string, menetapkan default, serta menangani
nilai properti berulang.
Dalam contoh ini, mendukung ketidaksetaraan (yaitu kueri yang menggunakan <, <=, >, >=) memerlukan upaya lebih. Contoh implementasi berikut menerapkan ukuran maksimum bilangan bulat dan menyimpan nilai sebagai string panjang tetap:
Ini dapat digunakan dengan cara yang sama seperti LongIntegerProperty
,
kecuali bahwa Anda harus meneruskan jumlah bit ke konstruktor properti,
misalnya BoundedLongIntegerProperty(1024)
.
Anda dapat membuat subclass jenis properti lain dengan cara yang sama.
Pendekatan ini juga berfungsi untuk menyimpan data terstruktur.
Misalkan Anda memiliki class Python FuzzyDate
yang mewakili
rentang tanggal; metode ini menggunakan kolom first
dan last
untuk menyimpan awal dan akhir rentang tanggal:
...
Anda dapat membuat FuzzyDateProperty
yang berasal dari
StructuredProperty
. Sayangnya, kode yang terakhir tidak
berfungsi dengan class Python lama biasa; kode ini memerlukan subclass Model
.
Jadi, definisikan subclass Model sebagai representasi perantara;
Selanjutnya, buat subclass StructuredProperty
yang meng-hardcode argumen modelclass menjadi FuzzyDateModel
, serta menentukan metode _to_base_type()
dan _from_base_type()
untuk mengonversi antara FuzzyDate
dan
FuzzyDateModel
:
Aplikasi dapat menggunakan class ini seperti berikut:
...
Misalkan Anda ingin menerima objek date
biasa
selain objek FuzzyDate
sebagai nilai untuk
FuzzyDateProperty
. Untuk melakukannya, ubah metode _validate()
sebagai berikut:
Sebagai gantinya, Anda dapat membuat subclass FuzzyDateProperty
sebagai berikut
(dengan asumsi FuzzyDateProperty._validate()
seperti yang ditampilkan di atas).
Saat Anda menetapkan nilai ke kolom MaybeFuzzyDateProperty
, MaybeFuzzyDateProperty._validate()
dan FuzzyDateProperty._validate()
akan dipanggil dalam urutan tersebut.
Hal yang sama berlaku untuk _to_base_type()
dan
_from_base_type()
: metode di dalam
superclass dan subclass digabungkan secara implisit.
(Jangan gunakan super
untuk mengontrol perilaku yang diwariskan.
Untuk ketiga metode ini,
interaksinya halus dan super
tidak melakukan apa yang Anda inginkan.)