Nota:gli sviluppatori che creano nuove applicazioni sono fortemente incoraggiati a utilizzare la libreria client NDB, che offre diversi vantaggi rispetto a questa libreria client, ad esempio la memorizzazione automatica nella cache delle entità tramite l'API Memcache. Se al momento utilizzi la libreria client DB precedente, leggi la guida alla migrazione da DB a NDB.
Panoramica
Un'entità datastore ha una chiave e un insieme di proprietà. Un'applicazione utilizza l'API Datastore per definire i modelli di dati e creare istanze di questi modelli da archiviare come entità. I modelli forniscono una struttura comune alle entità create dall'API e possono definire regole per la convalida dei valori delle proprietà.
Classi modello
Classe modello
Un'applicazione descrive i tipi di dati che utilizza con i modelli. Un modello è una classe Python che eredita dalla classe Model. La classe del modello definisce un nuovo tipo di entità del datastore e le proprietà che il tipo deve assumere. Il nome del tipo è definito dal nome della classe istanziata che eredita da db.Model.
Le proprietà del modello vengono definite utilizzando gli attributi della classe nella classe del modello. Ogni attributo di classe è un'istanza di una sottoclasse della classe Property, di solito una delle classi di proprietà fornite. Un'istanza di proprietà contiene la configurazione della proprietà, ad esempio se la proprietà è obbligatoria o meno per la validità dell'istanza o un valore predefinito da utilizzare per l'istanza se non ne viene fornito nessuno.
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()
Un'entità di uno dei tipi di entità definiti è rappresentata nell'API da un'istanza della classe di modello corrispondente. L'applicazione può creare una nuova entità chiamando il costruttore della classe. L'applicazione accede alle proprietà dell'entità e le manipola utilizzando gli attributi dell'istanza. Il costruttore dell'istanza del modello accetta i valori iniziali delle proprietà come argomenti con parole chiave.
from google.appengine.api import users pet = Pet(name="Fluffy", type="cat") pet.weight_in_pounds = 24
Nota:gli attributi della classe del modello sono la configurazione delle proprietà del modello, i cui valori sono istanze di Property. Gli attributi dell'istanza del modello sono i valori effettivi delle proprietà, i cui valori sono del tipo accettato dalla classe Property.
La classe Model utilizza le istanze Property per convalidare i valori assegnati agli attributi dell'istanza del modello. La convalida del valore della proprietà viene eseguita quando viene creata per la prima volta un'istanza del modello e quando a un attributo dell'istanza viene assegnato un nuovo valore. In questo modo, una proprietà non può mai avere un valore non valido.
Poiché la convalida viene eseguita durante la creazione dell'istanza, qualsiasi proprietà configurata come obbligatoria deve essere inizializzata nel costruttore. In questo esempio, name e type sono valori obbligatori, quindi i loro valori iniziali sono specificati nel costruttore. weight_in_pounds non è richiesto dal modello, quindi inizialmente non viene assegnato, poi viene assegnato un valore in un secondo momento.
Un'istanza di un modello creato utilizzando il costruttore non esiste nel datastore finché non viene "inserita" per la prima volta.
Nota:come per tutti gli attributi della classe Python, la configurazione delle proprietà del modello viene inizializzata al primo importazione dello script o del modulo. Poiché App Engine memorizza nella cache i moduli importati tra le richieste, la configurazione dei moduli potrebbe essere inizializzata durante una richiesta per un utente e riutilizzata durante una richiesta per un altro. Non inizializzare la configurazione delle proprietà del modello, ad esempio i valori predefiniti, con dati specifici della richiesta o dell'utente corrente. Per saperne di più, consulta Memorizzazione nella cache delle app.
The Expando Class
Un modello definito utilizzando la classe Model stabilisce un insieme fisso di proprietà che ogni istanza della classe deve avere (magari con valori predefiniti). Questo è un modo utile per modellare gli oggetti dati, ma il datastore non richiede che ogni entità di un determinato tipo abbia lo stesso insieme di proprietà.
A volte è utile che un'entità abbia proprietà che non sono necessariamente simili a quelle di altre entità dello stesso tipo. Un'entità di questo tipo è rappresentata nell'API Datastore da un modello "espandibile". Una classe di modello espandibile crea sottoclassi della superclasse Expando. Qualsiasi valore assegnato a un attributo di un'istanza di un modello espandibile diventa una proprietà dell'entità datastore, utilizzando il nome dell'attributo. Queste proprietà sono note come proprietà dinamiche. Le proprietà definite utilizzando le istanze della classe Property negli attributi della classe sono proprietà fisse.
Un modello espandibile può avere proprietà sia fisse che dinamiche. La classe del modello imposta semplicemente gli attributi della classe con gli oggetti di configurazione della proprietà per le proprietà fisse. L'applicazione crea proprietà dinamiche quando assegna loro dei valori.
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
Poiché le proprietà dinamiche non hanno definizioni di proprietà del modello, non vengono convalidate. Qualsiasi proprietà dinamica può avere un valore di uno qualsiasi dei tipi di base del datastore, incluso None. Due entità dello stesso tipo possono avere tipi diversi di valori per la stessa proprietà dinamica e una può lasciare una proprietà non impostata che l'altra imposta.
A differenza delle proprietà fisse, le proprietà dinamiche non devono esistere. Una proprietà dinamica con un valore di None è diversa da una proprietà dinamica inesistente. Se un'istanza del modello espandibile non ha un attributo per una proprietà, l'entità di dati corrispondente non ha quella proprietà. Puoi eliminare una proprietà dinamica eliminando l'attributo.
Gli attributi i cui nomi iniziano con un trattino basso (_) non vengono salvati nell'entità datastore. In questo modo, puoi memorizzare i valori nell'istanza del modello per un utilizzo interno temporaneo senza influire sui dati salvati con l'entità.
Nota:le proprietà statiche vengono sempre salvate nell'entità datastore indipendentemente dal fatto che sia Expando, Model o inizi con un trattino basso (_).
del p.chess_elo_rating
Una query che utilizza una proprietà dinamica in un filtro restituisce solo le entità il cui valore per la proprietà è dello stesso tipo del valore utilizzato nella query. Analogamente, la query restituisce solo le entità con questa proprietà impostata.
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
Nota:l'esempio precedente utilizza query in più gruppi di entità, che potrebbero restituire risultati obsoleti. Per risultati fortemente coerenti, utilizza le query sugli antenati all'interno dei gruppi di entità.
La classe Expando è una sottoclasse della classe Model e ne eredita tutti i metodi.
La classe PolyModel
L'API Python include un'altra classe per la modellazione dei dati che consente di definire gerarchie di classi ed eseguire query che possono restituire entità di una determinata classe o di una delle sue sottoclassi. Questi modelli e queste query sono chiamati "polimorfici" perché consentono alle istanze di una classe di essere risultati per una query di una classe principale.
L'esempio seguente definisce una classe Contact con le sottoclassi Person e 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()
Questo modello garantisce che tutte le entità Person e tutte le entità Company abbiano proprietà phone_number e address e che le query per le entità Contact possano restituire entità Person o Company. Solo le entità Person hanno proprietà mobile_number.
Le sottoclassi possono essere istanziate come qualsiasi altra classe di modello:
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()
Una query per le entità Contact può restituire istanze di Contact, Person o Company. Il seguente codice stampa le informazioni per entrambe le entità create sopra:
for contact in Contact.all(): print 'Phone: %s\nAddress: %s\n\n' % (contact.phone_number, contact.address)
Una query per le entità Company restituisce solo istanze di Company:
for company in Company.all() # ...
Per il momento, i modelli polimorfici non devono essere passati direttamente al costruttore della classe Query. Utilizza invece il metodo all(), come nell'esempio precedente.
Per ulteriori informazioni su come utilizzare i modelli polimorfici e su come vengono implementati, consulta The PolyModel Class.
Classi e tipi di proprietà
Il datastore supporta un insieme fisso di tipi di valori per le proprietà delle entità, tra cui stringhe Unicode, numeri interi, numeri in virgola mobile, date, chiavi di entità, stringhe di byte (blob) e vari tipi GData. A ogni tipo di valore del datastore corrisponde una classe Property fornita dal modulo google.appengine.ext.db.
Tipi e classi di proprietà descrive tutti i tipi di valori supportati e le relative classi di proprietà. Di seguito sono descritti diversi tipi di valori speciali.
Stringhe e chiazze
Datastore supporta due tipi di valori per l'archiviazione di testo: stringhe di testo brevi fino a 1500 byte di lunghezza e stringhe di testo lunghe fino a 1 MB di lunghezza. Le stringhe brevi vengono indicizzate e possono essere utilizzate nelle condizioni di filtro delle query e negli ordinamenti. Le stringhe lunghe non vengono indicizzate e non possono essere utilizzate nelle condizioni di filtro o negli ordinamenti.
Un valore stringa breve può essere un valore unicode o un valore str. Se il valore è un str, viene presupposta una codifica di 'ascii'. Per specificare una codifica diversa per un valore str, puoi convertirlo in un valore unicode con il costruttore di tipo unicode(), che accetta str e il nome della codifica come argomenti. Le stringhe brevi possono essere modellate utilizzando la classe 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")
Un valore stringa lungo è rappresentato da un'istanza db.Text. Il suo costruttore accetta un valore unicode o un valore str e, facoltativamente, il nome della codifica utilizzata in str. Le stringhe lunghe possono essere modellate utilizzando la classe 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")
Il datastore supporta anche due tipi simili per le stringhe di byte non di testo: db.ByteString e db.Blob. Questi valori sono stringhe di byte non elaborati e non vengono trattati come testo codificato (ad esempio UTF-8).
Come i valori db.StringProperty, i valori db.ByteString vengono indicizzati. Come le proprietà db.TextProperty, i valori db.ByteString sono limitati a 1500 byte. Un'istanza ByteString rappresenta una breve stringa di byte e accetta un valore str come argomento del suo costruttore. Le stringhe di byte vengono modellate utilizzando la classe ByteStringProperty.
Come db.Text, un valore db.Blob può raggiungere un megabyte, ma non viene indicizzato e non può essere utilizzato nei filtri delle query o negli ordinamenti. La classe db.Blob accetta un valore str come argomento del suo costruttore oppure puoi assegnare il valore direttamente. I blob vengono modellati utilizzando la classe BlobProperty.
class MyModel(db.Model): blob = db.BlobProperty() obj = MyModel() obj.blob = open("image.png").read()
Elenchi
Una proprietà può avere più valori, rappresentati nell'API datastore come un list Python. L'elenco può contenere valori di qualsiasi tipo supportato dal datastore. Una singola proprietà elenco può anche avere valori di tipi diversi.
L'ordine viene generalmente mantenuto, quindi quando le entità vengono restituite dalle query e da get(), i valori delle proprietà di elenco sono nello stesso ordine in cui sono stati archiviati. Esiste un'eccezione: i valori Blob e Text vengono spostati alla fine dell'elenco, ma mantengono l'ordine originale l'uno rispetto all'altro.
La classe ListProperty modella un elenco e impone che tutti i valori dell'elenco siano di un determinato tipo. Per comodità, la libreria fornisce anche StringListProperty, simile a 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.
Una query con filtri su una proprietà elenco verifica ogni valore dell'elenco singolarmente. L'entità corrisponderà alla query solo se alcuni valori nell'elenco superano tutti i filtri applicati a quella proprietà. Per saperne di più, consulta la pagina Query Datastore.
# 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")
I filtri delle query operano solo sui membri degli elenchi. Non è possibile testare la somiglianza di due elenchi in un filtro query.
Internamente, il datastore rappresenta un valore della proprietà elenco come più valori per la proprietà. Se il valore di una proprietà elenco è l'elenco vuoto, la proprietà non è rappresentata nel datastore. L'API Datastore tratta questa situazione in modo diverso per le proprietà statiche (con ListProperty) e quelle dinamiche:
- A una ListProperty statica può essere assegnato l'elenco vuoto come valore. La proprietà non esiste nel datastore, ma l'istanza del modello si comporta come se il valore fosse l'elenco vuoto. Una ListProperty statica non può avere un valore di
None. - A una proprietà dinamica con un valore
listnon può essere assegnato un valore di elenco vuoto. Tuttavia, può avere un valore diNonee può essere eliminato (utilizzandodel).
Il modello ListProperty verifica che un valore aggiunto all'elenco sia del tipo corretto e genera un errore BadValueError in caso contrario. Questo test viene eseguito (e potenzialmente non riesce) anche quando un'entità precedentemente memorizzata viene recuperata e caricata nel modello. Poiché i valori str vengono convertiti in valori unicode (come testo ASCII) prima dell'archiviazione, ListProperty(str) viene trattato come ListProperty(basestring), il tipo di dati Python che accetta sia i valori str sia i valori unicode. A questo scopo, puoi utilizzare anche StringListProperty().
Per archiviare stringhe di byte non testuali, utilizza i valori db.Blob. I byte di una stringa blob vengono conservati quando vengono archiviati e recuperati. Puoi dichiarare una proprietà che è un elenco di blob come ListProperty(db.Blob).
Le proprietà di elenco possono interagire con gli ordini di ordinamento in modi insoliti. Per maggiori dettagli, consulta la pagina Query Datastore.
Riferimenti
Un valore della proprietà può contenere la chiave di un'altra entità. Il valore è un'istanza Key.
La classe ReferenceProperty modella una coppia chiave-valore e impone che tutti i valori facciano riferimento a entità di un determinato tipo. Per comodità, la libreria fornisce anche SelfReferenceProperty, equivalente a una ReferenceProperty che fa riferimento allo stesso tipo dell'entità con la proprietà.
L'assegnazione di un'istanza del modello a una proprietà ReferenceProperty utilizza automaticamente la relativa chiave come valore.
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()
Un valore della proprietà ReferenceProperty può essere utilizzato come se fosse l'istanza del modello dell'entità a cui viene fatto riferimento. Se l'entità a cui viene fatto riferimento non è in memoria, l'utilizzo della proprietà come istanza recupera automaticamente l'entità dal datastore. Una proprietà di riferimento memorizza anche una chiave, ma l'utilizzo della proprietà comporta il caricamento dell'entità correlata.
obj2.reference.prop = 999 obj2.reference.put() results = db.GqlQuery("SELECT * FROM SecondModel") another_obj = results.fetch(1)[0] v = another_obj.reference.prop
Se una chiave punta a un'entità inesistente, l'accesso alla proprietà genera un errore. Se un'applicazione prevede che un riferimento possa non essere valido, può verificare l'esistenza dell'oggetto utilizzando un blocco try/except:
try: obj1 = obj2.reference except db.ReferencePropertyResolveError: # Referenced entity was deleted or never existed.
ReferenceProperty ha un'altra funzionalità utile: i riferimenti inversi. Quando un modello ha una ReferenceProperty a un altro modello, ogni entità a cui viene fatto riferimento ottiene una proprietà il cui valore è una Query che restituisce tutte le entità del primo modello che fanno riferimento a essa.
# To fetch and iterate over every SecondModel entity that refers to the # FirstModel instance obj1: for obj in obj1.secondmodel_set: # ...
Il nome della proprietà di riferimento inverso è impostato per impostazione predefinita su modelname_set (con il nome della classe del modello in lettere minuscole e "_set" aggiunto alla fine) e può essere modificato utilizzando l'argomento collection_name del costruttore ReferenceProperty.
Se hai più valori ReferenceProperty che fanno riferimento alla stessa classe di modello, la costruzione predefinita della proprietà di riferimento inverso genera un errore:
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)
Per evitare questo errore, devi impostare esplicitamente l'argomento 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")
Il riferimento e l'annullamento del riferimento automatici delle istanze del modello, il controllo del tipo e i riferimenti inversi sono disponibili solo utilizzando la classe di proprietà del modello ReferenceProperty. Le chiavi archiviate come valori delle proprietà dinamiche Expando o dei valori ListProperty non dispongono di queste funzionalità.