Creating Entity Models

You need to create a model class for your entity. You can do this in two ways:

  • Create a model class that defines entity properties.
  • Create an expando model class that does not define entities in advance.

This document describes how to create each of these types of model classes. It also describes how to create a model hook, so that your application can run some code before or after some type of operations, for example, before each get().

Creating a model class with properties

Before creating an entity, you must create a model class that defines one or more entity properties. For example:

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

where you want each Account entity to have properties for user name, user ID, and email.

For a full list of property types, see the Entity Property Reference.

Creating an Expando model class

You don't have to use a model class that defines properties in advance. A special model subclass called Expando changes the behavior of its entities so that any attribute assigned is saved to the data store. Note that such attributes can't start with an underscore.

Here is how to create an Expando model:

class Mine(ndb.Expando):
    pass
...
e = Mine()
e.foo = 1
e.bar = 'blah'
e.tags = ['exp', 'and', 'oh']
e.put()

This writes an entity to the data store with a foo property with integer value 1, a bar property with string value 'blah', and a repeated tag property with string values 'exp', 'and', and 'oh'. The properties are indexed, and you can inspect them using the entity's _properties attribute:

return e._properties
# {
#     'foo': GenericProperty('foo'),
#     'bar': GenericProperty('bar'),
#     'tags': GenericProperty('tags', repeated=True)
# }

An Expando created by getting a value from the data store has properties for all property values that were saved in the data store.

An application can add predefined properties to an Expando subclass:

class FlexEmployee(ndb.Expando):
    name = ndb.StringProperty()
    age = ndb.IntegerProperty()
...
employee = FlexEmployee(name='Sandy', location='SF')

This gives employee a name attribute with value 'Sandy', an age attribute with value None, and a dynamic attribute location with value 'SF'.

To create an Expando subclass whose properties are unindexed, set _default_indexed = False in the subclass definition:

class Specialized(ndb.Expando):
    _default_indexed = False
...
e = Specialized(foo='a', bar=['b'])
return e._properties
# {
#     'foo': GenericProperty('foo', indexed=False),
#     'bar': GenericProperty('bar', indexed=False, repeated=True)
# }

You can also set _default_indexed on an Expando entity. In this case it will affect all properties assigned after it was set.

Another useful technique is querying an Expando kind for a dynamic property. A query like the following

FlexEmployee.query(FlexEmployee.location == 'SF')

won't work, because the class doesn't have a property object for the location property. Instead, use GenericProperty, the class Expando uses for dynamic properties:

FlexEmployee.query(ndb.GenericProperty('location') == 'SF')

Using Model Hooks

NDB offers a lightweight hooking mechanism. By defining a hook, an application can run some code before or after some type of operations; for example, a Model might run some function before every get().

A hook function runs when using the synchronous, asynchronous and multi versions of the appropriate method. For example, a "pre-get" hook would apply to all of get(), get_async(), and get_multi(). There are pre-RPC and post-RPC versions of each hook.

Hooks can be useful for the following:

  • query caching
  • auditing Cloud Datastore activity per-user
  • mimicking database triggers

The following example shows how to define hook functions:

from google.appengine.ext import ndb
...
class Friend(ndb.Model):
    name = ndb.StringProperty()

    def _pre_put_hook(self):
        _notify('Gee wiz I have a new friend!')

    @classmethod
    def _post_delete_hook(cls, key, future):
        _notify('I have found occasion to rethink our friendship.')
...
f = Friend()
f.name = 'Carole King'
f.put()  # _pre_put_hook is called
fut = f.key.delete_async()  # _post_delete_hook not yet called
fut.get_result()  # _post_delete_hook is called

If you use post-hooks with asynchronous APIs, the hooks are triggered by calling check_result(), get_result() or yielding (inside a tasklet) an async method's future. Post hooks do not check whether the RPC was successful; the hook runs regardless of failure.

All post- hooks have a Future argument at the end of the call signature. This Future object holds the result of the action. You can call get_result() on this Future to retrieve the result; you can be sure that get_result() won't block, since the Future is complete by the time the hook is called.

Raising an exception during a pre-hook prevents the request from taking place. Although hooks are triggered inside <var>*</var>_async methods, you cannot pre-empt an RPC by raising tasklets.Return in a pre-RPC hook.

What's next