Entidades, propriedades e chaves

Observação: é altamente recomendável a desenvolvedores que criam novos aplicativos usar a biblioteca de cliente NDB, porque ela oferece diversos benefícios em comparação com esta biblioteca de cliente, como armazenamento em cache automático de entidades por meio da API Memcache. Se você estiver usando a antiga biblioteca de cliente DB, leia o Guia de migração de DB para NDB.

Os objetos de dados no Datastore são conhecidos como entidades. Uma entidade tem uma ou mais propriedades nomeadas, que podem ter um ou mais valores. Entidades do mesmo tipo não precisam ter as mesmas propriedades. Além disso, os valores de uma entidade para uma determinada propriedade não precisam ser todos do mesmo tipo de dados. Se necessário, um aplicativo pode estabelecer e aplicar essas restrições no próprio modelo de dados.

O Datastore é compatível com uma grande variedade de tipos de dados como valores de propriedade. Estes são alguns deles:

  • Números inteiros
  • Números de ponto flutuante
  • Strings
  • Datas
  • Dados binários

Para uma lista completa de tipos, consulte Propriedades e tipos de valor.

Cada entidade no Datastore tem uma chave que a identifica de maneira exclusiva. A chave tem os seguintes componentes:

  • O namespace da entidade, que possibilita a multilocação
  • O tipo da entidade, que a categoriza para as consultas do Datastore
  • Um identificador da entidade individual, que pode ser:
    • Uma string de nome da chave
    • um código numérico inteiro.
  • Um caminho ancestral opcional que localiza a entidade na hierarquia do Datastore.

Um aplicativo pode buscar uma entidade individual do Datastore usando a chave da entidade ou recuperar uma ou mais entidades emitindo uma consulta com base nas chaves ou nos valores de propriedade das entidades.

O SDK do App Engine para Python inclui uma biblioteca de modelagem de dados para representar entidades do Datastore como instâncias de classes do Python e para armazenar e recuperar essas entidades no Datastore.

O Datastore em si não impõe restrições na estrutura das entidades, como se uma determinada propriedade tem o valor de um tipo específico; esta tarefa é deixada para o aplicativo e para a biblioteca de modelagem de dados.

Tipos e identificadores

Cada entidade do Datastore é de um tipo específico, que a classifica para fins de consultas. Por exemplo, um aplicativo de recursos humanos pode representar cada funcionário de uma empresa com uma entidade do tipo Employee. Na API Datastore para Python, o tipo de uma entidade é determinado pela classe de modelo dela, que você define no aplicativo como uma subclasse da classe de biblioteca de modelagem de dados db.Model. O nome da classe do modelo se torna o tipo de entidades pertencentes a ela. Por serem reservados, todos os nomes de tipo que começam com dois sublinhados (__) não podem ser usados.

O exemplo a seguir cria uma entidade do tipo Employee, preenche os valores da propriedade e a salva no Datastore:

import datetime
from google.appengine.ext import db


class Employee(db.Model):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hire_date = db.DateProperty()
  attended_hr_training = db.BooleanProperty()


employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

A classe Employee declara quatro propriedades para o modelo de dados: first_name, last_name, hire_date e attended_hr_training. A superclasse Model garante que os atributos de objetos Employee estejam em conformidade com este modelo: por exemplo, uma tentativa de atribuir um valor de string ao atributo hire_date resultaria em um erro de tempo de execução, pois o modelo de dados para hire_date foi declarado como db.DateProperty.

Além de um tipo, cada entidade tem um identificador, que é atribuído quando ela é criada. Como ele é parte da chave da entidade, o identificador é associado permanentemente à entidade e não pode ser alterado. Ele pode ser atribuído de duas formas:

  • Seu aplicativo pode especificar a própria string de nome de chave para a entidade.
  • O Datastore atribui automaticamente à entidade um ID numérico inteiro.

Para atribuir uma entidade a um nome de chave, forneça o argumento nomeado key_name ao construtor da classe de modelo ao criar a entidade:

# Create an entity with the key Employee:'asalieri'.
employee = Employee(key_name='asalieri')

Para que o Datastore atribua um código numérico automaticamente, omita o argumento key_name:

# Create an entity with a key such as Employee:8261.
employee = Employee()

Como atribuir identificadores

O Datastore pode ser configurado para gerar códigos automaticamente usando duas políticas de identificação automática diferentes:

  • A política default gera uma sequência aleatória de IDs não utilizados que são aproximadamente distribuídos de maneira uniforme. Cada ID pode ter até 16 dígitos decimais.
  • A política legacy cria uma sequência de IDs inteiros menores e não consecutivos.

Para exibir os IDs de entidade para o usuário e/ou depender da ordem deles, use a alocação manual.

O Datastore gera uma sequência aleatória de IDs não utilizados que são distribuídos de maneira quase uniforme. Cada ID pode ter até 16 dígitos decimais.

Caminhos ancestrais

As entidades do Cloud Datastore formam um espaço hierarquicamente estruturado, semelhante à estrutura de diretórios de um sistema de arquivos. Ao criar uma entidade, é possível designar outra entidade como mãe e a nova como filha. Ao contrário do que ocorre em um sistema de arquivos, a entidade mãe não precisa existir de verdade. Uma entidade sem mãe é uma entidade raiz. A associação entre uma entidade e a mãe é permanente e não pode ser alterada depois que a entidade é criada. O Cloud Datastore nunca atribuirá o mesmo ID numérico a duas entidades com a mesma mãe ou a duas entidades raiz (sem mãe).

A mãe de uma entidade, a mãe da mãe, e assim por diante são ancestrais dela. A filha, a filha da filha, e assim por diante são descendentes dela. Uma entidade raiz e todos os descendentes pertencem ao mesmo grupo de entidades. A sequência de entidades começando com uma entidade raiz e prosseguindo de pai para filho, levando a uma determinada entidade, constitui o caminho do ancestral dessa entidade. A chave completa que identifica a entidade consiste em uma sequência de pares de identificadores de tipo que especifica o caminho ancestral e termina com os da própria entidade:

[Person:GreatGrandpa, Person:Grandpa, Person:Dad, Person:Me]

Para uma entidade raiz, o caminho ancestral está vazio, e a chave consiste unicamente no próprio tipo e identificador da entidade:

[Person:GreatGrandpa]

Esse conceito é ilustrado pelo seguinte diagrama:

Mostra a relação entre a entidade raiz e as entidades filho no grupo de entidades

Para criar o pai de uma entidade, use o argumento parent para o construtor da classe de modelo ao criar a entidade filho. O valor desse argumento pode ser a própria entidade pai ou a chave dela. É possível conseguir a chave chamando o método key() da entidade pai. O exemplo a seguir cria uma entidade do tipo Address e mostra duas maneiras de designar uma entidade Employee como o pai dela:

# Create Employee entity
employee = Employee()
employee.put()

# Set Employee as Address entity's parent directly...
address = Address(parent=employee)

# ...or using its key
e_key = employee.key()
address = Address(parent=e_key)

# Save Address entity to datastore
address.put()

Transações e grupos de entidades

Toda tentativa de criar, atualizar ou excluir uma entidade ocorre no contexto de uma transação. Uma única transação pode incluir qualquer número dessas operações. Para manter a consistência dos dados, a transação assegura que todas as operações que ela contém sejam aplicadas ao Datastore como uma unidade ou, se alguma das operações falhar, que nenhuma delas seja aplicada. Além disso, todas as leituras com consistência forte (consultas ou operações get de ancestral) executadas na mesma transação seguem um instantâneo consistente dos dados.

Como mencionado acima, um grupo de entidades é um conjunto de entidades conectadas por meio de um ancestral a um elemento raiz comum. A organização dos dados em grupos de entidades pode limitar quais transações podem ser realizadas:

  • Todos os dados acessados por uma transação devem estar contidos em, no máximo, 25 grupos de entidades.
  • Se você quiser usar consultas em uma transação, os dados precisam estar organizados em grupos de entidades para que você possa especificar filtros de ancestral que corresponderão aos dados corretos.
  • Há um limite de capacidade de gravação de aproximadamente uma transação por segundo em um único grupo de entidades. Essa limitação existe porque, para fornecer alta confiabilidade e tolerância a falhas, o Datastore executa a replicação síncrona sem mestre de cada grupo de entidades em uma ampla área geográfica.

Em muitos aplicativos, é aceitável usar a consistência eventual (ou seja, uma consulta que não seja de ancestral abrangendo vários grupos de entidades, que às vezes pode retornar dados um pouco desatualizados) para uma visualização ampla de dados não relacionados e depois usar a consistência forte (uma consulta de ancestral ou uma operação get para uma única entidade) para visualizar ou editar um único conjunto de dados altamente relacionados. Em tais aplicativos, geralmente é uma boa abordagem usar um grupo de entidades separado para cada conjunto de dados altamente relacionados. Para mais informações, consulte Como estruturar uma consistência forte.

Propriedades e tipos de valores

Os valores de dados associados a uma entidade consistem em uma ou mais propriedades. Cada propriedade tem um nome e um ou mais valores. Uma propriedade pode ter valores de mais de um tipo, e duas entidades podem ter valores de tipos diferentes para a mesma propriedade. As propriedades podem ser indexadas ou não (consultas que ordenam ou filtram com base em uma propriedade P ignoram entidades em que P não seja indexada). Uma entidade pode ter no máximo 20.000 propriedades indexadas.

Estes são os tipos de valor compatíveis:

Tipo de valor Tipo(s) do Python Ordem de classificação Observações
Número inteiro int
long
Numérico Número inteiro de 64 bits, assinado
Número de ponto flutuante float Numérico Precisão dupla de 64 bits,
IEEE 754
Booleano bool False<True
String de texto (curta) str
unicode
Unicode
(str tratado como ASCII)
Até 1500 bytes
String de texto (longa) db.Text Nenhum Até 1 megabyte

Não indexado
String de bytes (curta) db.ByteString Ordem de bytes Até 1500 bytes
String de bytes (longa) db.Blob Nenhum Até 1 megabyte

Não indexado
Data e hora datetime.date
datetime.time
datetime.datetime
Cronológica
Ponto geográfico db.GeoPt Por latitude,
e depois longitude
Endereço postal db.PostalAddress Unicode
Número de telefone db.PhoneNumber Unicode
Endereço de e-mail db.Email Unicode
Usuário das Contas do Google users.User Endereço de e-mail
em ordem Unicode
Identificador de mensagens instantâneas db.IM Unicode
Link db.Link Unicode
Categoria db.Category Unicode
Classificação db.Rating Numérico
Chave do Datastore db.Key Por elementos do caminho
(tipo, identificador,
tipo, identificador...)
Chave Blobstore blobstore.BlobKey Ordem de bytes
Null NoneType Nenhum

Importante: é altamente recomendável não armazenar um UserProperty, porque ele inclui o endereço de e-mail e o ID exclusivo do usuário. Se um usuário alterar o endereço de e-mail, e você comparar o antigo User armazenado com o novo valor de User, eles não serão correspondentes.

Para strings de texto e dados binários não codificados (strings de bytes), o Datastore aceita dois tipos de valor:

  • Strings curtas (até 1500 bytes) são indexadas e podem ser usadas em condições de filtro de consulta e ordens de classificação.
  • Strings longas (até 1 megabyte) não são indexadas e não podem ser usadas em filtros de consulta e ordens de classificação.
Observação: o tipo de string de bytes longa é nomeado como Blob na API Datastore. Esse tipo não está relacionado aos blobs, conforme usado na API Blobstore.

Quando uma consulta envolve um campo com valores de tipos mistos, o Firestore usa uma ordem determinista com base nas representações internas:

  1. Valores nulos
  2. Números de ponto fixo
    • Números inteiros
    • Datas e horas
    • Classificações
  3. Valores booleanos
  4. Sequências de bytes
    • String de bytes
    • String Unicode
    • Chaves do Blobstore
  5. Números de ponto flutuante
  6. Pontos geográficos
  7. Usuários das Contas do Google
  8. Chaves do armazenamento de dados

Como strings de texto e de bytes longas não são indexadas, elas não têm uma ordem definida.

Trabalhar com entidades

Os aplicativos podem usar a API Datastore para criar, recuperar, atualizar e excluir entidades. Se o aplicativo souber a chave completa de uma entidade ou puder derivá-la da própria chave pai, tipo e identificador, ele pode usá-la para operar diretamente na entidade. O aplicativo também pode conseguir a chave de uma entidade por meio de uma consulta do Datastore. Para mais informações, consulte a página Consultas do Datastore.

Como criar uma entidade

Para criar uma nova entidade no Python, gere uma instância de uma classe de modelo, preencha as propriedades dela, se necessário, e chame o método put() para armazená-la no Datastore. Você pode especificar o nome da chave da entidade passando um argumento key_name para o construtor:

employee = Employee(key_name='asalieri',
                    first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Se você não informar um nome de chave, o Datastore gerará automaticamente um código numérico para a chave da entidade:

employee = Employee(first_name='Antonio',
                    last_name='Salieri')

employee.hire_date = datetime.datetime.now().date()
employee.attended_hr_training = True

employee.put()

Recuperar uma entidade

Para recuperar uma entidade identificada por uma determinada chave, transmita o Key como um argumento para a função db.get(). Você pode gerar o objeto Key usando o método de classe Key.from_path(). O caminho completo é uma sequência de entidades no caminho ancestral, com cada entidade representada pelo tipo (uma sequência) dela seguido do identificador (nome da chave ou código numérico):

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
address = db.get(address_k)

db.get() retorna uma instância da classe de modelo apropriada. Verifique se você importou a classe de modelo para a entidade que está sendo recuperada.

Atualizar uma entidade

Para atualizar uma entidade atual, modifique os atributos do objeto e chame o método put() dele. Os dados do objeto sobrescrevem a entidade atual. O objeto inteiro é enviado ao Datastore com todas as chamadas para put().

Para excluir uma propriedade, exclua o atributo do objeto do Python:

del address.postal_code

e salve o objeto.

Excluir uma entidade

Dada a chave de uma entidade, é possível excluir a entidade com a função db.delete()

address_k = db.Key.from_path('Employee', 'asalieri', 'Address', 1)
db.delete(address_k)

ou chamando o próprio método delete() da entidade:

employee_k = db.Key.from_path('Employee', 'asalieri')
employee = db.get(employee_k)

# ...

employee.delete()

Operações em lote

As funções db.put(), db.get() e db.delete() e (suas equivalentes assíncronas db.put_async(), db.get_async() e db.delete_async()) podem aceitar um argumento de lista para atuar em várias entidades em uma única chamada do Datastore:

# A batch put.
db.put([e1, e2, e3])

# A batch get.
entities = db.get([k1, k2, k3])

# A batch delete.
db.delete([k1, k2, k3])

As operações em lote não alteram os custos. Você será cobrado por todas as chaves em uma operação em lote, independentemente de cada chave existir ou não. O tamanho das entidades envolvidas em uma operação não afeta o custo.

Como excluir entidades em massa

Se você precisar excluir um grande número de entidades, recomendamos usar o Dataflow para excluir entidades em massa.

Como usar uma lista vazia

Para a interface NDB, o Datastore escreveu historicamente uma lista vazia como uma propriedade omitida para propriedades estáticas e dinâmicas. Para manter a compatibilidade com versões anteriores, esse comportamento continua sendo o padrão. Para modificar isso globalmente ou por ListProperty, defina o argumento write_empty_list como true na classe Property. Após isso, a lista vazia será gravada no Datastore e poderá ser lida como uma lista vazia.

Para a interface DB, as gravações de lista vazia não eram historicamente permitidas se a propriedade fosse dinâmica. Se tentasse isso, você receberia um erro. Isso significa que não há comportamento padrão que precise ser preservado para compatibilidade com versões anteriores das propriedades dinâmicas DB. Dessa maneira, basta escrever e ler a lista vazia no modelo dinâmico sem nenhuma alteração.

No entanto, para propriedades estáticas DB, a lista vazia foi gravada como uma propriedade omitida, e esse comportamento continua por padrão para compatibilidade com versões anteriores. Se quiser ativar listas vazias para propriedades estáticas DB, defina o argumento write_empty_list como true na classe Property. Após isso, a lista vazia será gravada no Datastore.