My favorites | Português | Sign in

Entidades e modelos

Uma entidade de armazenamento de dados possui uma chave e um conjunto de propriedades. Um aplicativo utiliza a API de armazenamento de dados para definir modelos de dados e cria instâncias desses modelos para serem armazenadas como entidades. Os modelos fornecem uma estrutura comum às entidades criadas pela API e podem definir regras de validação dos valores das propriedades.

A interface Model

Um aplicativo usa os modelos para descrever os tipos de dados que utiliza. Um modelo é uma classe Python que herda da classe Model. A classe modelo define um novo tipo de entidade de armazenamento de dados e as propriedades que esse tipo deverá assumir.

As propriedades do modelo são definidas utilizando atributos de classe da classe modelo. Cada atributo de classe é uma instância de uma subclasse da classe Property, normalmente uma das classes de propriedade fornecidas. Uma instância de propriedade contém a configuração da propriedade, como se a propriedade é necessária para que a instância seja válida ou não, ou um valor padrão a ser usado com a instância caso não seja fornecido nenhum valor.

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()
  owner = db.UserProperty(required=True)

Uma entidade de um dos tipos definidos é representada na API por uma instância da classe de modelo correspondente. O aplicativo pode chamar o construtor da classe para criar uma nova entidade. O aplicativo acessa e manipula as propriedades da entidade utilizando atributos da instância. O construtor da instância de modelo aceita valores iniciais das propriedades como argumentos de palavras-chave.

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat",
          owner=users.get_current_user())
pet.weight_in_pounds = 24

Observação: Os atributos da classe de 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 da propriedade em si, cujos valores são do tipo aceito pela classe Property.

A classe Model utiliza as instâncias de Property para validar valores atribuídos aos atributos da instância de modelo. A validação do valor de Property ocorre quando uma instância do modelo é construída pela primeira vez e quando um atributo de instância recebe um novo valor. Isso assegura que uma propriedade nunca possa ter um valor inválido.

Como a validação ocorre quando a instância é construída, qualquer propriedade configurada como obrigatória deve ser inicializada no construtor. Neste exemplo, name, type e owner são valores obrigatórios, portanto seus valores iniciais são especificados no construtor. weight_in_pounds não é obrigatório para o modelo, portanto não é atribuído de início, mas recebe um valor posteriormente.

Uma instância de um modelo criado usando o construtor não existe no armazenamento de dados até ser "colocada" (put) pela primeira vez. Consulte Criação, obtenção e exclusão de dados.

Observação: Assim como os atributos de classe Python, a configuração de propriedade de modelo é inicializada quando o script ou módulo é importado pela primeira vez. Como o Google App Engine armazena os módulos importados em cache entre as solicitações, a configuração do módulo pode ser inicializada durante uma solicitação para um usuário e reutilizada durante a solicitação para outro. Não inicialize a configuração de propriedade do modelo, como valores padrão, com dados específicos para a solicitação ou para o usuário atual. Consulte Cache de aplicativos para obter mais informações.

Modelos Expando

Um modelo definido usando a classe Model estabelece um conjunto fixo de propriedades que todas as instâncias da classe devem ter (talvez com valores padrão). Isso é muito útil para modelar objetos de dados, mas o armazenamento de dados não requer que todas as entidades de um tipo determinado tenham o mesmo conjunto de propriedades.

Às vezes, pode ser útil existir uma entidade com propriedades que não sejam necessariamente as mesmas das outras entidades do mesmo tipo. Tal entidade é representada na API de armazenamento de dados por um modelo "expando". Uma classe de modelo expando é uma subclasse da superclasse Expando. Qualquer valor designado para um atributo de uma instância de modelo expando se torna uma propriedade da entidade do armazenamento de dados, utilizando o nome do atributo. Essas propriedades são conhecidas como propriedades dinâmicas. As propriedades definidas usando instâncias da classe Property nos atributos de classe são propriedades fixas.

Um modelo expando pode ter propriedades fixas e dinâmicas. A classe do modelo simplesmente define os atributos de classe com objetos de configuração Property para as propriedades fixas. O aplicativo cria as propriedades dinâmicas ao atribuir valores a elas.

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

Como as propriedades dinâmicas não têm definições de propriedade de modelo, elas não são validadas. Qualquer propriedade dinâmica pode ter um valor de qualquer um dos tipos básicos do armazenamento de dados, incluindo None. Duas entidades do mesmo tipo podem ter tipos diferentes de valores para a mesma propriedade dinâmica, e uma delas pode definir a propriedade enquanto a outra não o faz.

As propriedades dinâmicas não precisam existir, diferente das propriedades fixas. Uma propriedade dinâmica com valor None é diferente de uma propriedade dinâmica não existente. Caso uma instância de modelo expando não tenha um atributo para uma propriedade, a entidade de dados correspondente não terá tal propriedade. Para excluir uma propriedade dinâmica, exclua o atributo.

del p.chess_elo_rating

Uma consulta utilizando uma propriedade dinâmica em um filtro retorna somente as entidades cujo valor da propriedade seja do mesmo tipo que o valor usado na consulta. De modo semelhante, a consulta retorna somente as entidades com tal 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

A classe Expando é uma subclasse da classe Model e herda todos os seus métodos.

Modelos polimórficos

A API em Python inclui uma outra classe para modelagem de dados que permite a definição de hierarquias de classes e realiza consultas que podem retornar entidades de uma determinada classe ou qualquer uma de suas subclasses. Esses modelos e consultas são chamados "polimórficos", pois permitem que instâncias de uma classe sejam resultados para uma consulta de uma classe pai.

O exemplo a seguir define uma classe Contact e as classes Person e Company que são subclasses de 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()

Esse modelo garante que todas as entidades Person e todas as entidades Company tenham as propriedades phone_number e address e as consultas para as entidades Contact possam retornar as entidades Person ou Company. Apenas entidades Person têm propriedades mobile_number.

As subclasses podem ser instanciadas 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 para as entidades Contact podem retornar instâncias de Contact, Person ou Company. O seguinte código imprime informações para ambas as entidades criadas acima:

for contact in Contact.all():
  print 'Phone: %s\nAddress: %s\n\n'
        % (contact.phone,
           contact.address))

Uma consulta para as entidades Company retorna apenas instâncias de Company:

for company in Company.all()
  # ...

Para obter mais informações sobre como usar modelos polimórficos e como eles são implementados, consulte A classe PolyModel.

Propriedades e tipos

O armazenamento de dados suporta um conjunto fixo de tipos de valores para propriedades de entidades, incluindo strings Unicode, inteiros, números com ponto flutuante, datas, chaves de entidade, strings de byte (blobs) e diversos tipos de Google Data. Cada um dos tipos de valor do armazenamento de dados possui uma classe Property correspondente, fornecida pelo módulo google.appengine.ext.db.

Tipos e classes Property descreve todos os tipos de valor suportados e as classes Property correspondentes. Há diversos tipos de valores especiais descritos abaixo.

Strings e Blobs

O armazenamento de dados suporta dois tipos de valores para armazenamento de texto: strings de texto curtas com até 500 bytes de comprimento e strings de texto longas com até 1 megabyte de comprimento. As strings curtas são indexadas e podem ser usadas em condições de filtro de consulta e ordens de classificação. As strings longas não são indexadas e não podem ser usadas em condições de filtro ou ordens de classificação.

Um valor de string curta pode ser unicode ou str. Se o valor for str, será suposta a codificação 'ascii'. Para especificar uma codificação diferente para um valor str, converta-o em um valor unicode com o construtor de tipo unicode(), que assume a str e o nome da codificação como argumentos. As strings curtas podem ser modeladas usando a 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 value 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 longa é representado por uma instância db.Text. Seu construtor assume um valor unicode ou um valor str e, como opção, o nome da codificação usada em str. As strings longas podem ser modeladas usando a classe TextProperty.

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

obj = MyModel()

# Text() can take a Unicode value.
obj.text = db.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 = db.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 parecidos de strings de bytes não textuais: db.ByteString e db.Blob. Esses valores são strings de bytes brutos e não são tratados como texto codificado (como UTF-8).

Como os valores str ou unicode, os valores db.ByteString são indexados e limitados a 500 caracteres. Uma instância de ByteString representa uma string de byte curta e assume um valor str como um argumento para seu construtor. Strings de byte são modeladas usando a classe ByteStringProperty.

Como o db.Text, um valor de db.Blob pode ser tão grande quanto 1 megabyte, mas não é indexado e não pode ser usado nos filtros da consulta nem nas ordens de classificação. A classe db.Blob assume um valor str como um argumento para o seu construtor. Os blobs são modelados usando a classe BlobProperty.

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

obj = MyModel()

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

Listas

Uma propriedade pode ter diversos valores, representados na API de armazenamento de dados como uma list Python. A lista pode conter valores de qualquer um dos outros tipos suportados pelo armazenamento de dados. Uma única propriedade da lista pode até mesmo ter valores de tipos diferentes. A ordem é preservada. Assim, quando as entidades são retornadas pelas consultas e uso de get(), as propriedades da lista terão valores na mesma ordem em que foram armazenados.

A classe ListProperty modela uma lista e impõe que todos os valores dela sejam de um tipo determinado. Por uma questão de conveniência, 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.

Um filtro de consulta de uma propriedade de lista testa o valor dado em relação aos membros da lista. A condição será true se pelo menos um membro da lista atender à condição.

# 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 consulta operam somente com os membros da lista. Não há uma forma de testar a semelhança entre duas listas em um filtro de consulta.

Internamente, o armazenamento de dados representa um valor de propriedade de lista como diversos valores para a propriedade. Se um valor de propriedade de lista for a lista vazia, a propriedade não terá representação no armazenamento de dados. A API de armazenamento de dados trata essa situação de forma diferente para propriedades estáticas (com ListProperty) e dinâmicas:

  • Uma ListProperty estática pode receber a lista vazia como valor. A propriedade não existe no armazenamento de dados, mas a instância de modelo se comporta como se o valor fosse a lista vazia. Uma ListProperty estática não pode ter valor None.
  • Uma propriedade dinâmica com valor list não pode receber um valor de lista vazia. Entretanto, ela pode ter valor None e pode ser excluída (utilizando del).

O modelo ListProperty testa se um valor adicionado à lista é do tipo correto e emite BadValueError caso não seja. Este teste (e a falha potencial) ocorre mesmo quando uma entidade armazenada anteriormente é recuperada e carregada no modelo. Como os valores str são convertidos em valores unicode (como texto ASCII) antes de serem armazenados, ListProperty(str) é tratada como ListProperty(basestring), o tipo de dados Python que aceita os valores str e unicode. Você também pode usar StringListProperty() para essa finalidade.

Para armazenar strings de byte não textuais, use valores db.Blob. Os bytes de uma string blob são preservados quando são armazenados e recuperados. Você pode declarar uma propriedade composta por uma lista de blobs como ListProperty(db.Blob).

As propriedades de lista interagem de maneira incomum com as ordens de classificação. Consulte Consultas e índices: ordens de classificação e propriedades com diversos valores para obter mais 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 uma valor de chave e impõe que todos os valores se refiram a entidades de um tipo determinado. Por uma questão de conveniência, 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 utiliza sua chave automaticamente 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 de propriedade ReferenceProperty pode ser usado como se fosse a instância de modelo da entidade da referência. Se a entidade da referência não estiver na memória, o uso da propriedade como instância obtém a entidade do armazenamento de dados automaticamente.

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

A exclusão de uma entidade cuja chave é o valor de uma propriedade de referência não altera a propriedade de referência. Um valor de propriedade de referência pode ser uma chave que não é mais válida. Se um aplicativo espera que uma referência seja válida, ele pode testar a existência do objeto utilizando uma instrução if:

obj1 = obj2.reference

if not obj1:
  # Referenced entity was deleted.

ReferenceProperty tem outro recurso útil: referências anteriores. Quando um modelo tiver uma ReferenceProperty fazendo referência a outro modelo, cada entidade da referência recebe uma propriedade cujo valor é uma Query que retorna todas as entidades do primeiro modelo que fazem referência 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 anterior é modelname_set como padrão (com o nome da classe de modelo em minúsculas e "_set" adicionado ao final) e pode ser ajustado usando o argumento collection_name para o construtor de ReferenceProperty.

Se houver diversos valores de ReferenceProperty fazendo referência à mesma classe de modelo, a construção padrão da propriedade de referência anterior emitirá 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 esse erro, é necessário definir o argumento collection_name de maneira explícita:

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

Ao fazer e desfazer referências de instâncias de modelo automaticamente, a verificação de tipo e as referências anteriores estão disponíveis somente ao usar a classe de propriedade de modelo ReferenceProperty. As chaves armazenadas como valores de propriedades dinâmicas Expando ou valores de ListProperty não têm esses recursos.

Nomes de propriedade

O armazenamento de dados reserva todos os nomes de propriedade que começam e terminam com dois caracteres sublinhados (__*__). Um aplicativo não pode criar uma propriedade com tal nome.

Na API Python, os atributos de instâncias de modelo cujos nomes começam com um caractere sublinhado (_) são ignorados e não são salvos na entidade do armazenamento de dados. Isso permite o armazenamento de valores na instância do modelo para uso interno temporário, sem afetar os dados salvos com a entidade.

Como a API Python utiliza por padrão os atributos de instâncias de modelo como nomes de propriedade, nenhum dos atributos que já estejam sendo usados pelos métodos de instância podem ser usados diretamente como nomes de atributo de propriedade. De um modo semelhante, nenhum dos nomes usados pelos argumentos de palavra-chave dos construtores de modelo podem ser usados como nomes de atributos de propriedade. Consulte a lista de nomes de propriedade reservados.

O armazenamento de dados em si permite esses nomes. Se for necessário a um aplicativo que uma entidade do armazenamento de dados tenha uma propriedade com nome semelhante a uma palavra reservada na API Python, o aplicativo poderá usar uma propriedade fixa e passar o argumento name ao construtor da classe Property. Consulte o construtor da classe Property.

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