RkBlog

Hardware, programming and astronomy tutorials and reviews.

Encje i modele datastore

Encja w datastore ma klucz i zestaw w艂a艣ciwo艣ci. Aplikacja u偶ywa API by okre艣li膰 modele danych i tworzy膰 instancje tych modeli reprezentuj膮cych encje. Modele zapewniaj膮 sta艂膮 i jasno okre艣lon膮 struktur臋 dla encji tworzonych przez API i mog膮 narzuci膰 r贸偶ne warunku walidacji dla warto艣ci w艂a艣ciwo艣ci encji.

Modele

Model to klasa Pythona dziedzicz膮ca klas臋 Model udost臋pnian膮 przez API. Ka偶dy atrybut to obiekt klasy dziedzicz膮cej klas臋 Property i b臋d膮cy w艂a艣ciwo艣ci膮 encji. Klasa ta przechowuje informacje o w艂a艣ciwo艣ci, a tak偶e opcje konfiguracyjne (jak np. wym贸g podania warto艣ci, czy te偶 podania warto艣ci o 艣ci艣le okre艣lonym typie), oto przyk艂ad:

from google.appengine.ext import db

# klasa modelu opisuj膮ca dany typ encji
class Pet(db.Model):
	name = db.StringProperty(required=True)
	type = db.StringProperty(required=True, choices=set(["kot", "pies", "ptak"]))
	birthdate = db.DateProperty()
	weight = db.IntegerProperty()
	spayed_or_neutered = db.BooleanProperty()
	owner = db.UserProperty(required=True)
Encja danego typu jest reprezentowana w API jako instancja klasy modelu. Aplikacja mo偶e tworzy膰 nowe encje wywo艂uj膮c konstruktor klasy modelu. Aplikacja mo偶e manipulowa膰 w艂a艣ciwo艣ciami encji poprzez atrybuty instancji. Konstruktor klasy modelu mo偶e przyj膮膰 jako argumenty warto艣ci dla poszczeg贸lnych atrybut贸w.
from google.appengine.api import users

pet = Pet(name="Koteczek",
          type="kot",
          owner=users.get_current_user())
pet.weight = 24

Jako 偶e walidacja warto艣ci atrybut贸w odbywa si臋 przy tworzeniu instancji - nale偶y w konstruktorze poda膰 warto艣ci dla wszystkich wymaganych w艂a艣ciwo艣ci. W powy偶szym przyk艂adzie w艂a艣ciwo艣膰 "weight" nie jest wymagana i warto艣膰 mo偶na poda膰 po stworzeniu instancji (lub w og贸le jej nie podawa膰).

Nale偶y zwr贸ci膰 uwag臋, 偶e GAE keszuje importowane modu艂y mi臋dzy 偶膮daniami. Konfiguracja modu艂u mo偶e zosta zainicjalizowana dla 偶膮dania nades艂anego przez jednego u偶ytkownika i wykorzystana przy 偶膮daniu nades艂anym przez innego. Nie ustawiaj konfiguracji modeli, np. domy艣lnych warto艣ci zale偶nych od bie偶膮cego u偶ytkownika.

Modele Expando

Modele definiowane poprzez API okre艣laj膮 sta艂膮 struktur臋 danych encji danego typu, jednak偶e datastore nie wymaga by wszystkie encje danego typu mia艂y taki sam uk艂ad w艂a艣ciwo艣ci. Czasami potrzeba by jaka艣 encja mia艂a inne w艂a艣ciwo艣ci. Tego typu encje mo偶na tworzy膰 poprzez API za pomoc膮 modeli "expando".

Model expando dziedziczy klas臋 Expando i mo偶e mie膰 list臋 w艂a艣ciwo艣ci okre艣lon膮 za pomoc膮 atrybut贸w jak zwyk艂y model. W odr贸偶nieniu od typowego modelu - ka偶da warto艣膰 przypisana do atrybutu nie wymienionego w modelu w definicjach w艂a艣ciwo艣ci zostanie zapisana jako dynamiczne w艂a艣ciwo艣ci. Jako 偶e dynamiczne w艂a艣ciwo艣ci nie s膮 opisane - nie s膮 tak偶e walidowane. Oto przyk艂ad:

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

# statyczne w艂a艣ciwo艣ci
p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

# dynamiczne w艂a艣ciwo艣ci obs艂ugiwane przez Expando
p.chess_elo_rating = 1350

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

Zapytanie, kt贸re filtruje wyniki po warto艣ci dynamicznej w艂a艣ciwo艣ci zwr贸ci tylko te encje, kt贸re maj膮 tak膮 w艂a艣ciwo艣膰 i warto艣膰 kt贸rej jest zgodna co do typu (i warto艣ci) z t膮 podan膮 w zapytaniu.

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 ma p1, ale nie p2 czy p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people nie ma 偶adnych encji

Modele polimorficzne

Pythonowe API zawiera jeszcze jedn膮 klas臋 umo偶liwiaj膮c膮 definiowanie klas modeli zale偶nych od siebie hierarchi膮. Zapytanie wykonane na modelu, kt贸remu hierarchicznie podlega kilka innych modeli zwr贸ci w tym przypadku tak偶e encje zale偶nych modeli. Takie modele i zapytania nazywa si臋 polimorficznymi, gdy偶 zezwalaj膮c instancji jednej klasy by膰 wynikami zapytania klasy-rodzica.

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()
Klasy Person i Company dziedzicz膮 klas臋-rodzic Contact. Ten model zapewnia, 偶e wszystkie encje "Person" i "Company" b臋d膮 mia艂y w艂a艣ciwo艣ci "phone_number" i "address", jak i zapytania dotycz膮ce encji Contact b臋d膮 mog艂y zwr贸ci膰 encje Person i lub Company.

Instancje klas-modeli mo偶na tworzy膰 tak (zwr贸膰 uwag臋 na obecno艣膰 w艂a艣ciwo艣ci phone_number i address):

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

Zapytanie dotycz膮ce encji Contact mo偶e zwr贸ci膰 encje Contact, Person czy Company. Zapytanie dotycz膮ce encji Company zwr贸ci tylko encje Company. Zapytanie na polimorficznej klasie Contact mo偶na wykona膰 tak:

for contact in Contact.all():
  print 'Phone: %s
Address: %s

'
        % (contact.phone,
           contact.address))

W艂a艣ciwo艣ci i typy

Datastore obs艂uguje szereg typ贸w warto艣ci jakie przyj膮膰 mo偶e w艂a艣ciwo艣膰 encji, a s膮 to 艂a艅cuchy unikodu, liczby ca艂kowite, liczby zmiennoprzecinkowe, daty, klucze encji, 艂a艅cuchy bajtowe (bloby) i r贸偶ne typy GData. Ka偶dy z tych typ贸w posiada w艂asn膮 klas臋 Property dost臋pn膮 w API w module google.appengine.ext.db.

Datastore obs艂uguje dwa typy warto艣ci dla 艂a艅cuch贸w: kr贸tkie 艂a艅cuchy do 500 bajt贸w d艂ugo艣ci i d艂ugie 艂a艅cuchy do 1 MB d艂ugo艣ci. Kr贸tkie 艂a艅cuchy podlegaj膮 indeksom i mo偶na operowa膰 na nich w zapytaniach. D艂ugie 艂a艅cuchy nie podlegaj膮 indeksom i nie mo偶na filtrowa膰, sortowa膰 po nich encji.

Kr贸tki 艂a艅cuch mo偶e by膰 unikodem (unicode), lub zwyk艂ym 艂a艅cuchem (str) z za艂o偶eniem domy艣lnego kodowania na ascii. Kr贸tkie 艂a艅cuchy w modelu oznacza si臋 jako StringProperty:

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

obj = MyModel()

# unikod
obj.string = u"kittens"

# unicode() konwertuj膮cy 艂a艅cuch o kodowaniu ascii do unikodu
obj.string = unicode("kittens", "latin-1")

# 艂a艅cuch ascii
obj.string = "kittens"

# kr贸tkich 艂a艅cuch贸w mo偶na u偶y膰 do filtrowania i sortowania encji
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

D艂ugi 艂a艅cuch jest reprezentowany przez instancj臋 db.Text. Konstruktor przyjmuje jako warto艣膰 unikod lub zwyk艂y 艂a艅cuch z opcjonalnym podaniem kodowania. D艂ugie 艂a艅cuchy w modelach okre艣lane s膮 przez TextProperty.

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

obj = MyModel()

# unikod
obj.text = db.Text(u"lots of kittens")

# zwyk艂y 艂a艅cuch z podanym kodowaniem
obj.text = db.Text("lots of kittens", "latin-1")

# domy艣lnie zak艂adane kodowanie ascii
obj.text = db.Text("lots of kittens")

# db.Text mo偶e przechowywa膰 bardzo d艂ugie 艂a艅cuchy
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

Datastore obs艂uguje tak偶e dwa podobne typy dla 艂a艅cuch贸w bajtowych - db.ByteString i db.Blob. Te 艂a艅cuchy sk艂adaj膮ce si臋 z surowych bajt贸w nie s膮 traktowane jako unikod (np. utf-8). ByteString ma limit na 500 znak贸w i podlega indeksom bazy (ByteStringProperty w modelach), a Blob mo偶e przechowywa膰 d艂u偶e 艂a艅cuchy, lecz nie podlega indeksom (BlobProperty w modelach). Typy te s膮 stosowane do przechowywania binarnych danych - np. grafiki:

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

obj = MyModel()

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

Pythonowe listy s艂u偶膮 do przedstawiania w艂a艣ciwo艣ci maj膮cej wiele warto艣ci. Listy mog膮 zawiera膰 dowolne warto艣ci o obs艂ugiwanych przez datastore typach. Zapisywana jest tak偶e kolejno艣膰 element贸w na li艣cie. Klasa ListProperty s艂u偶y do opisania takiej listy i mo偶e wymusi膰 by wszystkie jej warto艣ci by艂y okre艣lonego typu. Dost臋pna jest tak偶e pomocnicza klasa StringListProperty dla listy 艂a艅cuch贸w. Oto przyk艂ad:

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

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

obj.numbers = ["hello"]  # tutaj b臋dzie b艂膮d bo lista musi mie膰 tylko longi

Zapytanie na w艂a艣ciwo艣膰 maj膮c膮 jako warto艣膰 list臋 b臋dzie sprawdza艂o ka偶dy jej element i zostanie spe艂nione je偶eli co najmniej jeden element listy spe艂nia warunek zapytania

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

Do statycznej ListProperty mo偶na przypisa膰 pust膮 list臋, lecz dla datastore w efekcie b臋dzie to encja bez danej w艂a艣ciwo艣ci. Statyczna ListProperty nie mo偶e mie膰 None jako warto艣ci. Dynamiczna lista nie mo偶e mie膰 przypisanej pustej listy, lecz mo偶e mie膰 warto艣膰 None.

Do przechowywania nie-tekstowych danych u偶ywaj db.Blob. Do stworzenia listy takich binarnych danych mo偶na u偶y膰 "ListProperty(db.Blob)."

W艂a艣ciwo艣膰 mo偶e przyj膮膰 jako warto艣膰 klucz innej encji. Warto艣ci膮 jest tutaj instancja klucza. Referencja tego typu opisywana jest przez ReferenceProperty i wymusza by warto艣ci odnosi艂y si臋 do kluczy encji danego typu. Dla uproszczenia dodano tak偶e SelfReferenceProperty, kt贸ra dopuszcza tylko instancje kluczy encji tego samego modelu. Przypisuj膮c jako referencj臋 instancj臋 klasy spowoduje automatyczne u偶ycie jej klucza:

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

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

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

obj2 = SecondModel()

# atrybut reference przyjmuje jako warto艣膰 instancj臋 klucza innej encji
obj2.reference = obj1.key()

# przypisanie instancji encji tak偶e spowoduje przypisanie instancji jej klucza
obj2.reference = obj1
obj2.put()

Gdy encja, kt贸re instancja klucza jest gdzie艣 zapisana jako referencja zostanie skasowana nie spowoduje to zmian w tych referencjach. By sprawdzi膰 czy encja, kt贸rej instancj臋 klucza mamy zapisan膮 nie zosta艂a skasowana mo偶na u偶y膰:

obj1 = obj2.reference

if not obj1:
  # encja zosta艂a skasowana

W艂a艣ciwo艣膰 ReferenceProperty daje jeszcze jedn膮 przydatn膮 funkcjonalno艣膰. Ka偶da encja, do kt贸rej istniej膮 referencje dostaje w艂a艣ciwo艣膰, kt贸rej warto艣ci膮 jest zapytanie pobieraj膮ce wszystkie referuj膮ce do niej encje:

# obj1 to instancja encji z modelu FirstModel. Poni偶szy kod pobierze wszystkie encje z modelu SecondModel
# kt贸re maj膮 zapisan膮 referencj臋 do encji obj1
for obj in obj1.secondmodel_set:
  # ...
Nazwa tej w艂a艣ciwo艣ci domy艣lnie ustawiana jest na nazwamodelu_set (gdzie nazwa modelu pisana ma艂ymi literami)

Je偶eli masz wiele ReferenceProperty do tego samego modelu domy艣lna konstrukcja wstecznej referencji opisanej powy偶ej nie b臋dzie funkcjonowa膰. Nale偶y poda膰 okre艣lon膮 nazw臋 dla ka偶dej wstecznej zale偶no艣ci w referuj膮cym modelu poprzez argument collection_name dla ReferenceProperty.

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

# Tak jest poprawnie
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")

Nazwy w艂a艣ciwo艣ci

Datastore rezerwuje wszystkie nazwy w艂a艣ciwo艣ci zaczynaj膮ce si臋 i ko艅cz膮ce dwoma podkre艣leniami. Aplikacja nie mo偶e tworzy膰 w艂a艣ciwo艣ci o takich nazwach.

W Pythonowym API atrybuty w instancjach modeli, kt贸rych nazwy zaczynaj膮 si臋 jednym podkre艣leniem s膮 ignorowane i nie s膮 zapisywane do datastore. Mo偶na to wykorzysta膰 do tymczasowego przetrzymywania jaki艣 danych wraz z encj膮.

Dodatkowo istnieje lista zakazanych nazw w艂a艣ciwo艣ci, cho膰 samo datastore zezwala na ich u偶ycie. W Pythonowym API nale偶y poda膰 zakazan膮 nazw臋 w argumencie "name" w艂a艣ciwo艣ci:

class MyModel(db.Model):
  obj_key = db.StringProperty(name="key")

Na podstawie Entities and Models.
RkBlog

Google App Engine (GAE), 31 July 2009, Piotr Mali艅ski

Comment article