Python 2.7 has reached end of supportand will bedeprecatedon January 31, 2026. After deprecation, you won't be able to deploy Python 2.7applications, even if your organization previously used an organization policy tore-enable deployments of legacy runtimes. Your existing Python2.7 applications will continue to run and receive traffic after theirdeprecation date. We recommend thatyoumigrate to the latest supported version of Python.

Creating Entity Models

This page describes how to use the legacy bundled services and APIs. This API can only run in first-generation runtimes in the App Engine standard environment. If you are updating to the App Engine Python 3 runtime, refer to themigration guide to learn about your migration options for legacy bundled services.

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 yourapplication can run some code before or after some type of operations, forexample, before eachget().

Creating a model class with properties

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

fromgoogle.appengine.extimportndb
...
classAccount(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 theEntity 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 calledExpando changes the behavior ofits entities so that any attribute assigned is saved to the data store. Notethat such attributes can't start with an underscore.

Here is how to create an Expando model:

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

This writes an entity to the data store with afoo property with integer value1,abar property with string value'blah',and a repeatedtag property with stringvalues'exp','and', and'oh'.The properties are indexed, and you can inspect them using theentity's_properties attribute:

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

AnExpando created by getting a value fromthe data store has properties for all property values that were savedin the data store.

An application can add predefined properties to anExpando subclass:

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

This givesemployee aname attribute with value'Sandy',anage attribute with valueNone,and a dynamic attributelocation with value'SF'.

To create anExpando subclass whose properties are unindexed,set_default_indexed = False in the subclass definition:

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

You can also set_default_indexed on anExpando entity. In this case it will affect all properties assignedafter it was set.

Another useful technique is querying anExpando kind for a dynamic property. Aquery like the following

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

won't work, because the class doesn't have a property object for the locationproperty. Instead, useGenericProperty, the classExpando uses for dynamicproperties:

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

Using Model Hooks

NDB offers a lightweight hooking mechanism. By defining a hook, an applicationcan run some code before or after some type of operations; for example, aModelmight run some function before everyget().

A hook function runs when using the synchronous, asynchronous and multi versionsof the appropriate method. For example, a "pre-get" hook would apply to all ofget(),get_async(), andget_multi(). There are pre-RPC and post-RPC versionsof 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:

fromgoogle.appengine.extimportndb
...
classFriend(ndb.Model):name=ndb.StringProperty()def_pre_put_hook(self):_notify('Gee wiz I have a new friend!')@classmethoddef_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 calledfut=f.key.delete_async()# _post_delete_hook not yet calledfut.get_result()# _post_delete_hook is called

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

All post- hooks have aFutureargument at the end of the call signature. ThisFuture objectholds the result of the action. You can callget_result()on thisFuture to retrieve the result; you can be sure thatget_result()won't block, since theFuture 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 cannotpre-empt an RPC by raisingtasklets.Return in a pre-RPC hook.

What's next

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-15 UTC.