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:
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.
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:
- 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 ponto flutuante
- Pontos geográficos
- Usuários das Contas do Google
- 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 comotrue
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.