O Python 2 não é mais compatível com a comunidade. Recomendamos que você migre aplicativos do Python 2 para o Python 3.

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 designadas, 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 determinado tipo 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:

  • O aplicativo especifica a própria string de nome de chave para a entidade.
  • O Datastore pode atribuir automaticamente um código numérico inteiro à entidade.

Para atribuir um nome de chave a uma entidade, 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 automáticos usando duas diferentes políticas de código automático:

  • A política default gera uma sequência aleatória de códigos não utilizados que são distribuídos de maneira quase uniformemente. Cada código pode ter até 16 dígitos decimais.
  • A política legacy cria uma sequência de códigos inteiros menores, não consecutivos.

Para exibir os códigos 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 códigos não utilizados que são distribuídos de maneira quase uniforme. Cada código pode ter até 16 dígitos decimais.

Os valores de código atribuídos pelo sistema são totalmente exclusivos para o grupo de entidades. Se você copiar uma entidade de um grupo de entidades ou namespace para outro e quiser preservar a parte do código da chave, atribua o código primeiro para impedir que o Datastore selecione esse código em uma atribuição futura.

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 designar 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 apresentar falhas, que nenhuma delas seja aplicada. Além disso, todas as leituras com consistência forte (consultas ou operações get para 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) ao ter uma visualização ampla dos dados não relacionados, e na sequência usar a consistência forte (uma consulta de ancestral ou uma operação getget para uma única entidade) ao 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érica Número inteiro de 64 bits, assinado
Número de ponto flutuante float Numérica Precisão dupla de 64 bits,
IEEE 754.
Booleano bool False<True
String de texto (curta) str
unicode
Unicode
(str tratado como ASCII)
Até 1.500 bytes
String de texto (longa) db.Text Nenhum Até 1 megabyte

Não indexado
String de bytes (curta) db.ByteString Ordem de bytes Até 1.500 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érica
Chave do Datastore db.Key Por elementos de caminho
(tipo, identificador,
tipo, identificador...)
Chave Blobstore blobstore.BlobKey Ordem de bytes
Nulo NoneType Nenhum

Importante: é altamente recomendável não armazenar um UserProperty, porque ele inclui o endereço de e-mail e o código exclusivo do usuário. Se um usuário alterar o endereço de e-mail e você comparar o User antigo 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 inclui uma propriedade com valores de tipos mistos, o Datastore usa uma ordem determinística baseada 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 Datastore

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

Como 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, do tipo e do identificador), ele poderá 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

No Python, você cria uma nova entidade construindo uma instância de uma classe de modelo, preenchendo as propriedades dela se necessário e chamando o método put() para salvá-la no Datastore. É possível 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()

Como recuperar uma entidade

Para recuperar uma entidade identificada por uma determinada chave, transmita o objeto 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.

Como atualizar uma entidade

Para atualizar uma entidade atual, modifique os atributos do objeto e chame o método put(). 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.

Como excluir uma entidade

Dada a chave de uma entidade, você pode 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 substituir isso globalmente ou por ListProperty, defina o argumento write_empty_list como true na sua classe Property. A lista vazia é gravada no Datastore e pode 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.