Mis favoritos | Español | Acceder

Entidades y modelos

Las entidades de almacén de datos incluyen una clave y un conjunto de propiedades. Las aplicaciones utilizan el API de almacén de datos para definir modelos de datos y crear instancias de dichos modelos que se almacenarán como entidades. Los modelos proporcionan una estructura común a las entidades creadas por el API y pueden definir reglas para validar valores de propiedad.

La interfaz de modelo

Las aplicaciones describen los tipos de datos que utilizan con modelos. Un modelo es una clase de Python que hereda propiedades de la clase Model. La clase de modelo define un nuevo tipo de entidad de almacén de datos y las propiedades que se espera que adopte este tipo.

Las propiedades de Model se definen con atributos de clase en la clase de modelo. Cada atributo de clase es una instancia de una subclase de la clase Property, por lo general una de las clases de propiedad proporcionadas. La instancia de propiedad contiene la configuración de la propiedad como, por ejemplo, si la propiedad es necesaria para que la instancia sea válida, o un valor predeterminado que se utilizará para la instancia si no se proporciona ninguno.

from google.appengine.ext import db

class Pet(db.Model):
  name = db.StringProperty(required=True)
  type = db.StringProperty(required=True, choices=set(["cat", "dog", "bird"]))
  birthdate = db.DateProperty()
  weight_in_pounds = db.IntegerProperty()
  spayed_or_neutered = db.BooleanProperty()
  owner = db.UserProperty(required=True)

Una entidad de uno de los tipos de entidad definidos se representa en el API con una instancia de la clase de modelo correspondiente. Las aplicaciones pueden crear una nueva entidad mediante una llamada al constructor de la clase. Éstas acceden y manipulan las propiedades de la entidad mediante los atributos de la instancia. El constructor de instancias de modelos acepta los valores iniciales de las propiedades como argumentos de palabra clave.

from google.appengine.api import users

pet = Pet(name="Fluffy",
          type="cat",
          owner=users.get_current_user())
pet.weight_in_pounds = 24

Nota: los atributos de la clase de modelo representan la configuración de las propiedades del modelo, cuyos valores son instancias de Property. Los atributos de la instancia de modelo son los valores de propiedad reales, cuyos valores son del tipo aceptado por la clase Property.

La clase Model utiliza las instancias de Property para validar valores asignados a los atributos de instancia de modelo. La validación del valor de propiedad se produce cuando una instancia de modelo se construye por primera vez y cuando a un atributo de instancia se le asigna un valor nuevo. De esta forma se garantiza que las propiedades nunca puedan contener un valor no válido.

Dado que la validación se produce cuando se construye una instancia, todas las propiedades que se configuren como necesarias se deberán inicializar en el constructor. En este ejemplo, name, type y owner son valores obligatorios, por lo que sus valores iniciales se especifican en el constructor. El modelo no requiere la propiedad weight_in_pounds, por lo que comienza sin asignación y, posteriormente, se le asigna un valor.

Una instancia de un modelo creada con el constructor no existirá en el almacén de datos hasta que se "coloque" por primera vez. Consulta Creación, obtención y eliminación de datos.

Nota: al igual que sucede con todos los atributos de clase de Python, la configuración de propiedades de modelo se inicializa cuando la secuencia de comandos o el módulo se importa por primera vez. Dado que App Engine almacena módulos importados entre solicitudes en caché, la configuración del módulo se puede inicializar durante una solicitud de un usuario y reutilizarse durante una solicitud de otro. No inicialices la configuración de propiedad de modelo como, por ejemplo, los valores predeterminados, con datos específicos de la solicitud o el usuario actual. Para obtener más información, consulta Almacenamiento en caché de aplicaciones.

Modelos Expando

Un modelo definido mediante la clase Model establece un conjunto fijo de propiedades que deben incluir todas las instancias de la clase (quizá con los valores predeterminados). Resulta un método práctico para crear modelos de objetos de datos, pero el almacén de datos no requiere que todas las entidades de un tipo determinado contengan el mismo conjunto de propiedades.

En algunos casos, resulta útil que una entidad tenga propiedades que no sean necesariamente idénticas a las propiedades de otras entidades del mismo tipo. Dicha entidad se representa en el API de almacén de datos con un modelo "Expando". Una clase de modelo Expando adopta la condición de subclase de la superclase Expando. Cualquier valor asignado a un atributo de una instancia de un modelo Expando se convierte en una propiedad de la entidad de almacén de datos, utilizando el nombre del atributo. Estas propiedades se conocen como propiedades dinámicas. Las propiedades definidas mediante instancias de clase Property en atributos de clase son propiedades fijas.

Un modelo Expando puede presentar propiedades fijas y dinámicas simultáneamente. La clase de modelo simplemente establece atributos de clase con objetos de configuración de Property para las propiedades fijas. La aplicación crea propiedades dinámicas cuando les asigna valores.

class Person(db.Expando):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  hobbies = db.StringListProperty()

p = Person(first_name="Albert", last_name="Johnson")
p.hobbies = ["chess", "travel"]

p.chess_elo_rating = 1350

p.travel_countries_visited = ["Spain", "Italy", "USA", "Brazil"]
p.travel_trip_count = 13

Dado que las propiedades dinámicas no disponen de definiciones de propiedades de modelo, las propiedades dinámicas no se validan. Todas las propiedades dinámicas pueden contener un valor de cualquiera de los tipos básicos de almacén de datos, incluido None. Dos entidades del mismo tipo pueden contener distintos tipos de valores para la misma propiedad dinámica, y una puede dejar sin establecer una propiedad que establezca la otra.

A diferencia de las propiedades fijas, no es necesario que las propiedades dinámicas existan. Una propiedad dinámica con un valor de None es distinta de una propiedad dinámica no existente. Si una instancia de modelo Expando no dispone de un atributo para una propiedad, la entidad de datos correspondiente no contendrá esa propiedad. Para eliminar una propiedad dinámica, elimina el atributo.

del p.chess_elo_rating

Las consultas que utilizan una propiedad dinámica en un filtro sólo devolverán entidades cuyo valor para la propiedad sea del mismo tipo que el valor utilizado en la consulta. Del mismo modo, la consulta sólo devolverá entidades con esa propiedad establecida.

p1 = Person()
p1.favorite = 42
p1.put()

p2 = Person()
p2.favorite = "blue"
p2.put()

p3 = Person()
p3.put()

people = db.GqlQuery("SELECT * FROM Person WHERE favorite < :1", 50)
# people has p1, but not p2 or p3

people = db.GqlQuery("SELECT * FROM Person WHERE favorite > :1", 50)
# people has no results

La clase Expando es una subclase de la clase Model y hereda todos sus métodos.

Modelos Polymorphic

El API Python incluye otra clase de modelado de datos que te permite definir jerarquías de clases y realizar consultas que pueden devolver entidades de una clase determinada o de cualquiera de sus subclases. Estos modelos y consultas se denominan "polymorphic", porque permiten que las instancias de una clase sean los resultados de una consulta de una clase de entidad principal.

El siguiente ejemplo define una clase Contact y clases Person y Company que son subclases Contact:

from google.appengine.ext import db
from google.appengine.ext.db import polymodel

class Contact(polymodel.PolyModel):
  phone_number = db.PhoneNumberProperty()
  address = db.PostalAddressProperty()

class Person(Contact):
  first_name = db.StringProperty()
  last_name = db.StringProperty()
  mobile_number = db.PhoneNumberProperty()

class Company(Contact):
  name = db.StringProperty()
  fax_number = db.PhoneNumberProperty()

Este modelo garantiza que todas las entidades Person y Company disponen de propiedades phone_number y address y que las consultas a entidades Contact pueden devolver tanto entidades Person como Company. Únicamente las entidades Person tienen propiedades mobile_number.

Se pueden crear instancias de las subclases como si se tratara de cualquier otra clase de modelo:

p = Person(phone_number='1-206-555-9234',
           address='123 First Ave., Seattle, WA, 98101',
           first_name='Alfred',
           last_name='Smith',
           mobile_number='1-206-555-0117')
p.put()

c = Company(phone_number='1-503-555-9123',
            address='P.O. Box 98765, Salem, OR, 97301',
            name='Data Solutions, LLC',
            fax_number='1-503-555-6622')
c.put()

Una consulta para entidades Contact puede devolver instancias tanto de Contact, Person como de Company. El código que aparece a continuación imprime información para las dos entidades creadas más arriba:

for contact in Contact.all():
  print 'Phone: %s\nAddress: %s\n\n'
        % (contact.phone,
           contact.address))

Una consulta a entidades Company devuelve únicamente instancias de Company:

for company in Company.all()
  # ...

Para obtener más información sobre cómo se utilizan los modelos polymorphic y cómo se implementan, consulta La clase PolyModel.

Propiedades y tipos

El almacén de datos admite un conjunto fijo de tipos de valores para propiedades de entidad, incluidos números enteros, cadenas de Unicode, números de punto flotante, fechas, claves de entidad, cadenas de bytes (blobs) y diversos tipos de datos de Google. Cada uno de los tipos de valores de almacén de datos tiene una clase Property correspondiente proporcionada por el módulo google.appengine.ext.db.

En la sección Tipos y clases de propiedades se describen todos los tipos de valores compatibles y sus clases Property correspondientes. A continuación se describen varios tipos de valor especiales.

Cadenas y Blobs

El almacén de datos admite dos tipos de valores para almacenar texto: cadenas cortas de texto de hasta 500 bytes de longitud y cadenas largas de texto de hasta 1 megabyte de longitud. Las cadenas cortas se indexan y se pueden utilizar con fines de ordenación y filtrado de consultas. Las cadenas largas no se indexan y no se pueden utilizar con fines de ordenación y filtrado.

Un valor de cadena corta puede ser un valor unicode o un valor str. Si el valor es str, se asume una codificación 'ascii'. Para especificar una codificación distinta para un valor str, puedes convertirlo en un valor unicode con el constructor de tipos unicode(), que adopta el valor str y el nombre de la codificación como argumentos. Se puede utilizar la clase StringProperty para crear un modelo de cadena corta.

class MyModel(db.Model):
  string = db.StringProperty()

obj = MyModel()

# Python Unicode literal syntax fully describes characters in a text string.
obj.string = u"kittens"

# unicode() converts a byte string to a Unicode value using the named codec.
obj.string = unicode("kittens", "latin-1")

# A byte string is assumed to be text encoded as ASCII (the 'ascii' codec).
obj.string = "kittens"

# Short string properties can be used in query filters.
results = db.GqlQuery("SELECT * FROM MyModel WHERE string = :1", u"kittens")

Un valor de cadena larga se representa con una instancia de db.Text. Su constructor utiliza un valor unicode o un valor str y, de forma opcional, el nombre de la codificación utilizada en el valor str. Se puede utilizar la clase TextProperty para crear un modelo de cadena larga.

class MyModel(db.Model):
  text = db.TextProperty()

obj = MyModel()

# Text() can take a Unicode value.
obj.text = db.Text(u"lots of kittens")

# Text() can take a byte string and the name of an encoding.
obj.text = db.Text("lots of kittens", "latin-1")

# If no encoding is specified, a byte string is assumed to be ASCII text.
obj.text = db.Text("lots of kittens")

# Text properties can store large values.
obj.text = db.Text(open("a_tale_of_two_cities.txt").read(), "utf-8")

El almacén de datos también admite dos tipos similares para las cadenas de bytes sin texto: db.ByteString y db.Blob. Estos valores son cadenas de bytes sin procesar y no se tratan como texto codificado (como por ejemplo UTF-8).

Como en el caso de los valores str o unicode, los valores db.ByteString se indexan y tienen un límite de 500 caracteres. Una instancia de ByteString representa una cadena corta de bytes y adopta un valor str como argumento para su constructor. Se puede utilizar la clase ByteStringProperty para crear un modelo de cadena de bytes.

Como en el caso de db.Text, un valor db.Blob puede ser de hasta 1 megabyte, pero no se indexa y no se puede utilizar en los filtros de consulta o criterios de ordenación. La clase db.Blob toma un valor str como argumento para su constructor. Se puede utilizar la clase BlobProperty para crear un modelo de blob.

class MyModel(db.Model):
  blob = db.BlobProperty()

obj = MyModel()

obj.blob = db.Blob(open("image.png").read())

Listas

Una propiedad puede incluir múltiples valores, representados en el API de almacén de datos como una lista (list) de Python. La lista puede contener valores de cualquiera de los tipos de valores que admite el almacén de datos. Una propiedad de lista única puede incluso incluir valores de distintos tipos. El orden se mantiene de forma que, cuando las entidades se devuelven a través de consultas y de la función get(), las propiedades de lista presentan los valores en el mismo orden en que se almacenaron.

La clase ListProperty crea un modelo de lista e impone que todos los valores de esa lista sean de un tipo determinado. Para comodidad del usuario, la biblioteca también proporciona el tipo de propiedad StringListProperty, similar a ListProperty(basestring).

class MyModel(db.Model):
  numbers = db.ListProperty(long)

obj = MyModel()
obj.numbers = [2, 4, 6, 8, 10]

obj.numbers = ["hello"]  # ERROR: MyModel.numbers must be a list of longs.

Un filtro de consultas en una propiedad de lista prueba el valor especificado con los miembros de la lista. La condición presenta el valor "true" si al menos un miembro de la lista cumple la condición.

# Get all entities where numbers contains a 6.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers = 6")

# Get all entities where numbers contains at least one element less than 10.
results = db.GqlQuery("SELECT * FROM MyModel WHERE numbers < 10")

Los filtros de consultas sólo funcionan con los miembros de la lista. En un filtro de consultas no se puede comprobar la similitud de dos listas.

Internamente, el almacén de datos representa un valor de propiedad de lista como múltiples valores para la propiedad. Si uno de los valores de propiedad de lista es la lista vacía, la propiedad no tendrá representación en el almacén de datos. El API del almacén de datos trata esta situación de forma distinta en función de si trabaja con propiedades estáticas (con ListProperty) o dinámicas:

  • A una propiedad ListProperty estática se le puede asignar la lista vacía como valor. La propiedad no existe en el almacén de datos, pero la instancia de modelo se comporta como si el valor fuera la lista vacía. Las propiedades ListProperty estáticas no pueden tener un valor None.
  • A una propiedad dinámica con un valor list no se le puede asignar un valor de lista vacía. Sin embargo, puede contener un valor None y se puede eliminar (con del).

El modelo de ListProperty prueba que un valor añadido a la lista es del tipo correcto y genera un error de valor incorrecto (BadValueError) si no lo es. Esta comprobación se lleva a cabo (y puede generar un error) aunque se haya recuperado y cargado en el modelo una entidad almacenada previamente. Dado que los valores str se convierten en valores unicode (como texto ASCII) antes del almacenamiento, ListProperty(str) se trata como ListProperty(basestring), el tipo de dato de Python que acepta valores str y unicode. Para ello, también puedes utilizar StringListProperty().

Para almacenar cadenas de bytes sin texto, utiliza valores db.Blob. Los bytes de una cadena de blob se conservan cuando se almacenan y recuperan. Se puede declarar una propiedad que consista en una lista de blobs como ListProperty(db.Blob).

Las propiedades de lista interactúan de forma inusual con los criterios de ordenación. Puedes obtener información detallada en Consultas e índices: criterios de ordenación y propiedades con varios valores.

Referencias

Un valor de propiedad contiene la clave de otra entidad. El valor es una instancia de Key.

La clase ReferenceProperty crea un modelo de un valor clave e impone que todos los valores hagan referencia a entidades de un tipo determinado. Para comodidad del usuario, la biblioteca también proporciona el tipo de propiedad SelfReferenceProperty, equivalente a una propiedad ReferenceProperty que haga referencia al mismo tipo que la entidad con la propiedad.

La asignación de una instancia de modelo a una propiedad ReferenceProperty hace que su clave se utilice automáticamente como valor.

class FirstModel(db.Model):
  prop = db.IntegerProperty()

class SecondModel(db.Model):
  reference = db.ReferenceProperty(FirstModel)

obj1 = FirstModel()
obj1.prop = 42
obj1.put()

obj2 = SecondModel()

# A reference value is the key of another entity.
obj2.reference = obj1.key()

# Assigning a model instance to a property uses the entity's key as the value.
obj2.reference = obj1
obj2.put()

Un valor de propiedad ReferenceProperty se puede utilizar como si fuera la instancia de modelo de la entidad a la que se hace referencia. Si la entidad a la que se hace referencia no se encuentra en memoria, utiliza la propiedad como instancia para extraer automáticamente la entidad del almacén de datos.

obj2.reference.prop = 999
obj2.reference.put()

results = db.GqlQuery("SELECT * FROM SecondModel")
another_obj = results.fetch(1)[0]
v = another_obj.reference.prop

Si se elimina una entidad cuya clave es el valor de una propiedad de referencia, la propiedad de referencia no cambia. Un valor de propiedad de referencia puede ser una clave que ya no sea válida. Si una aplicación espera que una referencia pueda no ser válida, puede comprobar la existencia del objeto mediante una instrucción if:

obj1 = obj2.reference

if not obj1:
  # Referenced entity was deleted.

ReferenceProperty presenta otra función útil: las referencias a otros elementos. Cuando un modelo tiene una propiedad ReferenceProperty que hace referencia a otro modelo, cada entidad a la que se hace referencia adopta una propiedad cuyo valor es una consulta (Query) que devuelve todas las entidades del primer modelo que hace referencia a ella.

# To fetch and iterate over every SecondModel entity that refers to the
# FirstModel instance obj1:
for obj in obj1.secondmodel_set:
  # ...

El nombre de la propiedad de referencia a otro elemento adopta el valor predeterminado modelname_set (con el nombre de la clase de modelo en minúsculas y "_set" añadido al final) y se puede ajustar mediante el argumento collection_name para el constructor de ReferenceProperty.

Si dispones de varios valores de ReferenceProperty que hacen referencia a la misma clase de modelo, la construcción predeterminada de la propiedad de referencia a otro elemento generará un error:

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class raises a DuplicatePropertyError with the message
# "Class Firstmodel already has property secondmodel_set"
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel)
  reference_two = db.ReferenceProperty(FirstModel)

Para evitar este error, debes establecer explícitamente el argumento collection_name:

class FirstModel(db.Model):
  prop = db.IntegerProperty()

# This class runs fine
class SecondModel(db.Model):
  reference_one = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_one_set")
  reference_two = db.ReferenceProperty(FirstModel,
      collection_name="secondmodel_reference_two_set") 

La referencia y la anulación de la referencia automáticas de las instancias de modelo, la comprobación del tipo y las referencias a otros elementos sólo están disponibles a través de la clase de propiedad de modelo ReferenceProperty. Las claves almacenadas como valores de propiedades dinámicas Expando o valores ListProperty no disponen de estas funciones.

Nombres de propiedades

El almacén de datos reserva todos los nombres de propiedades que comienzan y terminan con dos caracteres de subrayado (__*__). Una aplicación no puede crear una propiedad con dicho nombre.

En el API de Python, los atributos de instancias de modelo cuyos nombres comienzan por un carácter de subrayado (_) se ignoran y no se guardan en la entidad de almacén de datos. De esta forma, puedes almacenar valores de la instancia de modelo para un uso interno temporal sin que ello afecte a los datos guardados en la entidad.

Dado que el API de Python utiliza atributos de instancias de modelo como nombres de propiedades de forma predeterminada, ninguno de los atributos que ya están siendo utilizados por métodos de instancia se pueden utilizar como nombres de atributos de propiedad. Del mismo modo, ninguno de los nombres utilizados por los argumentos de palabra clave de los constructores del modelo se pueden utilizar como nombres de atributos de propiedad. Consulta la lista de nombres de propiedades reservadas.

El propio almacén de datos permite estos nombres. Si una aplicación necesita una entidad de almacén de datos para tener una propiedad con un nombre similar a una palabra reservada en el API de Python, la aplicación puede utilizar una propiedad fija y transmitir el argumento name al constructor de la clase Property. Consulta la sección del constructor de la clase Property.

class MyModel(db.Model):
  obj_key = db.StringProperty(name="key")