Referencia de propiedades de entidades

Firestore en el modo de Datastore (Datastore) admite varios tipos de datos para los valores de las propiedades. Entre ellos, se incluyen los siguientes:

  • Números enteros
  • Números de punto flotante
  • Cadenas
  • Fechas
  • Datos binarios

Para ver una lista completa de los tipos, consulta Propiedades y tipos de valor.

Propiedades y tipos de valores

Los valores de datos asociados a una entidad constan de una o varias propiedades. Cada propiedad tiene un nombre y uno o varios valores. Una propiedad puede tener valores de más de un tipo, y dos entidades pueden tener valores de tipos diferentes para la misma propiedad. Las propiedades pueden estar indexadas o no (las consultas que ordenan o filtran por 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 siguientes tipos de valores:

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

  1. valores nulos
  2. Números de coma fija
    • Números enteros
    • Fechas y horas
  3. valores booleanos
  4. Secuencias de bytes
    • Cadena Unicode
    • claves del almacén de blob
  5. Números de punto flotante
  6. Claves de Datastore

Como las cadenas de texto largas y las cadenas de bytes largas no se indexan, no tienen ningún orden definido.

.

Tipos de propiedad

NDB admite los siguientes tipos de propiedades:

Tipo de propiedad Descripción
IntegerProperty Número entero con signo de 64 bits
FloatProperty Número de punto flotante de doble precisión.
BooleanProperty Booleano
StringProperty Cadena Unicode de hasta 1500 bytes indexada.
TextProperty Cadena Unicode; longitud ilimitada, no indexada
BlobProperty Cadena de bytes sin interpretar:
si defines indexed=True, hasta 1500 bytes, indexada;
si indexed es False (el valor predeterminado), longitud ilimitada, no indexada.
Argumento de palabra clave opcional: compressed.
DateTimeProperty Fecha y hora (consulta Propiedades de fecha y hora)
DateProperty Fecha (consulta Propiedades de fecha y hora).
TimeProperty Hora (consulte Propiedades de fecha y hora)
GeoPtProperty Ubicación geográfica. Es un objeto ndb.GeoPt. El objeto tiene los atributos lat y lon, ambos de tipo float. Puedes crear una con dos números de coma flotante, como ndb.GeoPt(52.37, 4.88) o con una cadena ndb.GeoPt("52.37, 4.88"). (En realidad, es la misma clase que db.GeoPt).
KeyProperty Clave de Datastore
Argumento de palabra clave opcional: kind=kind, para requerir que las claves asignadas a esta propiedad siempre tengan el tipo indicado. Puede ser una cadena o una subclase de Model.
BlobKeyProperty Clave de Blobstore
Corresponde a BlobReferenceProperty en la antigua API de la base de datos, pero el valor de la propiedad es un BlobKey en lugar de un BlobInfo. Puedes crear un BlobInfo a partir de él con BlobInfo(blobkey).
UserProperty Objeto de usuario.
StructuredProperty Incluye un tipo de modelo dentro de otro, por valor (consulta Propiedades estructuradas).
LocalStructuredProperty Como StructuredProperty, pero la representación en disco es un blob opaco y no se indexa (consulta Propiedades estructuradas).
Argumento de palabra clave opcional: compressed.
JsonProperty Value es un objeto de Python (como una lista, un diccionario o una cadena) que se puede serializar mediante el módulo json de Python. Datastore almacena la serialización JSON como un blob. No se indexan de forma predeterminada.
Argumento de palabra clave opcional: compressed.
PickleProperty El valor es un objeto de Python (como una lista, un diccionario o una cadena) que se puede serializar mediante el protocolo pickle de Python. Datastore almacena la serialización pickle como un blob. No se indexan de forma predeterminada.
Argumento de palabra clave opcional: compressed.
GenericProperty Valor genérico
Se usa principalmente en la clase Expando, pero también se puede usar explícitamente. Su tipo puede ser cualquiera de los siguientes: int, long, float, bool, str, unicode, datetime, Key, BlobKey, GeoPt, User o None.
ComputedProperty Valor calculado a partir de otras propiedades mediante una función definida por el usuario. Consulta Propiedades calculadas.

Algunas de estas propiedades tienen un argumento de palabra clave opcional, compressed. Si la propiedad tiene el valor compressed=True, sus datos se comprimen con gzip en el disco. Ocupa menos espacio, pero necesita CPU para codificar o decodificar en operaciones de escritura y lectura.

Tanto la compresión como la descompresión son "lazy": un valor de propiedad comprimido solo se descomprimirá la primera vez que accedas a él. Si lees una entidad que contiene un valor de propiedad comprimido y la vuelves a escribir sin acceder a la propiedad comprimida, no se descomprimirá ni se comprimirá. La caché en contexto también participa en este esquema de carga diferida, pero memcache siempre almacena el valor comprimido de las propiedades comprimidas.

Debido al tiempo de CPU adicional que se necesita para la compresión, normalmente es mejor usar propiedades comprimidas solo si los datos son demasiado grandes para caber sin ellas. Recuerda que la compresión basada en gzip no suele ser eficaz para imágenes y otros datos multimedia, ya que esos formatos ya están comprimidos mediante un algoritmo de compresión específico para contenido multimedia (por ejemplo, JPEG para imágenes).

Opciones de propiedad

La mayoría de los tipos de propiedad admiten algunos argumentos estándar. El primero es un argumento posicional opcional que especifica el nombre de Datastore de la propiedad. Puede usarlo para asignar a la propiedad un nombre diferente en Datastore que desde el punto de vista de la aplicación. Una de las aplicaciones más habituales es reducir el espacio en Datastore, lo que permite que Datastore use nombres de propiedades abreviados mientras que tu código usa nombres más largos y significativos. Por ejemplo,

class Employee(ndb.Model):
    full_name = ndb.StringProperty('n')
    retirement_age = ndb.IntegerProperty('r')

Esto resulta especialmente útil para las propiedades repetidas de las que esperas muchos valores por entidad.

Además, la mayoría de los tipos de propiedades admiten los siguientes argumentos de palabras clave:

Argumento Tipo Predeterminado Descripción
indexed bool Normalmente True Incluye la propiedad en los índices de Datastore. Si es False, no se pueden consultar los valores, pero las escrituras son más rápidas. No todos los tipos de propiedades admiten la indexación, por lo que, en esos casos, no se puede asignar el valor indexed a True.
Las propiedades sin indexar cuestan menos operaciones de escritura que las propiedades indexadas.
repeated bool False El valor de la propiedad es una lista de Python que contiene valores del tipo subyacente (consulte Propiedades repetidas).
No se puede combinar con required=True ni default=True.
required bool False Se debe especificar un valor para la propiedad.
default Tipo subyacente de la propiedad Ninguno Valor predeterminado de la propiedad si no se especifica ninguno explícitamente.
choices Lista de valores del tipo subyacente None Lista opcional de valores permitidos.
validator Función None

Función opcional para validar y, posiblemente, convertir el valor.

Se llamará con los argumentos (prop, value) y debe devolver el valor (posiblemente coaccionado) o generar una excepción. Si se vuelve a llamar a la función en un valor coaccionado, no se debe modificar más el valor. Por ejemplo, puedes devolver value.strip() o value.lower(), pero no value + '$'. También puedes devolver None, que significa "sin cambios". Consulta también Escribir subclases de propiedades.

verbose_name cadena None

Etiqueta HTML opcional que se usa en frameworks de formularios web, como jinja2.

Propiedades repetidas

Cualquier propiedad con repeated=True se convierte en una propiedad repetida. La propiedad toma una lista de valores del tipo subyacente en lugar de un solo valor. Por ejemplo, el valor de una propiedad definida con IntegerProperty(repeated=True) es una lista de números enteros.

Datastore puede ver varios valores para esa propiedad. Se crea un registro de índice independiente para cada valor. Esto afecta a la semántica de las consultas. Consulta un ejemplo en Consultar propiedades repetidas .

En este ejemplo se usa una propiedad repetida:

class Article(ndb.Model):
    title = ndb.StringProperty()
    stars = ndb.IntegerProperty()
    tags = ndb.StringProperty(repeated=True)
...
article = Article(
    title='Python versus Ruby',
    stars=3,
    tags=['python', 'ruby'])
article.put()

De esta forma, se crea una entidad de Datastore con el siguiente contenido:

assert article.title == 'Python versus Ruby'
assert article.stars == 3
assert sorted(article.tags) == sorted(['python', 'ruby'])

Cuando se consulta la propiedad tags, esta entidad satisfará una consulta de 'python' o 'ruby'.

Cuando actualices una propiedad repetida, puedes asignarle una lista nueva o modificar la lista actual. Cuando asignas una nueva lista, los tipos de los elementos de la lista se validan inmediatamente. Los tipos de elementos no válidos (por ejemplo, asignar [1, 2] a art.tags anteriormente) generan una excepción. Cuando modificas la lista, el cambio no se valida inmediatamente. En su lugar, el valor se validará cuando escribas la entidad en Datastore.

Datastore conserva el orden de los elementos de la lista en una propiedad repetida, por lo que puedes asignar un significado a su orden.

Propiedades de fecha y hora

Hay tres tipos de propiedad disponibles para almacenar valores relacionados con fechas y horas:

  • DateProperty
  • TimeProperty
  • DateTimeProperty

Estos toman valores que pertenecen a las clases correspondientes (date, time y datetime) del módulo datetime estándar de Python. El más general de los tres es DateTimeProperty, que denota tanto una fecha del calendario como una hora del día. Los otros dos son útiles en ocasiones para fines especiales que requieren solo una fecha (como una fecha de nacimiento) o solo una hora (como la hora de una reunión). Por motivos técnicos, DateProperty y TimeProperty son subclases de DateTimeProperty, pero no deberías depender de esta relación de herencia (y ten en cuenta que es diferente de las relaciones de herencia entre las clases subyacentes definidas por el propio módulo datetime).

Nota: Las horas del reloj de App Engine siempre se expresan en tiempo universal coordinado (UTC). Esto es importante si usas la fecha o la hora actuales (datetime.datetime.now()) como valor o si conviertes objetos datetime en marcas de tiempo POSIX o tuplas de tiempo, o viceversa. Sin embargo, en Datastore no se almacena información explícita sobre la zona horaria, por lo que, si tienes cuidado, puedes usar estos campos para representar horas locales en cualquier zona horaria, siempre que utilices la hora actual o las conversiones.

Cada una de estas propiedades tiene dos opciones de palabras clave booleanas adicionales:

Opción Descripción
auto_now_add Define la propiedad con la fecha y la hora actuales cuando se crea la entidad. Puede anular esta propiedad manualmente. Cuando se actualiza la entidad, la propiedad no cambia. Para ello, usa auto_now.
auto_now Define la propiedad con la fecha y la hora actuales cuando se crea la entidad y cada vez que se actualiza.

Estas opciones no se pueden combinar con repeated=True. Ambos tienen el valor predeterminado False. Si ambos tienen el valor True, auto_now tiene prioridad. Es posible anular el valor de una propiedad con auto_now_add=True, pero no con auto_now=True. El valor automático no se genera hasta que se escribe la entidad, es decir, estas opciones no proporcionan valores predeterminados dinámicos. Estos detalles son diferentes de los de la antigua API db.

Nota: Cuando falla una transacción que escribe una propiedad con auto_now_add=True y se vuelve a intentar más tarde, se reutilizará el mismo valor de tiempo que en el intento original en lugar de actualizarlo a la hora del reintento. Si la transacción falla de forma permanente, el valor de la propiedad se seguirá definiendo en la copia en memoria de la entidad.

Propiedades estructuradas

Puedes estructurar las propiedades de un modelo. Por ejemplo, puedes definir una clase de modelo Contact que contenga una lista de direcciones, cada una con una estructura interna. Las propiedades estructuradas (tipo StructuredProperty) te permiten hacerlo. Por ejemplo:

class Address(ndb.Model):
    type = ndb.StringProperty()  # E.g., 'home', 'work'
    street = ndb.StringProperty()
    city = ndb.StringProperty()
...
class Contact(ndb.Model):
    name = ndb.StringProperty()
    addresses = ndb.StructuredProperty(Address, repeated=True)
...
guido = Contact(
    name='Guido',
    addresses=[
        Address(
            type='home',
            city='Amsterdam'),
        Address(
            type='work',
            street='Spear St',
            city='SF')])

guido.put()

De esta forma, se crea una sola entidad de Datastore con las siguientes propiedades:

assert guido.name == 'Guido'
addresses = guido.addresses
assert addresses[0].type == 'home'
assert addresses[1].type == 'work'
assert addresses[0].street is None
assert addresses[1].street == 'Spear St'
assert addresses[0].city == 'Amsterdam'
assert addresses[1].city == 'SF'

Al leer una entidad de este tipo, se reconstruye la entidad Contact original exactamente. Aunque las instancias de Address se definen con la misma sintaxis que las clases de modelo, no son entidades completas. No tienen sus propias claves en Datastore. No se pueden recuperar de forma independiente de la entidad Contact a la que pertenecen. Sin embargo, una aplicación puede consultar los valores de sus campos individuales. Consulta Filtrar por valores de propiedades estructuradas. Ten en cuenta que address.type, address.street y address.city se consideran arrays paralelos desde el punto de vista de Datastore, pero la biblioteca NDB oculta este aspecto y crea la lista correspondiente de instancias de Address.

Puede especificar las opciones de propiedad habituales para las propiedades estructuradas (excepto indexed). El nombre de Datastore es el segundo argumento posicional en este caso (el primero es la clase de modelo que se usa para definir la subestructura).

Si no necesitas consultar las propiedades internas de una subestructura, puedes usar una propiedad estructurada local (LocalStructuredProperty). Si sustituyes StructuredProperty por LocalStructuredProperty en el ejemplo anterior, el comportamiento del código de Python será el mismo, pero Datastore solo verá un blob opaco para cada dirección. La entidad guido creada en el ejemplo se almacenaría de la siguiente manera: name = 'Guido' address = <opaque blob for {'type': 'home', 'city': 'Amsterdam'}> address = <opaque blob for {'type': 'work', 'city': 'SF', 'street': 'Spear St'}>

La entidad se leerá correctamente. Como las propiedades de este tipo siempre están sin indexar, no puedes consultar valores de dirección.

Nota: Un StructuredProperty con una propiedad anidada (estructurada o no) solo admite una capa de propiedades repetidas. El StructuredProperty se puede repetir o la propiedad anidada se puede repetir, pero no ambas. Una solución alternativa es usar LocalStructuredProperty, que no tiene esta restricción (pero no permite consultas sobre los valores de sus propiedades).

Propiedades calculadas

Las propiedades calculadas (ComputedProperty) son propiedades de solo lectura cuyo valor se calcula a partir de otros valores de propiedad mediante una función proporcionada por la aplicación. Ten en cuenta que una propiedad calculada solo admite los tipos que admiten las propiedades genéricas. El valor calculado se escribe en Datastore para que se pueda consultar y mostrar en el visor de Datastore, pero el valor almacenado se ignora cuando la entidad se vuelve a leer de Datastore. En su lugar, el valor se vuelve a calcular llamando a la función cada vez que se solicita. Por ejemplo:

class SomeEntity(ndb.Model):
    name = ndb.StringProperty()
    name_lower = ndb.ComputedProperty(lambda self: self.name.lower())
...
entity = SomeEntity(name='Nick')
entity.put()

Almacena una entidad con los siguientes valores de propiedad:

assert entity.name == 'Nick'
assert entity.name_lower == 'nick'

Si cambiamos el nombre a "Nickie" y pedimos el valor de name_lower, devuelve "nickie":

entity.name = 'Nick'
assert entity.name_lower == 'nick'
entity.name = 'Nickie'
assert entity.name_lower == 'nickie'

Nota: Usa ComputedProperty si la aplicación consulta el valor calculado. Si solo quieres usar la versión derivada en código Python, define un método normal o usa la función integrada @property de Python.

Nota: Si creas un modelo sin especificar manualmente una clave y, en su lugar, confías en Datastore para generar automáticamente el ID de la entidad, en tu primer put(), un ComputedProperty no podrá leer el campo de ID, ya que se calcula antes de que se genere el ID. Si necesitas un ComputedProperty que use el ID de la entidad, puedes usar el método allocate_ids para generar un ID y una clave con los que crear la entidad, de forma que tu ComputedProperty pueda hacer referencia a ese ID en el primer put() de la entidad.

Propiedades de los mensajes de la llamada a procedimiento remoto (RPC) del protocolo de Google

La biblioteca Google Protocol RPC usa objetos Message para los datos estructurados, que pueden representar solicitudes o respuestas de RPC, entre otros elementos. NDB proporciona una API para almacenar objetos Message de Google Protocol RPC como propiedades de entidad. Supongamos que defines una subclase Message:

from protorpc import messages
...
class Note(messages.Message):
    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

Puedes almacenar objetos Note en Datastore como valores de propiedad de entidad mediante la API msgprop de NDB.

from google.appengine.ext import ndb
from google.appengine.ext.ndb import msgprop
...
class NoteStore(ndb.Model):
    note = msgprop.MessageProperty(Note, indexed_fields=['when'])
    name = ndb.StringProperty()
...
my_note = Note(text='Excellent note', when=50)

ns = NoteStore(note=my_note, name='excellent')
key = ns.put()

new_notes = NoteStore.query(NoteStore.note.when >= 10).fetch()

Si quieres consultar nombres de campos, estos deben estar indexados. Puede especificar una lista de nombres de campos que se indexarán con el parámetro indexed_fields en MessageProperty.

MessageProperty admite muchas opciones de propiedad, pero no todas. Admite lo siguiente:

  • name
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

Las propiedades de los mensajes no admiten la opción de propiedad indexed, por lo que no puede indexar valores Message. (Puedes indexar los campos de un mensaje tal como se describe más arriba).

También funcionan los mensajes anidados (con MessageField):

class Notebook(messages.Message):
    notes = messages.MessageField(Note, 1, repeated=True)
...
class SignedStorableNotebook(ndb.Model):
    author = ndb.StringProperty()
    nb = msgprop.MessageProperty(
        Notebook, indexed_fields=['notes.text', 'notes.when'])

MessageProperty tiene una opción de propiedad especial, protocol, que especifica cómo se serializa el objeto de mensaje en Datastore. Los valores son nombres de protocolo tal como los usa la clase protorpc.remote.Protocols. Los nombres de protocolo admitidos son protobuf y protojson. El valor predeterminado es protobuf.

msgprop también define EnumProperty, un tipo de propiedad que se puede usar para almacenar un valor protorpc.messages.Enum en una entidad. Ejemplo:

class Color(messages.Enum):
    RED = 620
    GREEN = 495
    BLUE = 450
...
class Part(ndb.Model):
    name = ndb.StringProperty()
    color = msgprop.EnumProperty(Color, required=True)
...
p1 = Part(name='foo', color=Color.RED)
print p1.color  # prints "RED"

EnumProperty almacena el valor como un número entero. De hecho, EnumProperty es una subclase de IntegerProperty. Esto significa que puedes cambiar el nombre de los valores de enumeración sin tener que modificar las entidades ya almacenadas, pero no puedes cambiar su número.

EnumProperty admite las siguientes opciones de propiedad:

  • name
  • indexed
  • repeated
  • required
  • default
  • choices
  • validator
  • verbose_name

Acerca de los modelos de entidad de NDB

Un modelo de entidad de NDB puede definir propiedades. Las propiedades de entidad son como los miembros de datos de las clases de Python, una forma estructurada de almacenar datos. También son como los campos de un esquema de base de datos.

Una aplicación típica define un modelo de datos definiendo una clase que hereda de Model con algunos atributos de clase de propiedad. Por ejemplo,


from google.appengine.ext import ndb
...
class Account(ndb.Model):
    username = ndb.StringProperty()
    userid = ndb.IntegerProperty()
    email = ndb.StringProperty()

En este caso, username, userid y email son propiedades de Account.

Hay otros tipos de propiedades. Algunos son útiles para representar fechas y horas, y tienen funciones de actualización automática muy prácticas.

Una aplicación puede ajustar el comportamiento de una propiedad especificando opciones en la propiedad. Estas opciones pueden facilitar la validación, definir valores predeterminados o cambiar la indexación de consultas.

Un modelo puede tener propiedades más complejas. Las propiedades repetidas son como listas. Las propiedades estructuradas son similares a los objetos. Las propiedades calculadas de solo lectura se definen mediante funciones, lo que facilita la definición de una propiedad en términos de una o varias propiedades. Los modelos Expando pueden definir propiedades de forma dinámica.