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:
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.
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:
- Valores nulos
- Números de ponto fixo
- Números inteiros
- Datas e horas
- Classificações
- Valores booleanos
- Sequências de bytes
- String de bytes
- String Unicode
- Chaves do Blobstore
- Números de vírgula flutuante
- Pontos geográficos
- Utilizadores de Contas Google
- 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 comotrue
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.