Descripción general de la biblioteca cliente de NDB de App Engine para Python 2

La biblioteca cliente de NDB en Google Datastore permite que las apps de Python en App Engine se conecten a Datastore. La biblioteca cliente de NDB se basa en la biblioteca de DB de Datastore anterior y agrega las siguientes funciones para almacenar datos:

  • La clase StructuredProperty, que permite que las entidades tengan estructuras anidadas.
  • Almacenamiento en caché automático integrado, que generalmente brinda lecturas rápidas y de bajo costo mediante un almacenamiento en caché en contexto y Memcache.
  • Admite APIs asíncronas para acciones simultáneas y API síncronas.

En esta página, se proveen instrucciones y una descripción general sobre la biblioteca cliente de App Engine NDB. Para obtener información sobre cómo migrar a Cloud NDB, que admite Python 3, consulta Migra a Cloud NDB.

Definición de entidades, claves y propiedades

Datastore almacena objetos de datos, llamados entidades. Una entidad tiene una o más propiedades, denominadas valores de uno de los varios tipos de datos compatibles. Por ejemplo, una propiedad puede ser una cadena, un número entero o una referencia a otra entidad.

Cada entidad está definida por una clave, un identificador único dentro del almacén de datos de la aplicación. La clave puede tener una principal, que es otra clave. Esta clave principal puede tener también una clave principal, y así sucesivamente. En la parte superior de esta “cadena” de principales, hay una clave denominada raíz, que no tiene una principal.

Muestra la relación entre la entidad raíz y las entidades secundarias en un grupo de entidades.

Las entidades cuyas claves tienen la misma raíz forman un grupo de entidad o grupo. Si las entidades se encuentran en grupos diferentes, puede parecer que los cambios en esas entidades están "desordenados". No hay problemas si las entidades no están relacionadas con la semántica de la aplicación. Pero si algunos cambios de las entidades deben coincidir, la aplicación debe incorporarlos al mismo grupo al crearlos.

En el siguiente diagrama de relación de entidades y ejemplo de código, se muestra cómo un Guestbook puede tener varios Greetings, los cuales tienen propiedades content y date.

Muestra las relaciones de entidades creadas con la muestra de código incluida.

Esta relación se implementa en la siguiente muestra de código.

import cgi
import textwrap
import urllib

from google.appengine.ext import ndb

import webapp2


class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

        greeting_blockquotes = []
        for greeting in greetings:
            greeting_blockquotes.append(
                '<blockquote>%s</blockquote>' % cgi.escape(greeting.content))

        self.response.out.write(textwrap.dedent("""\
            <html>
              <body>
                {blockquotes}
                <form action="/sign?{sign}" method="post">
                  <div>
                    <textarea name="content" rows="3" cols="60">
                    </textarea>
                  </div>
                  <div>
                    <input type="submit" value="Sign Guestbook">
                  </div>
                </form>
                <hr>
                <form>
                  Guestbook name:
                    <input value="{guestbook_name}" name="guestbook_name">
                    <input type="submit" value="switch">
                </form>
              </body>
            </html>""").format(
                blockquotes='\n'.join(greeting_blockquotes),
                sign=urllib.urlencode({'guestbook_name': guestbook_name}),
                guestbook_name=cgi.escape(guestbook_name)))


class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()
        self.redirect('/?' + urllib.urlencode(
            {'guestbook_name': guestbook_name}))


app = webapp2.WSGIApplication([
    ('/', MainPage),
    ('/sign', SubmitForm)
])

Usar modelos para almacenar datos

Un modelo es una clase que describe un tipo de entidad, incluidos los tipos y la configuración de sus propiedades. Es similar a una tabla en SQL. Para crear una entidad, se llama al constructor de clase del modelo y, luego, se almacena cuando se llama al método put().

Este código de ejemplo define la clase del modelo Greeting. Cada entidad Greeting cuenta con dos propiedades: el contenido de texto del saludo y la fecha en que se creó el saludo.

class Greeting(ndb.Model):
    """Models an individual Guestbook entry with content and date."""
    content = ndb.StringProperty()
    date = ndb.DateTimeProperty(auto_now_add=True)
class SubmitForm(webapp2.RequestHandler):
    def post(self):
        # We set the parent key on each 'Greeting' to ensure each guestbook's
        # greetings are in the same entity group.
        guestbook_name = self.request.get('guestbook_name')
        greeting = Greeting(parent=ndb.Key("Book",
                                           guestbook_name or "*notitle*"),
                            content=self.request.get('content'))
        greeting.put()

Para crear y almacenar un saludo nuevo, la aplicación crea un objeto Greeting y llama a su método put().

Para asegurarse de que los saludos en un libro de visitas no aparezcan de forma desordenada, la aplicación establece una clave principal cuando crea un nuevo Greeting. De esta forma, el saludo nuevo estará en el mismo grupo de entidad que otros saludos del mismo libro de visitas. La aplicación usa este dato cuando realiza una consulta: utiliza una consulta principal.

Índices y consultas

Una aplicación puede realizar consultas para buscar entidades que coincidan con algunos filtros.

    @classmethod
    def query_book(cls, ancestor_key):
        return cls.query(ancestor=ancestor_key).order(-cls.date)


class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.out.write('<html><body>')
        guestbook_name = self.request.get('guestbook_name')
        ancestor_key = ndb.Key("Book", guestbook_name or "*notitle*")
        greetings = Greeting.query_book(ancestor_key).fetch(20)

Una consulta de NDB típica filtra entidades por tipo similar. En este ejemplo, query_book genera una consulta que muestra entidades de Greeting. Una consulta también puede especificar filtros en los valores de propiedad de la entidad y las claves. Como se muestra en este ejemplo, una consulta puede especificar una principal, para buscar solo las entidades que "pertenezcan" a la entidad principal. Una consulta puede especificar un orden de clasificación. Si una entidad determinada tiene, al menos, un valor (posiblemente nulo) por cada propiedad en los filtros y órdenes de clasificación, y los valores de propiedad cumplen con todos los criterios del filtro, se mostrará como resultado esa entidad.

Cada consulta utiliza un índice, que es una tabla que contiene los resultados de la consulta en el orden deseado. El almacenamiento de Datastore subyacente mantiene de forma automática índices simples (que solo utilizan una propiedad).

Define sus índices complejos en un archivo de configuración, index.yaml. El servidor de desarrollo web agrega sugerencias automáticamente a este archivo cuando encuentra consultas que todavía no tienen índices configurados.

Para ajustar los índices de forma manual, edita el archivo antes de subir la aplicación. Puedes actualizar los índices por separado cuando subes la aplicación si ejecutas gcloud app deploy index.yaml. Si el almacén de datos tiene demasiadas entidades, llevará mucho tiempo crear un índice nuevo para estas. En ese caso, lo más acertado es actualizar las definiciones del índice antes de subir el código que utiliza el índice nuevo. Puedes usar la Consola del administrador para saber cuándo se terminaron de compilar los índices.

El mecanismo de índices admite una amplia variedad de consultas y es apropiado para la mayoría de las aplicaciones. Sin embargo, no admite algunos tipos de consultas que son comunes en otras tecnologías de base de datos. En particular, no admite las consultas de unión.

Comprende las escrituras de NDB: confirmación, invalidación de caché y aplicación

NDB escribe datos en distintos pasos:

  • En la fase de confirmación, el servicio subyacente de Datastore registra los cambios.
  • NDB invalida el almacenamiento en caché de las entidades afectadas. Por lo tanto, las lecturas futuras leerán desde Datastore (y almacenarán en caché), en vez de leer los valores obsoletos del almacenamiento en caché.
  • Finalmente, quizá unos segundos después, Datastore aplica el cambio. Hace que el cambio sea visible para las consultas globales y las lecturas de coherencia eventual.

La función de NDB que escribe los datos [por ejemplo, put()] se muestra después de la invalidación de caché. La fase de aplicación es asíncrona.

Si ocurre un error durante la fase de confirmación, se hacen reintentos automáticos, pero si los errores continúan, la aplicación recibe una excepción. Si la fase de confirmación es correcta, pero la de aplicación falla, la aplicación avanza hasta su finalización cuando ocurre alguna de las siguientes situaciones:

  • Los barridos periódicos de Datastore verifican si hay trabajos de confirmación incompletos y los aplican.
  • La siguiente escritura, transacción o lectura de coherencia sólida en el grupo de entidades afectado hace que los cambios que todavía no se aplicaron se apliquen antes de la lectura, escritura o transacción.

Este comportamiento afecta la manera y el momento en que los datos son visibles en la aplicación. El cambio podrá tardar algunos cientos de milisegundos para aplicarse por completo, luego de que lo muestre la función de NDB. Una consulta no principal que se realiza mientras se aplica un cambio puede ver un estado no coherente, es decir, una parte, pero no todo el cambio.

Transacciones y datos almacenados en caché

La biblioteca cliente de NDB puede agrupar varias operaciones en una sola transacción. La transacción no se hará correctamente, a menos que cada operación de la transacción se complete con éxito. Si alguna de las operaciones falla, la transacción se revertirá de forma automática. Esto es muy útil para aplicaciones web distribuidas, en las que varios usuarios pueden tener acceso a los mismos datos o manipularlos al mismo tiempo.

NDB utiliza Memcache como un servicio de almacenamiento en caché para “puntos críticos” en los datos. Si la aplicación lee algunas entidades con frecuencia, NDB puede leerlas rápidamente desde el almacenamiento de caché.

Usa Django con NDB

Para usar NDB con el marco de trabajo web de Django, agrega google.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware a la entrada MIDDLEWARE_CLASSES en tu archivo settings.py de Django. Es mejor insertarlo delante de cualquier otra clase de middleware, ya que algún middleware podría llamar al almacén de datos y las llamadas no se manejarán correctamente si se invoca ese middleware antes que este. Puedes obtener más información acerca del middleware de Django.

¿Qué sigue?

Más información acerca de los siguientes temas: