Entidades, propriedades e chaves

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

Os objetos de dados no Datastore são conhecidos como entidades. Uma entidade tem uma ou mais propriedades com nome, cada uma das quais pode ter um ou mais valores. As entidades do mesmo tipo não têm de ter as mesmas propriedades e os valores de uma entidade para uma determinada propriedade não têm de ser todos do mesmo tipo de dados. (Se necessário, uma aplicação pode estabelecer e aplicar essas restrições no seu próprio modelo de dados.)

O Datastore suporta uma variedade de tipos de dados para valores de propriedades. Estas incluem, entre outras:

  • Números inteiros
  • Números de vírgula flutuante
  • Strings
  • Datas
  • Dados binários

Para ver uma lista completa de tipos, consulte o artigo Propriedades e tipos de valores.

Cada entidade no Datastore tem uma chave que a identifica de forma exclusiva. A chave é composta pelos seguintes componentes:

  • O espaço de nomes da entidade, que permite a multilocação
  • O tipo da entidade, que a categoriza para efeitos de consultas do Datastore
  • Um identificador para a entidade individual, que pode ser
    • Uma string de nome da chave
    • um ID numérico inteiro
  • Um caminho do antepassado opcional que localiza a entidade na hierarquia do Datastore

Uma aplicação pode obter uma entidade individual do Datastore através da chave da entidade ou pode obter uma ou mais entidades emitindo uma consulta com base nas chaves ou nos valores das propriedades das entidades.

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

O Datastore em si não aplica restrições à estrutura das entidades, como se uma determinada propriedade tem um valor de um tipo específico. Esta tarefa é deixada à aplicação e à biblioteca de modelagem de dados.

Tipos e identificadores

Cada entidade do Datastore é de um tipo específico,que categoriza a entidade para fins de consultas: por exemplo, uma aplicação de recursos humanos pode representar cada funcionário de uma empresa com uma entidade do tipo Employee. Na API Python Datastore, o tipo de uma entidade é determinado pela respetiva classe de modelo, que define na sua aplicação como uma subclasse da classe da biblioteca de modelagem de dados db.Model. O nome da classe do modelo torna-se o tipo das entidades pertencentes à mesma. Todos os nomes de tipos que começam com dois sublinhados (__) estão reservados e não podem ser usados.

O exemplo seguinte cria uma entidade do tipo Employee, preenche os respetivos valores de propriedades e guarda-a 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 dos objetos Employee estão em conformidade com este modelo: por exemplo, uma tentativa de atribuir um valor de string ao atributo hire_date resultaria num erro de tempo de execução, uma vez que o modelo de dados para hire_date foi declarado como db.DateProperty.

Além de um tipo, cada entidade tem um identificador, atribuído quando a entidade é criada. Uma vez que faz parte da chave da entidade, o identificador está associado permanentemente à entidade e não pode ser alterado. Pode ser atribuído de duas formas:

  • A sua aplicação pode especificar a sua própria string de nome da chave para a entidade.
  • Pode fazer com que o Datastore atribua automaticamente à entidade um ID numérico inteiro.

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

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

Para que o Datastore atribua automaticamente um ID numérico, omita o argumento key_name:

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

Atribuir identificadores

O Datastore pode ser configurado para gerar IDs automáticos através de duas políticas de IDs automáticos diferentes:

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

Se quiser apresentar os IDs das entidades ao utilizador e/ou depender da respetiva ordem, a melhor opção é usar a atribuição manual.

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

Caminhos de antecessores

As entidades no Cloud Datastore formam um espaço estruturado hierarquicamente semelhante à estrutura de diretórios de um sistema de ficheiros. Quando cria uma entidade, pode, opcionalmente, designar outra entidade como respetiva principal. A nova entidade é uma secundária da entidade principal (tenha em atenção que, ao contrário de um sistema de ficheiros, a entidade principal não tem de existir efetivamente). Uma entidade sem um elemento principal é uma entidade raiz. A associação entre uma entidade e a respetiva entidade principal é permanente e não pode ser alterada depois de a entidade ser criada. O Cloud Datastore nunca atribui o mesmo ID numérico a duas entidades com o mesmo elemento principal ou a duas entidades raiz (as que não têm um elemento principal).

O principal de uma entidade, o principal do principal e assim sucessivamente, são os seus ancestrais; os seus secundários, os secundários dos secundários e assim sucessivamente, são os seus descendentes. Uma entidade raiz e todos os respetivos descendentes pertencem ao mesmo grupo de entidades. A sequência de entidades que começa com uma entidade de raiz e prossegue do elemento principal para o elemento secundário, conduzindo a uma determinada entidade, constitui o caminho do antepassado dessa entidade. A chave completa que identifica a entidade consiste numa sequência de pares de tipo-identificador que especificam o respetivo caminho de antepassados e terminam com os da própria entidade:

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

Para uma entidade raiz, o caminho de antepassados está vazio e a chave consiste apenas no tipo e no identificador da própria entidade:

[Person:GreatGrandpa]

Este conceito é ilustrado pelo diagrama seguinte:

Mostra a relação da entidade raiz com as entidades
  secundárias no grupo de entidades

Para designar o elemento principal de uma entidade, use o argumento parent no construtor da classe do modelo quando criar a entidade secundária. O valor deste argumento pode ser a própria entidade principal ou a respetiva chave. Pode obter a chave chamando o método key() da entidade principal. O exemplo seguinte cria uma entidade do tipo Address e mostra duas formas de designar uma entidade Employee como a respetiva entidade principal:

# 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

Todas as tentativas de criar, atualizar ou eliminar uma entidade ocorrem 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 garante que todas as operações que contém são aplicadas ao Datastore como uma unidade ou, se alguma das operações falhar, que nenhuma delas é aplicada. Além disso, todas as leituras fortemente consistentes (consultas de antecessores ou obtenções) realizadas na mesma transação observam um instantâneo consistente dos dados.

Conforme mencionado acima, um grupo de entidades é um conjunto de entidades ligadas através da ascendência a um elemento raiz comum. A organização dos dados em grupos de entidades pode limitar as transações que podem ser realizadas:

  • Todos os dados acedidos por uma transação têm de estar contidos num máximo de 25 grupos de entidades.
  • Se quiser usar consultas numa transação, os seus dados têm de estar organizados em grupos de entidades de forma que possa especificar filtros de antecessores que correspondam aos dados certos.
  • Existe um limite de débito de gravação de cerca de uma transação por segundo num único grupo de entidades. Esta limitação existe porque o Datastore executa a replicação síncrona sem mestre de cada grupo de entidades numa vasta área geográfica para oferecer elevada fiabilidade e tolerância a falhas.

Em muitas aplicações, é aceitável usar a consistência eventual (ou seja, uma consulta não ancestral que abranja vários grupos de entidades, que pode, por vezes, devolver dados ligeiramente desatualizados) quando obtém uma vista geral de dados não relacionados e, em seguida, usar a consistência forte (uma consulta ancestral ou um get de uma única entidade) quando visualiza ou edita um único conjunto de dados altamente relacionados. Em tais aplicações, é normalmente uma boa abordagem usar um grupo de entidades separado para cada conjunto de dados altamente relacionados. Para mais informações, consulte o artigo Estruturar para uma forte consistência.

Propriedades e tipos de valores

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

São suportados os seguintes tipos de valores:

Tipo de valor Tipos de Python Ordenação Notas
Número inteiro int
long
Numérico Número inteiro de 64 bits, com sinal
Número de vírgula 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 dos 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ógico
Ponto geográfico db.GeoPt Por latitude,
depois longitude
Morada db.PostalAddress Unicode
Número de telefone db.PhoneNumber Unicode
Endereço de email db.Email Unicode
Utilizador da Conta Google users.User Endereço de email
por 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 armazenamento de dados db.Key Por elementos de caminho
(tipo, identificador,
tipo, identificador...)
Chave do Blobstore blobstore.BlobKey Ordem dos bytes
Nulo NoneType Nenhum

Importante: recomendamos vivamente que não armazene um UserProperty, uma vez que inclui o endereço de email e o ID exclusivo do utilizador. Se um utilizador alterar o respetivo endereço de email e comparar o valor User antigo armazenado com o novo valor User, não vai haver correspondência.

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

  • As strings curtas (até 1500 bytes) são indexadas e podem ser usadas em condições de filtro de consultas e ordens de ordenação.
  • As strings longas (até 1 megabyte) não são indexadas e não podem ser usadas em filtros de consultas nem ordens de ordenação.
Nota: o tipo de string de bytes longo é denominado Blob na API Datastore. Este tipo não está relacionado com blobs usados na API Blobstore.

Quando uma consulta envolve uma propriedade com valores de tipos mistos, o Datastore usa uma ordenação determinística 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 vírgula flutuante
  6. Pontos geográficos
  7. Utilizadores de Contas Google
  8. Chaves do Datastore

Uma vez que as strings de texto longas e as strings de bytes longas não são indexadas, não têm uma ordenação definida.

Trabalhar com entidades

As aplicações podem usar a API Datastore para criar, obter, atualizar e eliminar entidades. Se a aplicação souber a chave completa de uma entidade (ou puder derivá-la da respetiva chave principal, tipo e identificador), pode usar a chave para operar diretamente na entidade. Uma aplicação também pode obter a chave de uma entidade como resultado de uma consulta do Datastore. Consulte a página Consultas do Datastore para mais informações.

Criar uma entidade

Em Python, cria uma nova entidade construindo uma instância de uma classe de modelo, preenchendo as respetivas propriedades, se necessário, e chamando o respetivo método put() para a guardar no Datastore. Pode especificar o nome da chave da entidade transmitindo um argumento key_name ao 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 não fornecer um nome de chave, o Datastore gera automaticamente um ID 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()

Obter uma entidade

Para obter uma entidade identificada por uma determinada chave, transmita o objeto Key como um argumento para a função db.get(). Pode gerar o objeto Key através do método de classe Key.from_path(). O caminho completo é uma sequência de entidades no caminho de antepassados, com cada entidade representada pelo respetivo tipo (uma string) seguido do respetivo identificador (nome da chave ou ID numérico):

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

db.get() devolve uma instância da classe de modelo adequada. Certifique-se de que importou a classe de modelo para a entidade que está a ser obtida.

Atualizar uma entidade

Para atualizar uma entidade existente, modifique os atributos do objeto e, em seguida, chame o respetivo método put(). Os dados do objeto substituem a entidade existente. O objeto completo é enviado para o Datastore com cada chamada para put().

Para eliminar uma propriedade, elimine o atributo do objeto Python:

del address.postal_code

Em seguida, guarde o objeto.

Eliminar uma entidade

Dada a chave de uma entidade, pode eliminar 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 método delete() da própria 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 as respetivas contrapartes assíncronas db.put_async(), db.get_async() e db.delete_async()) podem aceitar um argumento de lista para agir em várias entidades numa ú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 seus custos. É-lhe cobrado o valor de cada chave numa operação em lote, quer a chave exista ou não. A dimensão das entidades envolvidas numa operação não afeta o custo.

Eliminar entidades em massa

Se precisar de eliminar um grande número de entidades, recomendamos que use o Dataflow para eliminar entidades em massa.

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, este comportamento continua a ser o predefinido. Para substituir esta opção globalmente ou com base em ListProperty, defina o argumento write_empty_list como true na sua classe Property. A lista vazia é, em seguida, escrita no Datastore e pode ser lida como uma lista vazia.

Para a interface da BD, as escritas de listas vazias não eram permitidas de todo historicamente se a propriedade fosse dinâmica: se tentasse fazê-lo, recebia um erro. Isto significa que não é necessário preservar nenhum comportamento predefinido para retrocompatibilidade para propriedades dinâmicas da base de dados, pelo que pode simplesmente escrever e ler a lista vazia no modelo dinâmico sem alterações.

No entanto, para propriedades estáticas da base de dados, a lista vazia foi escrita como uma propriedade omitida, e este comportamento continua predefinido para retrocompatibilidade. Se quiser ativar listas vazias para propriedades estáticas da BD, use o argumento write_empty_list para true na sua classe de propriedade. Em seguida, a lista vazia é escrita no Datastore.