Google App Engine DB Datestore簡介

*將Python Tutorial專案分解成Django樣式範例程式

Google App Engine 提供一套易於延展的資料存放區,它是以Big-Table為基礎,與目前關聯式資料庫不同,在儲存設備上,資料物件稱為"資料實體"(entity),資料物件中有欄位資料,稱為該實體中的屬性(property),每一個資料實體的屬性,可以根據資料存放區所提供的資料型態來定義資料欄位。

資料欄位型態的定義可參考 https://developers.google.com/appengine/docs/python/datastore/entities

在程式中要表式資料實體的類別,可以用"模型"(model)程式來表示。

在程式碼中,所建立用來產生資料實體的類別必須繼承自db.Model母類別,資料實體可利用類別的方法put()來儲存資料。

from google.appengine.ext import db

class Account(db.Model):
    name=db.StringProperty(required=True)
    email=db.EmailProperty(required=True)
    created=db.DateTimeProperty(auto_now_add=True)

account=Account(name='Eric',email='litong@must.edu.tw')
account.put()

print account.created



Model欄位的資料型能定義


每一個類別中的資料欄位型態必須正確的定義,才能將資料正確的儲存。以下介紹幾種資料欄位的型態。


  • StringProperty

儲存文字資料,可單行或多行文字,資料大小不可超過500bytes。其選項multiline可為True或False,用來設定是否可儲存多行文字,若設定為False,則儲存的資料就不能有換行字元\r或\n,預設為False

from google.appengine.ext import db

class StringSample(db.Model):
    title=db.StringProperty(required=True)
    content=db.StringProperty(multiline=True,default='')
    created=db.DateTimeProperty(auto_now_add=True)

stringSample=StringSample(title='string property sample',content='''
Hi this is a String Property
Sample''')
stringSample.put()

print stringSample.content
print stringSample.created

  • ByteStringProperty

是用來儲存500byte大小以內的資料,可以是純文字或二進位資料,與StringProperty不同的地方在於,在ByteStringProperty欄位內的資料是尚未使用文字編碼過的byte資料。

from google.appengine.ext import db

class ByteStringSample(db.Model):
    content=db.ByteStringProperty(required=True)

byteStringSample=ByteStringSample(content='sometext')
byteStringSample.put()

print byteStringSample.content

  • TextProperty

是用來儲存較長的文字資料,但它無法製作索引、排序與條件比對。

from google.appengine.ext import db

class TextSample(db.Model):
    content=db.TextProperty()

textSample=TextSample(content=u'sometext')
textSample.put()

print textSample.content


  • BooleanProperty

儲存True或是False的資料型態
import datetime
from google.appengine.ext import db


class BooleanSample(db.Model):
    enabled=db.BooleanProperty(required=True,default=False)

booleanSample=BooleanSample()
booleanSample.put()

print booleanSample.enabled
booleanSample.enabled=True
booleanSample.put()
print booleanSample.enabled


  • IntegerProperty


儲存整數型態的資料

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.IntegerProperty(required=True,default=0)

sample=Sample(value=123)
sample.put()
print sample.value



  • FloatProperty


儲存浮點數資料型態

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.FloatProperty(required=True,default=0.0)

sample=Sample(value=123.456)
sample.put()
print sample.value


  • DateTimeProperty、DateProperty、TimeProperty


用來儲存日期及時間資料型態的資料,需要使用python中的datetime.datetime、datetime.date或是datetime.time物件。
其選項

  1. auto_now_add:可為True或False,來設定物件實體產生時,是否會自動寫入目前的日期或時間。
  2. auto_now:可為True或False,來設定物件實體更新時,是否會自動寫入目前的日期或時間。

import datetime
from google.appengine.ext import db
import datetime


class Sample(db.Model):
    setDay=db.DateProperty()
    created=db.DateTimeProperty(auto_now_add=True)
    updated=db.DateTimeProperty(auto_now=True)

sample=Sample()
sample.setDay=datetime.date(2014,7,5)
sample.put()
print sample.key(),sample.setDay,sample.created,sample.updated

getSample=db.get(sample.key())
getSample.setDay=datetime.date(2009,1,1)
getSample.put()
print getSample.key(),getSample.setDay,getSample.created,getSample.updated


  • ListProperty、StringListProperty


可以用來儲存list的資料型態,在ListProperty 中可放入要存放list內元素的資料型態,StringListProperty則無此選項。

import datetime
from google.appengine.ext import db
import datetime


class Sample(db.Model):
    order=db.ListProperty(int)
    tags=db.StringListProperty()

sample=Sample()
sample.order=[1,2,4,5,7,2]
sample.tags=[u'item',u'bar',u'data']
sample.put()
print sample.order,sample.tags


  • ReferenceProperty、SelfReferenceProperty


用來指向其它資料實體的資料欄位,儲存資料的資料型態為db.Key物件,可以使用這個欄位來作出各實體物件間的關聯。其選項:

  1. reference_class:為參考資料實體的資料模型
  2. collection_name:在參考資料模型中建立的list資料欄位名稱,若不指定則會使用"模型名稱_set"來作為資料欄位名稱。

import datetime
from google.appengine.ext import db

class User(db.Model):
    name=db.StringProperty()


class Article(db.Model):
    author=db.ReferenceProperty(reference_class=User)
    title=db.StringProperty()
    content=db.TextProperty()

u=User(name='Eric')
u.put()

article1=Article(author=u,title='title1',content='content1')
article1.put()

article2=Article(author=u,title='title2',content='content2')
article2.put()

article3=Article(author=u,title='title3',content='content3')
article3.put()

print article1.author.name



user=db.get('ahJkZXZ-Z2Fld2VhdGhlcnJpc2tyEQsSBFVzZXIYgICAgIDkmQkM')
print user.name

for article in u.article_set:
    print 'Article: %s'%article.title

但若有兩個資料欄位都要參考到同一個資料模型時,就會發生錯誤。

class Article(db.Model):
    author=db.ReferenceProperty(reference_class=User)
    reviewer=db.ReferenceProperty(reference_class=User)

就必須要設定不同的collection_name

import datetime
from google.appengine.ext import db

class User(db.Model):
    name=db.StringProperty()


class Article(db.Model):
    author=db.ReferenceProperty(reference_class=User,collection_name='writings')
    reviewer=db.ReferenceProperty(reference_class=User,collection_name='reviews')
    title=db.StringProperty()
    content=db.TextProperty()

'''
u1=User(name='Eric1')
u1.put()
u2=User(name='Eric2')
u2.put()

article1=Article(author=u1,reviewer=u1,title='title1',content='content1')
article1.put()

article2=Article(author=u2,reviewer=u1,title='title2',content='content2')
article2.put()

article3=Article(author=u1,reviewer=u2,title='title3',content='content3')
article3.put()

article4=Article(author=u2,reviewer=u2,title='title4',content='content4')
article4.put()

'''

u=db.get('ahJkZXZ-Z2Fld2VhdGhlcnJpc2tyEQsSBFVzZXIYgICAgIDUkQgM')
print dir(u.writings.order)

for article in u.writings.order('title'):
    print article.title


========================================================

如果要參考到相同的資料模型,可以使用SelfReferenceProperty,並用collection_name來設定欄位名稱。


class User(db.Model):
    name=db.StringProperty()
    employer=db.SelfReferenceProperty(collection_name='employees')



當資料儲存之後,GAE會配給每一個資料實體一個Key值(是經過加密的),這個值內容包含資料種類及ID,另外您也可以自己命名一個獨一無二的鍵值名稱key_name。ID值則是系統自動配發的數字。

由上述的例子我們可以得知,資料的儲存可以利用put( )的方法,資料的取得可以利用db.get( )的方法,其中必須加入資料實體的key值:

#u_key 為某個資料實體的key值
db.get(u_key)

同樣的,資料實體的刪除,我們可以利用db.delete( )的方法

#u_key 為某個資料實體的key值
db.delete(u_key)



資料的操作

  • 資料實體的建立

資料實體entity的建立作法,是必須要先定義資料模型的類別,然後再去產生資料物件。產生資料物件的方法有二:

  • 直接在物件產生的同時就定義欄位值

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.IntegerProperty(default=0)

sample=Sample(value=123)
sample.put()
print sample.value



  • 先產生物件再指定欄位值


import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.IntegerProperty(default=0)

sample=Sample()
sample.value=456
sample.put()
print sample.value


資料的儲存可以利用物件類別put( )的方法或是透過db.put( )的方法

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.IntegerProperty(default=0)

sample=Sample()
sample.value=777
db.put(sample)
print sample.value

透過Query物件來取得資料實體

我們可以透過資料模型的all( )方法取得一個Query物件,然後利用fetch(limit,offset)的方法來取得我們所要的資料。

from google.appengine.ext import db

class User(db.Model):
    name=db.StringProperty()


class Article(db.Model):
    author=db.ReferenceProperty(reference_class=User)
    title=db.StringProperty()
    content=db.TextProperty()

query=Article.all()
articles=query.fetch(2,0)

for article in articles:
    print article.title


我們可以利用filter( )方法來比對我們想要的資料

query=Article.all()
query.filter('title =','title1')
articles=query.fetch(2,0)

for article in articles:
    print article.title

==============

query=Article.all()
query.filter('title >','title1')
articles=query.fetch(2,0)

for article in articles:
    print article.title


我們可以利用order( )方法來排序資料

query=Article.all()
query.order('title')
articles=query.fetch(3,0)

for article in articles:
    print article.title

或是order內的參數加減號(- )取得相反順序的排序資料。

query=Article.all()
query.order('-title')
articles=query.fetch(3,0)

for article in articles:
    print article.title


GQL查詢語言

除了利用資料模型的all( )方法產生Query物件來讀取資料外,我們也可以使用一組資料查詢語言GQL來讀取資料。GQL是一個與SQL相似的查詢語言,目前GQL只提供資料的查詢讀取,不支援資料新增、刪除與修改。
from google.appengine.ext import db

class User(db.Model):
    name=db.StringProperty()


class Article(db.Model):
    author=db.ReferenceProperty(reference_class=User)
    title=db.StringProperty()
    content=db.TextProperty()

query=db.GqlQuery("SELECT * FROM Article WHERE title = 'title1'")
articles=query.fetch(10)
print articles

for article in articles:
    print article.title,article.content

================================================
可以使用參數編號或指定名稱來進行條件比對


query=db.GqlQuery("SELECT * FROM Article WHERE title = :1",'title1')

or

query=db.GqlQuery("SELECT * FROM Article WHERE title = :title",title='title1')

=======================================================
也可以透過資料模型來操作GQL,在方法中就不必再加入資料種類。

query=Article.gql("WHERE title = 'title1'")

or

query=Article.gql("WHERE title = :1",'title1')

or

query=Article.gql("WHERE title = :title",title='title1')


在GQL中可以指定LIMIT及OFFSET關鍵字,回傳的資料就直接是list了,而不是Query物件,因此就可以不必再呼叫fetch方法了

articles=Article.gql("WHERE title = :1 LIMIT 10 OFFSET 0",'title1')


另外,你也可以直接讀取資料的key值,以節省查詢的時間

keys=db.Query(Article,keys_only=True)

print keys
for key in keys:
    print key



keys=db.GqlQuery('SELECT __key__ FROM Article')



鍵值名稱

一般來說鍵值是經過加密過後所產生的一串無義意的編碼字串,即便內部包含資料種類的相關資訊,我們也無法從鍵值直接看出來,我們可以利用鍵值名稱key_name來自行命名每一個資料實體名稱,之後透過get_by_key_name( )方法來將資料取出。

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.FloatProperty(required=True,default=0.0)

sample=Sample(key_name='555.5_sample',value=555.5)
sample.put()

sample=Sample.get_by_key_name('555.5_sample')
print sample.value

鍵值名稱與鍵值相同,必須是唯一的字串,使用鍵值名稱的好處是命名規則可以自訂,方便程式讀取資料實體。

刪除資料

要刪除資料可以用db.delete( )或物件實體delete( )方法來將資料刪除

db.delete(sample)

or

sample.delete()

您也可以使用db.delete( )的方法來刪除多筆資料(list)

import datetime
from google.appengine.ext import db


class Sample(db.Model):
    value=db.FloatProperty(required=True,default=0.0)

'''
sample1=Sample(key_name='555.5_sample',value=555.5)
sample1.put()

sample2=Sample(key_name='666.6_sample',value=666.6)
sample2.put()

print 'befere delete'
query=Sample.all()
query.filter('value >',555.0)
samples=query.fetch(10)

for sample in samples:
    print sample.value

'''
db.delete(samples)

print 'after delete'
query=Sample.all()
query.filter('value >',555.0)
samples=query.fetch(10)
for sample in samples:
    print sample.value


一對一(多)與多對多資料關聯

  • 一對一資料關聯

當你在設計郵件或電話通訊錄時,每一個使用者user可能會有一個以上的郵件或電話,這時你可以使用一對一關聯性將這些資料收集起來。

from google.appengine.ext import db

class User(db.Model):
    strName=db.StringProperty()

class Email(db.Model):
    user=db.ReferenceProperty(User,collection_name='emails')
    strType=db.StringProperty(required=True)
    emailAddr=db.EmailProperty(required=True)

u=User(strName='eric',key_name='eric_weng')
u.put()

office_mail=Email(user=u,strType='Office',emailAddr=db.Email('eric1@office.com'))
office_mail.put()
personal_mail=Email(user=u,strType='Personal',emailAddr=db.Email('eric2@personal.com'))
personal_mail.put()

在Email的資料模型中,有一個ReferenceProperty資料型態user指向類別User資料模型,並且在有一個emailAddr欄位來記錄email相關資料,如此就這兩個資料模型就具有一對多的關聯性。




我們可以透過user資料實體來取得與它相關的email資料實體,如下所示:

u=User.get_by_key_name('eric_weng')
for email in u.emails:
    print 'strType:%s,    emailAddr:%s'%(email.strType,email.emailAddr)

另外我們也可以從email資料實體來取得與它相關的user資料實體

u=User.get_by_key_name('eric_weng')
for email in u.emails:
     print 'strType:%s,    emailAddr:%s,    owner:%s'%(email.strType,email.emailAddr,email.user.strName)


  • 多對多關聯

當你在設計文章系統時,可能會有一篇文章屬於多個分類及一個分類有多篇文章之類的情形,此時你就需要將不同的資料模型建立多對多關聯。

from google.appengine.ext import db

class Article(db.Model):
    strTitle=db.StringProperty()
    strContent=db.TextProperty()
    lstCategories=db.ListProperty(db.Key)
    created=db.DateTimeProperty(auto_now_add=True)

class Category(db.Model):
    strName=db.StringProperty()
    strDescription=db.StringProperty(multiline=True)
 
    @property
    def getArticles(self):
        return Article.gql('WHERE lstCategories = :1',self.key())

上述例子中,Article資料模型使用了ListProperty的資料型態來記錄文章分類Category的key值,我們可以透過list取得每一個Category資料實體。
在Category的資料模型中,我們則建立一個getArticles的屬性,它可以根據Category資料實體的key值查詢到含有該鍵值的所有Article資料實體。
由此可知,對ListProperty欄位做" = "的運算,可以查詢到該資料是否存在list之中。

category1=Category(strName='Science',strDescription='About Science')
category1.put()
category2=Category(strName='Information',strDescription='About Information')
category2.put()

article1=Article(strTitle='Article1',strContent=db.Text('Article1 Content'))
article1.lstCategories=[category1.key(),category2.key()]
article1.put()

article2=Article(strTitle='Article2',strContent=db.Text('Article2 Content'))
article2.lstCategories=[category1.key()]
article2.put()

article3=Article(strTitle='Article3',strContent=db.Text('Article3 Content'))
article3.lstCategories=[category2.key()]
article1.put()


我們可以透過Article資料實體來取得它的所有分類。
for key in article1.lstCategories:
    print 'Category: %s'% db.get(key).strName

也可以透過某一個分類來取得所有該分類的所有文章,如此一來多對多的關聯查詢就容易許多了。
for article in category1.getArticles:
    print '[%s] %s'%(category1.strName,article.strTitle)


Expando 類別

當程式開發者希望使用一些比較彈性、鬆散的方式來定義資料型態時,可以使用Expando類別,它一樣繼承自Model類別,但它比Model 類別更具彈性,使用Expando所產生的資料物件,可以任意加上所要儲存的資料欄位,而且資料型態可以不固定,因此我們可以寫出下列程式

import datetime

from google.appengine.ext import db

class Song(db.Expando):
    title = db.StringProperty()

crazy = Song(title='Crazy like a diamond',author='Lucy Sky',publish_date='yesterday',rating=5.0)
crazy.put()

hoboken = Song(title='The man from Hoboken',author=['Anthony', 'Lou'],publish_date=datetime.datetime(1977, 5, 3))
hoboken.put()

crazy.last_minute_note=db.Text('Get a train to the station.')
crazy.put()

c=db.GqlQuery('SELECT * FROM Song WHERE publish_date = :1','yesterday').get()
print c.title


要刪除某一個欄位的資料,可以利用del關鍵字

c=db.GqlQuery('SELECT * FROM Song WHERE publish_date = :1','yesterday').get()
print dir(c)
print c.last_minute_note

del c.last_minute_note
c.put()

print c.last_minute_note


PolyModel類別

假設我們要製作一個通訊錄的資料模型,其中每一個聯絡人可能是個人或公司,雖然看起來是兩個不同的類別,但在通訊錄中可能會有相同欄位的存在,如電話號碼、地址等。在物件導向程式設計中,我們可能會利用"多型"(polymorphism)的概念來設計這樣的關係,如建立一個Contact類別,然後再建立Person及Company類別,再分別繼承自Contact類別。

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

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

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

for person in Person.all():
    print '%s %s: %s'%(person.first_name,person.last_name,person.phone_number)

在上述範例中,我們可以直接利用Contact.all( )來Query出繼承自它的所有Person與Company資料模型所產生的物件。

若想要只取出某個資料模型的物件,也可以使用各自的all( )方法。






















      

沒有留言: