Nota: os programadores que criam novas aplicações são fortemente aconselhados a usar a biblioteca de cliente NDB, que tem várias vantagens em comparação com esta biblioteca de cliente, como o armazenamento em cache automático de entidades através da API Memcache. Se estiver a usar atualmente a biblioteca cliente DB mais antiga, leia o guia de migração de DB para NDB
Vista geral
Uma entidade do Datastore tem uma chave e um conjunto de propriedades. Uma aplicação usa a API Datastore para definir modelos de dados e criar instâncias desses modelos para serem armazenados como entidades. Os modelos fornecem uma estrutura comum às entidades criadas pela API e podem definir regras para validar os valores das propriedades.
Classes de modelos
A classe Model
Uma aplicação descreve os tipos de dados que utiliza com modelos. Um modelo é uma classe Python que herda da classe Model. A classe de modelo define um novo tipo de entidade da base de dados e as propriedades que o tipo deve ter. O nome do tipo é definido pelo nome da classe instanciada que herda de db.Model
.
As propriedades do modelo são definidas através de atributos de classe na classe do modelo. Cada atributo de classe é uma instância de uma subclasse da classe Property, normalmente uma das classes de propriedades fornecidas. Uma instância de propriedade contém a configuração da propriedade, como se a propriedade é ou não necessária para que a instância seja válida, ou um valor predefinido a usar para a instância se não for fornecido nenhum.
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()
Uma entidade de um dos tipos de entidades definidos é representada na API por uma instância da classe de modelo correspondente. A aplicação pode criar uma nova entidade chamando o construtor da classe. A aplicação acede e manipula as propriedades da entidade através dos atributos da instância. O construtor da instância do modelo aceita valores iniciais para propriedades como argumentos de palavras-chave.
from google.appengine.api import users pet = Pet(name="Fluffy", type="cat") pet.weight_in_pounds = 24
Nota: os atributos da classe do modelo são a configuração das propriedades do modelo, cujos valores são instâncias de Property. Os atributos da instância do modelo são os valores das propriedades reais, cujos valores são do tipo aceite pela classe Property.
A classe Model usa as instâncias Property para validar os valores atribuídos aos atributos da instância do modelo. A validação do valor da propriedade ocorre quando uma instância do modelo é construída pela primeira vez e quando um atributo da instância recebe um novo valor. Isto garante que uma propriedade nunca pode ter um valor inválido.
Uma vez que a validação ocorre quando a instância é construída, qualquer propriedade configurada como obrigatória tem de ser inicializada no construtor. Neste exemplo, name
e type
são valores obrigatórios, pelo que os respetivos valores iniciais são especificados no construtor. weight_in_pounds
não é obrigatório para o modelo, por isso, começa sem atribuição e, em seguida, é-lhe atribuído um valor mais tarde.
Uma instância de um modelo criado através do construtor não existe no repositório de dados até ser "colocada" pela primeira vez.
Nota: tal como acontece com todos os atributos de classe Python, a configuração das propriedades do modelo é inicializada quando o script ou o módulo é importado pela primeira vez. Uma vez que o App Engine armazena em cache os módulos importados entre pedidos, a configuração do módulo pode ser inicializada durante um pedido de um utilizador e reutilizada durante um pedido de outro. Não inicialize a configuração das propriedades do modelo, como os valores predefinidos, com dados específicos do pedido ou do utilizador atual. Consulte o artigo Colocação em cache de apps para mais informações.
A classe Expando
Um modelo definido através da classe Model estabelece um conjunto fixo de propriedades que todas as instâncias da classe têm de ter (talvez com valores predefinidos). Esta é uma forma útil de modelar objetos de dados, mas o arquivo de dados não exige que todas as entidades de um determinado tipo tenham o mesmo conjunto de propriedades.
Por vezes, é útil que uma entidade tenha propriedades que não são necessariamente semelhantes às propriedades de outras entidades do mesmo tipo. Essa entidade é representada na API Datastore por um modelo "expando". Uma classe de modelo expansível é uma subclasse da superclasse Expando. Qualquer valor atribuído a um atributo de uma instância de um modelo expansível torna-se uma propriedade da entidade da base de dados, usando o nome do atributo. Estas propriedades são conhecidas como propriedades dinâmicas. As propriedades definidas através de instâncias da classe Property nos atributos de classe são propriedades fixas.
Um modelo expansível pode ter propriedades fixas e dinâmicas. A classe do modelo define simplesmente atributos de classe com objetos de configuração de propriedades para as propriedades fixas. A aplicação cria propriedades dinâmicas quando lhes atribui valores.
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
Uma vez que as propriedades dinâmicas não têm definições de propriedades do modelo, não são validadas. Qualquer propriedade dinâmica pode ter um valor de qualquer um dos tipos base da base de dados, incluindo None
. Duas entidades do mesmo tipo podem ter diferentes tipos de valores para a mesma propriedade dinâmica, e uma pode deixar uma propriedade não definida que a outra define.
Ao contrário das propriedades fixas, as propriedades dinâmicas não têm de existir. Uma propriedade dinâmica com um valor de None
é diferente de uma propriedade dinâmica inexistente. Se uma instância do modelo expansível não tiver um atributo para uma propriedade, a entidade de dados correspondente não tem essa propriedade. Pode eliminar uma propriedade dinâmica eliminando o atributo.
Os atributos cujos nomes começam com um sublinhado (_
) não são guardados na entidade da base de dados. Isto permite-lhe armazenar valores na instância do modelo para utilização interna temporária sem afetar os dados guardados com a entidade.
Nota: as propriedades estáticas são sempre guardadas na entidade da base de dados, independentemente de ser Expando, Model ou começar por um sublinhado (_
).
del p.chess_elo_rating
Uma consulta que usa uma propriedade dinâmica num filtro devolve apenas entidades cujo valor para a propriedade é do mesmo tipo que o valor usado na consulta. Da mesma forma, a consulta devolve apenas entidades com essa propriedade definida.
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: o exemplo acima usa consultas em grupos de entidades, que podem devolver resultados desatualizados. Para resultados fortemente consistentes, use consultas de antecessores em grupos de entidades.
A classe Expando é uma subclasse da classe Model e herda todos os respetivos métodos.
A classe PolyModel
A API Python inclui outra classe para modelagem de dados que lhe permite definir hierarquias de classes e executar consultas que podem devolver entidades de uma determinada classe ou de qualquer uma das suas subclasses. Estes modelos e consultas são denominados "polimórficos" porque permitem que as instâncias de uma classe sejam resultados para uma consulta de uma classe principal.
O exemplo seguinte define uma classe Contact
com as subclasses 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()
Este modelo garante que todas as entidades Person
e todas as entidades Company
têm propriedades phone_number
e address
, e as consultas de entidades Contact
podem devolver entidades Person
ou Company
. Apenas as entidades Person
têm propriedades mobile_number
.
As subclasses podem ser instanciadas tal como qualquer outra classe de modelo:
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()
Uma consulta de entidades Contact
pode devolver instâncias de Contact
, Person
ou Company
. O código seguinte imprime informações para ambas as entidades criadas acima:
for contact in Contact.all(): print 'Phone: %s\nAddress: %s\n\n' % (contact.phone_number, contact.address)
Uma consulta para entidades Company
devolve apenas instâncias de Company
:
for company in Company.all() # ...
Por agora, os modelos polimórficos não devem ser transmitidos diretamente ao construtor da classe Query. Em alternativa, use o método all()
, como no exemplo acima.
Para mais informações sobre como usar modelos polimórficos e como são implementados, consulte A classe PolyModel.
Classes e tipos de propriedades
O arquivo de dados suporta um conjunto fixo de tipos de valores para propriedades de entidades, incluindo strings Unicode, números inteiros, números de vírgula flutuante, datas, chaves de entidades, strings de bytes (blobs) e vários tipos GData. Cada um dos tipos de valores da base de dados tem uma classe de propriedade correspondente fornecida pelo módulo google.appengine.ext.db
.
O artigo Tipos e classes de propriedades descreve todos os tipos de valores suportados e as respetivas classes de propriedades. Seguem-se vários tipos de valores especiais.
Strings e bolhas
O arquivo de dados suporta dois tipos de valores para armazenar texto: strings de texto curtas com um comprimento máximo de 1500 bytes e strings de texto longas com um comprimento máximo de um megabyte. As strings curtas são indexadas e podem ser usadas em condições de filtro de consultas e ordens de ordenação. As strings longas não são indexadas e não podem ser usadas em condições de filtro nem ordens de ordenação.
Um valor de cadeia curta pode ser um valor unicode
ou um valor str
. Se o valor for str
, é assumida uma codificação de 'ascii'
. Para especificar uma codificação diferente para um valor str
, pode convertê-lo num valor unicode
com o construtor de tipo unicode()
, que usa o str
e o nome da codificação como argumentos. As strings curtas podem ser modeladas através da 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")
Um valor de string longo é representado por uma instância db.Text. O respetivo construtor recebe um valor unicode
ou um valor str
e, opcionalmente, o nome da codificação usada no str
. As strings longas podem ser modeladas através da 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")
O armazenamento de dados também suporta dois tipos semelhantes para strings de bytes não textuais: db.ByteString e db.Blob. Estes valores são strings de bytes não processados e não são tratados como texto codificado (como UTF-8).
Tal como os valores db.StringProperty
, os valores db.ByteString
são indexados. Tal como as propriedades db.TextProperty
, os valores db.ByteString
estão limitados a 1500 bytes. Uma instância ByteString
representa uma string curta de bytes e usa um valor str
como argumento para o respetivo construtor. As strings de bytes são modeladas através da classe ByteStringProperty.
Tal como db.Text
, um valor db.Blob
pode ter até um megabyte, mas não é indexado e não pode ser usado em filtros de consulta nem ordens de ordenação. A classe db.Blob
recebe um valor str
como argumento para o respetivo construtor ou pode atribuir o valor diretamente. Os blobs são modelados através da classe BlobProperty.
class MyModel(db.Model): blob = db.BlobProperty() obj = MyModel() obj.blob = open("image.png").read()
Listas
Uma propriedade pode ter vários valores, representados na API Datastore como um list
do Python. A lista pode conter valores de qualquer um dos tipos de valores suportados pelo arquivo de dados. Uma única propriedade de lista pode até ter valores de tipos diferentes.
Geralmente, a ordem é preservada. Assim, quando as entidades são devolvidas por consultas e get(), os valores das propriedades da lista estão na mesma ordem em que foram armazenados. Existe uma exceção a esta regra: os valores Blob
e Text
são movidos para o final da lista. No entanto, mantêm a respetiva ordem original entre si.
A classe ListProperty modela uma lista e garante que todos os valores na lista são de um determinado tipo. Para maior comodidade, a biblioteca também fornece StringListProperty, semelhante 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.
Uma consulta com filtros numa propriedade de lista testa cada valor na lista individualmente. A entidade corresponde à consulta apenas se algum valor na lista passar todos os filtros nessa propriedade. Consulte a página Consultas do Datastore para obter mais informações.
# 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")
Os filtros de consultas só funcionam em membros de listas. Não existe forma de testar a semelhança de duas listas num filtro de consulta.
Internamente, o arquivo de dados representa um valor de propriedade de lista como vários valores para a propriedade. Se um valor de propriedade de lista for a lista vazia, a propriedade não tem representação no arquivo de dados. A API Datastore trata esta situação de forma diferente para propriedades estáticas (com ListProperty) e propriedades dinâmicas:
- A um ListProperty estático pode ser atribuída a lista vazia como valor. A propriedade não existe no repositório de dados, mas a instância do modelo comporta-se como se o valor fosse a lista vazia. Uma ListProperty estática não pode ter um valor de
None
. - Não é possível atribuir um valor de lista vazio a uma propriedade dinâmica com um valor
list
. No entanto, pode ter um valor deNone
e pode ser eliminado (usandodel
).
O modelo ListProperty testa se um valor adicionado à lista é do tipo correto e gera um BadValueError se não for. Este teste ocorre (e pode falhar) mesmo quando uma entidade armazenada anteriormente é obtida e carregada no modelo. Uma vez que os valores str
são convertidos em valores unicode
(como texto ASCII) antes do armazenamento, ListProperty(str)
é tratado como ListProperty(basestring)
, o tipo de dados Python que aceita valores str
e unicode
. Também pode usar StringListProperty()
para este fim.
Para armazenar strings de bytes não textuais, use valores db.Blob. Os bytes de uma string de blob são preservados quando são armazenados e obtidos. Pode declarar uma propriedade que seja uma lista de blobs como ListProperty(db.Blob)
.
As propriedades de listas podem interagir com as ordens de ordenação de formas invulgares. Consulte a página Consultas do Datastore para ver detalhes.
Referências
Um valor de propriedade pode conter a chave de outra entidade. O valor é uma instância de Key.
A classe ReferenceProperty modela um valor-chave e garante que todos os valores se referem a entidades de um determinado tipo. Para maior comodidade, a biblioteca também fornece SelfReferenceProperty, equivalente a uma ReferenceProperty que se refere ao mesmo tipo que a entidade com a propriedade.
A atribuição de uma instância de modelo a uma propriedade ReferenceProperty usa automaticamente a respetiva chave como valor.
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()
Um valor da propriedade ReferenceProperty pode ser usado como se fosse a instância do modelo da entidade referenciada. Se a entidade referenciada não estiver na memória, a utilização da propriedade como uma instância obtém automaticamente a entidade do arquivo de dados. Uma ReferenceProperty também armazena uma chave, mas a utilização da propriedade faz com que a entidade relacionada seja carregada.
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 uma chave apontar para uma entidade inexistente, o acesso à propriedade gera um erro. Se uma aplicação esperar que uma referência possa ser inválida, pode testar a existência do objeto através de um bloco try/except
:
try: obj1 = obj2.reference except db.ReferencePropertyResolveError: # Referenced entity was deleted or never existed.
ReferenceProperty tem outra funcionalidade útil: referências inversas. Quando um modelo tem uma ReferenceProperty para outro modelo, cada entidade referenciada recebe uma propriedade cujo valor é uma Query que devolve todas as entidades do primeiro modelo que se referem a ela.
# To fetch and iterate over every SecondModel entity that refers to the # FirstModel instance obj1: for obj in obj1.secondmodel_set: # ...
O nome da propriedade de referência inversa é predefinido como modelname_set
(com o nome da classe do modelo em letras minúsculas e "_set" adicionado no final) e pode ser ajustado através do argumento collection_name
para o construtor ReferenceProperty.
Se tiver vários valores ReferenceProperty que se referem à mesma classe de modelo, a construção predefinida da propriedade de referência inversa gera um erro:
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)
Para evitar este erro, tem de definir explicitamente o argumento 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")
A referência e a desreferência automáticas de instâncias de modelos, a verificação de tipos e as referências inversas só estão disponíveis através da classe de propriedade do modelo ReferenceProperty. As chaves armazenadas como valores de propriedades dinâmicas Expando ou valores ListProperty não têm estas funcionalidades.