Entidades, propiedades y claves

Nota: Se recomienda enfáticamente a los desarrolladores que compilan aplicaciones nuevas que usen la biblioteca cliente de NDB, ya que tiene muchas ventajas en comparación con esta biblioteca cliente, como el almacenamiento en caché automático de entidades mediante la API de Memcache. Si por el momento usas la biblioteca cliente de DB anterior, lee la Guía de migración de DB a NDB.

Los objetos de datos en Datastore se conocen como entidades. Una entidad posee una o más propiedades con nombre, que pueden tener uno o más valores. No es necesario que las entidades del mismo tipo tengan las mismas propiedades, ni que los valores de una entidad para una propiedad determinada correspondan al mismo tipo de datos. (Si fuera necesario, una aplicación puede establecer y aplicar estas restricciones en su propio modelo de datos).

Datastore es compatible con una variedad de tipos de datos para valores de propiedad. Se incluyen, entre otros:

  • Números enteros
  • números de punto flotante
  • Strings
  • Fechas
  • Datos binarios

Para obtener una lista completa de los tipos, consulta Tipos de valores y propiedades.

Cada entidad en Datastore tiene una clave que la identifica de manera única. La clave consta de los siguientes componentes:

  • El espacio de nombres de la entidad, que permite la función de multiusuario
  • El tipo de entidad, que la clasifica para realizar consultas de Datastore
  • Un identificador de la entidad individual, que puede ser de alguna de las siguientes clases:
    • Una string de nombre de clave
    • un ID numérico entero
  • Una ruta principal opcional que ubica la entidad dentro de la jerarquía de Datastore

Una aplicación puede recuperar una entidad individual de Datastore mediante la clave de la entidad, o una o más entidades si emite una consulta basada en los valores de propiedad o las claves de las entidades.

El SDK de App Engine para Python incluye una biblioteca de modelado de datos que permite representar las entidades de Datastore como instancias de clases de Python, y almacenarlas y recuperarlas en Datastore.

Datastore no aplica ninguna restricción en las estructuras de las entidades. Por ejemplo, si una propiedad posee un valor de un tipo en particular, esta tarea le corresponde a la aplicación y a la biblioteca de modelado de datos.

Identificadores y tipos

Cada entidad de Datastore es de un tipo en particular que clasifica la entidad para realizar consultas; por ejemplo, una aplicación de recursos humanos podría representar a cada empleado de una empresa mediante una entidad del tipo Employee. En la API de Datastore para Python, el tipo de entidad se determina mediante su clase de modelo, que defines en tu aplicación como una subclase de la clase de biblioteca de modelado de datos db.Model. El nombre de la clase de modelo se convierte en el tipo de entidades que le pertenecen. Todos los nombres de tipos que comienzan con dos guiones bajos (__) están reservados y no se pueden usar.

En el siguiente ejemplo, se crea una entidad de tipo Employee, se propagan sus valores de propiedad y se la guarda en 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()

La clase Employee declara cuatro propiedades del modelo de datos: first_name, last_name, hire_date y attended_hr_training. La superclase Model garantiza que los atributos de objetos Employee cumplan con este modelo; por ejemplo, si se intentara asignar un valor de string al atributo hire_date, se generaría un error de entorno de ejecución, ya que el modelo de datos de hire_date se declaró como db.DateProperty.

Además de un tipo, cada entidad posee un identificador, que se le asigna en el momento de su creación. Debido a que es parte de la clave de la entidad, el identificador se encuentra asociado de forma permanente a la entidad y no se puede cambiar. Se puede asignar de dos maneras:

  • Tu aplicación puede especificar su propia string de nombre de clave para la entidad.
  • Puedes hacer que Datastore asigne de forma automática un ID de número entero a la entidad.

Para asignar un nombre de clave a una entidad, proporciona el argumento key_name al constructor de la clase del modelo cuando crees la entidad:

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

Para que Datastore asigne un ID numérico de forma automática, omite el argumento key_name:

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

Asigna identificadores

Datastore se puede configurar para generar ID de forma automática mediante dos políticas de identificación automática diferentes:

  • La política default genera una secuencia aleatoria de ID sin usar con una distribución bastante uniforme. Cada ID puede tener hasta 16 dígitos decimales.
  • La política legacy crea una secuencia de ID de números enteros no consecutivos más breves.

Si quieres mostrar al usuario los ID de la entidad o depender de su pedido, lo mejor que puedes hacer es usar la asignación manual.

Datastore genera una secuencia aleatoria de ID sin usar con una distribución bastante uniforme. Cada ID puede tener hasta 16 dígitos decimales.

Rutas de principales

Las entidades de Cloud Datastore conforman un espacio con estructura jerárquica, similar a la estructura de directorios de un sistema de archivos. Cuando creas una entidad, tienes la opción de designar otra entidad como superior, en cuyo caso la nueva entidad es secundaria respecto de la entidad superior (ten en cuenta que, a diferencia de lo que ocurre en un sistema de archivos, no es necesario que efectivamente exista una entidad superior). Las entidades que no tienen una principal se denominan entidades raíz. La asociación entre una entidad secundaria y la entidad superior es permanente, y no puede cambiarse una vez creada la entidad. Cloud Datastore nunca asignará el mismo ID numérico a dos entidades asociadas a la misma entidad superior ni a dos entidades raíz (no asociadas a una entidad superior).

La entidad superior de una entidad determinada, la superior de la superior y así sucesivamente son sus principales. Las entidades secundarias respecto de una entidad determinada, las secundarias de las secundarias y así sucesivamente son sus descendientes. Una entidad raíz y todos sus descendientes pertenecen al mismo grupo de entidad. La secuencia de entidades que comienza con una entidad raíz y sigue de principal a secundaria, hasta llegar a una entidad determinada, constituye la ruta de principal de esa entidad. La clave completa que identifica una entidad consta de una secuencia de pares de tipo-identificador que especifican la ruta de principales y terminan con el tipo y el identificador de la propia entidad:

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

En el caso de una entidad raíz, la ruta de principales está vacía, y la clave consta solo del tipo y el identificador de la entidad:

[Person:GreatGrandpa]

Este concepto se ilustra en el diagrama siguiente:

Muestra la relación de la entidad raíz con las entidades secundarias del grupo de entidad

A fin de designar el superior de una entidad, usa el argumento parent para el constructor de la clase de modelo cuando crees la entidad secundaria. El valor de este argumento puede ser la entidad principal en sí o su clave. Para obtener la clave, llama al método key() de la entidad principal. En el siguiente ejemplo, se crea una entidad del tipo Address y se muestran dos formas de designar una entidad Employee como su superior:

# 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()

Transacciones y grupos de entidades

Cada intento de crear, actualizar o borrar una entidad ocurre en el contexto de una transacción. Una transacción puede incluir una cantidad indeterminada de las operaciones mencionadas. Para conservar la coherencia de los datos, la transacción garantiza que todas las operaciones que contiene se apliquen a Datastore como una unidad o, si alguna de ellas falla, que no se aplique ninguna. Además, todas las operaciones de lectura de coherencia sólida (consultas principales o get) que se realizan dentro de la misma transacción ven una sola instantánea coherente de los datos.

Como se mencionó antes, un grupo de entidades es un conjunto de entidades conectadas de manera ascendente a un elemento raíz común. La organización de los datos en grupos de entidades puede limitar las transacciones que se pueden realizar:

  • Todos los datos a los que accede una transacción deben estar contenidos en 25 grupos de entidades como máximo.
  • Si quieres usar consultas dentro de una transacción, los datos deben estar organizados en grupos de entidades de manera tal que puedas especificar filtros de principales que coincidan con los datos correctos.
  • Existe un límite de capacidad de procesamiento de escrituras de alrededor de una transacción por segundo dentro de un mismo grupo de entidades. Esta limitación existe porque Datastore realiza una replicación síncrona sin instancia maestra de cada grupo de entidades en un área geográfica amplia para brindar alta confiabilidad y tolerancia a errores.

En muchas aplicaciones, es aceptable usar la coherencia eventual (es decir, una consulta no principal que abarque varios grupos de entidades y que a veces puede mostrar datos ligeramente inactivos) cuando se obtiene una vista amplia de datos no relacionados y, luego, usar la coherencia sólida (una consulta principal o una get de una sola entidad) cuando se visualiza o edita un solo conjunto de datos muy relacionados. En tales aplicaciones, suele ser una buena estrategia usar un grupo de entidades separado para cada conjunto de datos sumamente relacionados. Si deseas obtener más información, consulta Estructura datos para lograr una coherencia sólida.

Tipos de valores y propiedades

Los valores de datos asociados con una entidad constan de una o más propiedades. Cada propiedad tiene un nombre y uno o más valores. Una propiedad puede tener valores de más de un tipo, y dos entidades pueden tener valores de diferentes tipos para la misma propiedad. Las propiedades pueden o no estar indexadas (las consultas que ordenan o filtran en una propiedad P ignorarán las entidades en las que P no esté indexada). Una entidad puede tener como máximo 20,000 propiedades indexadas.

Se admiten los tipos de valor siguientes:

Tipo de valor Tipo(s) de Python Orden Notas
Número entero int
long
Numérico Número entero de 64 bits, con firma
Número de punto flotante float Numérico Precisión doble de 64 bits,
IEEE 754
Booleano bool False<True
String de texto (corta) str
unicode
Unicode
(str tratado como ASCII)
Hasta 1,500 bytes
String de texto (larga) db.Text Ninguno Hasta 1 megabyte

Sin indexar
String de bytes (corta) db.ByteString Orden de bytes Hasta 1,500 bytes
String de bytes (larga) db.Blob Ninguno Hasta 1 megabyte

Sin indexar
Fecha y hora datetime.date
datetime.time
datetime.datetime
Cronológico
Punto geográfico db.GeoPt Por latitud,
luego por longitud
Dirección postal db.PostalAddress Unicode
Número de teléfono db.PhoneNumber Unicode
Dirección de correo electrónico db.Email Unicode
Usuario de Cuentas de Google users.User Dirección de correo electrónico
en orden Unicode
Controlador de mensajería instantánea db.IM Unicode
Vínculo db.Link Unicode
Categoría db.Category Unicode
Calificación db.Rating Numérico
Clave de Datastore db.Key Por elementos de ruta de acceso
(tipo, identificador,
tipo, identificador…)
Clave de Blobstore blobstore.BlobKey Orden de bytes
Null NoneType Ninguno

Importante: Recomendamos que no almacenes una UserProperty, ya que incluye la dirección de correo electrónico y el ID único del usuario. Si un usuario cambia su dirección de correo electrónico y comparas su User anterior almacenado con el valor de User nuevo, no coincidirán.

Para strings de texto y datos binarios sin codificación (strings de bytes), Datastore es compatible con dos tipos de valores:

  • Las strings cortas (hasta 1,500 bytes) se indexan y pueden usarse en condiciones de filtros de consultas y para ordenar elementos.
  • Las strings largas (de hasta 1 megabyte) no se indexan ni pueden usarse en filtros de consultas ni para ordenar elementos.
Nota: El tipo de string de bytes larga se denomina Blob en la API de Datastore. Este tipo no está relacionado con los BLOB, como se usa en la API de Blobstore.

Cuando una consulta incluye una propiedad con valores de varios tipos, Datastore usa un orden determinista basado en representaciones internas:

  1. Valores nulos
  2. Números de coma fija
    • Números enteros
    • Fechas y horas
    • Calificaciones
  3. Valores booleanos
  4. Secuencias de bytes
    • String de bytes
    • String de Unicode
    • Claves de Blobstore
  5. Números de coma flotante
  6. Puntos geográficos
  7. Usuarios de Cuentas de Google
  8. Claves de Datastore

Las strings de texto largas y las strings de bytes largas no están indexadas y no tienen un orden definido.

Trabaja con entidades

Las aplicaciones pueden usar la API de Datastore para crear, recuperar, actualizar y borrar entidades. Si la aplicación conoce la clave completa de una entidad (o puede obtenerla del tipo, identificador o clave de la superior), puede usar la clave para operar directamente con la entidad. Una aplicación también puede obtener la clave de una entidad como resultado de una consulta de Datastore. Visita la página Consultas de Datastore para obtener más información.

Cómo crear una entidad

En Python, para crear una entidad nueva debes construir una instancia de una clase de modelo, propagar sus propiedades, si es necesario, y llamar al método put() a fin de guardarla en Datastore. Para especificar el nombre clave de la entidad, pasa un argumento key_name al constructor:

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()

Si no proporcionas un nombre de clave, Datastore generará de forma automática un ID numérico para la clave de la entidad:

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

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

employee.put()

Cómo recuperar una entidad

Para recuperar una entidad identificada mediante una clave determinada, pasa el objeto Key como un argumento a la función db.get(). Puedes generar el objeto Key mediante el método de clase Key.from_path(). La ruta de acceso completa es una secuencia de entidades en la ruta de principales, en la que cada entidad está representada por su tipo (una string) seguido de su identificador (nombre de clave o ID numérico):

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

db.get() muestra una instancia de la clase de modelo apropiada. Asegúrate de haber importado la clase de modelo para la entidad que se está recuperando.

Cómo actualizar una entidad

Para actualizar una entidad existente, modifica los atributos del objeto y llama al método put(). Los datos del objeto reemplazan la entidad existente. El objeto completo se envía a Datastore con cada llamada a put().

Para borrar una propiedad, borra el atributo del objeto de Python:

del address.postal_code

luego guarda el objeto.

Cómo borrar una entidad

Si tienes la clave correspondiente, puedes borrar una entidad con la función db.delete()

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

o mediante una llamada al método delete() de la entidad:

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

# ...

employee.delete()

Operaciones por lotes

Las funciones db.put(), db.get() y db.delete() (y sus contrapartes asíncronas db.put_async(), db.get_async() y db.delete_async()) pueden aceptar un argumento de lista para que opere en varias entidades en una sola llamada a 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])

Las operaciones por lotes no afectan los costos. Se te cobrará por cada clave de una operación por lotes, independientemente de si la clave existe o no. El tamaño de las entidades involucradas en una operación no afecta el costo.

Borrar entidades de manera masiva

Si necesitas borrar una gran cantidad de entidades, te recomendamos usar Dataflow para borrar entidades de manera masiva.

Usa una lista vacía

Para la interfaz de NDB, Datastore siempre escribía una lista vacía como una propiedad omitida para las propiedades estáticas y las dinámicas. Para mantener la retrocompatibilidad, este sigue siendo el comportamiento predeterminado. Para anular ese funcionamiento de forma global o por ListProperty, debes establecer el argumento write_empty_list en true en tu clase Property. La lista vacía se escribe en Datastore y se puede leer como una lista vacía.

Para la interfaz de DB, no se permitían escrituras de listas vacías si la propiedad era dinámica: si lo intentabas, se producía un error. Esto significa que no existe un comportamiento predeterminado que deba conservarse para garantizar la compatibilidad con versiones anteriores de las propiedades dinámicas de DB, por lo que tan solo puedes escribir y leer la lista vacía en el modelo dinámico sin ningún cambio.

No obstante, para las propiedades estáticas de DB, la lista vacía se escribía como una propiedad omitida y este comportamiento continúa de forma predeterminada para garantizar la compatibilidad con versiones anteriores. Si deseas activar las listas vacías para las propiedades estáticas de DB, usa el argumento write_empty_list en true en tu clase Property; la lista vacía se escribe en Datastore.