The Python 2 App Engine NDB Client Library Overview Stay organized with collections Save and categorize content based on your preferences.
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.The Google Datastore NDB Client Library allows App Engine Python appsto connect to Datastore. The NDB client library builds on theolderDB Datastore libraryadding the following data store features:
- The
StructuredPropertyclass, which allows entities to have nestedstructure. - Integrated automatic caching, which typically gives fast and inexpensive readsvia an in-context cache and Memcache.
- Supports both asynchronous APIs for concurrent actions in addition tosynchronous APIs.
This page provides an introduction and overview of the App Engine NDB client library. For information about how to migrate toCloud NDB, which supports Python 3, please seeMigrating to Cloud NDB.
Defining Entities, Keys, and Properties
Datastore stores data objects, calledentities.An entity has one or moreproperties, named values of one ofseveral supported data types. For example, a property can be a string, aninteger, or a reference to another entity.
Each entity is identified by akey, an identifier unique within theapplication's datastore. The key can have aparent, another key.This parent can itself have a parent, and so on; at the top of this "chain"of parents is a key with no parent, called theroot.
Entities whose keys have the same root form anentity group orgroup. If entities are in different groups, then changes to thoseentities might sometimes seem to occur "out of order". If the entitiesare unrelated in your application's semantics, that's fine. But if someentities' changes should be consistent, your application should makethem part of the same group when creating them.
The following entity-relationship diagram and code sample show how aGuestbookcan have multipleGreetings, which each havecontent anddate properties.
This relationship is implemented in the code sample below.
importcgiimporttextwrapimporturllibfromgoogle.appengine.extimportndbimportwebapp2classGreeting(ndb.Model):"""Models an individual Guestbook entry with content and date."""content=ndb.StringProperty()date=ndb.DateTimeProperty(auto_now_add=True)@classmethoddefquery_book(cls,ancestor_key):returncls.query(ancestor=ancestor_key).order(-cls.date)classMainPage(webapp2.RequestHandler):defget(self):self.response.out.write('<html><body>')guestbook_name=self.request.get('guestbook_name')ancestor_key=ndb.Key("Book",guestbook_nameor"*notitle*")greetings=Greeting.query_book(ancestor_key).fetch(20)greeting_blockquotes=[]forgreetingingreetings: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)))classSubmitForm(webapp2.RequestHandler):defpost(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_nameor"*notitle*"),content=self.request.get('content'))greeting.put()self.redirect('/?'+urllib.urlencode({'guestbook_name':guestbook_name}))app=webapp2.WSGIApplication([('/',MainPage),('/sign',SubmitForm)])Using Models for storing data
A model is a class that describes a type of entity, including the types and configuration for its properties. It's roughly analogous to a table in SQL. An entity can be created by calling the model's class constructor and then stored by calling theput() method.
This sample code defines the model classGreeting. EachGreeting entity has two properties: the text content of the greeting and the date the greeting was created.
classGreeting(ndb.Model):"""Models an individual Guestbook entry with content and date."""content=ndb.StringProperty()date=ndb.DateTimeProperty(auto_now_add=True)classSubmitForm(webapp2.RequestHandler):defpost(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_nameor"*notitle*"),content=self.request.get('content'))greeting.put()To create and store a new greeting, the application creates a newGreetingobject and calls itsput() method.
To make sure that greetings in a guestbook don't appear "out of order" the application sets a parent key when creating a newGreeting. Thus, the new greeting will be in the same entity group as other greetings in the same guestbook. The application uses this fact when querying: it uses an ancestor query.
Queries and Indexes
An application can query to find entities that match some filters.
@classmethoddefquery_book(cls,ancestor_key):returncls.query(ancestor=ancestor_key).order(-cls.date)classMainPage(webapp2.RequestHandler):defget(self):self.response.out.write('<html><body>')guestbook_name=self.request.get('guestbook_name')ancestor_key=ndb.Key("Book",guestbook_nameor"*notitle*")greetings=Greeting.query_book(ancestor_key).fetch(20)A typical NDB query filters entities by kind. In this example,query_book generates a query that returnsGreetingentities. A query can also specify filters on entity property values and keys.As in this example, a query can specify an ancestor, finding only entities that"belong to" some ancestor. A query can specify sort order. If a given entity hasat least one (possibly null) value for every property in the filters and sortorders and all the filter criteria are met by the property values, then thatentity is returned as a result.
Every query uses anindex, a table that contains the results for thequery in the desired order. The underlying Datastore automatically maintainssimple indexes (indexes that use only one property).
It defines its complex indexes in a configuration file,index.yaml. Thedevelopment web server automatically adds suggestions to this file when itencounters queries that do not yet have indexes configured.
You can tune indexes manually by editing the file before uploading theapplication. You can update the indexes separately from uploading theapplication by runninggcloud app deploy index.yaml.If your datastore has many entities, it takes a long time to create a new indexfor them; in this case, it's wise to update the index definitions beforeuploading code that uses the new index. You can use the Administration Consoleto find out when the indexes have finished building.
This index mechanism supports a wide range of queries and is suitable for mostapplications. However, it does not support some kinds of queries common in otherdatabase technologies. In particular, joins aren't supported.
Understanding NDB Writes: Commit, Invalidate Cache, and Apply
NDB writes data in steps:
- In the Commit phase, the underlying Datastore service records the changes.
- NDB invalidates its caches of the affected entity/entities. Thus, future readswill read from (and cache) the underlying Datastore instead of reading stalevalues from the cache.
- Finally, perhaps seconds later, the underlying Datastoreapplies thechange. It makes the change visible to global queries and eventually-consistent reads.
The NDB function that writes the data (for example,put())returns after the cache invalidation; the Apply phase happensasynchronously.
If there is a failure during the Commit phase, there are automatic retries, butif failures continue, your application receives an exception. If the Commitphase succeeds but the Apply fails, the Apply is rolled forward to completionwhen one of the following occurs:
- Periodic Datastore "sweeps" check for uncompleted Commit jobs and apply them.
- The next write, transaction, or strongly-consistent read in the impactedentity group causes the not-yet-applied changes to be applied before the read,write, or transaction.
This behavior affects how and when data is visible to your application. Thechange might not be completely applied to the underlying Datastore a few hundredmilliseconds or so after the NDB function returns. A non-ancestor queryperformed while a change is being applied might see an inconsistent state, thatis, part but not all of the change.
Transactions and caching data
The NDB Client Library can group multiple operations in a singletransaction. The transaction cannot succeed unless every operation inthe transaction succeeds; if any of the operations fail, the transaction isautomatically rolled back. This is especially useful for distributed webapplications, where multiple users might be accessing or manipulating the samedata at the same time.
NDB uses Memcache as a cache service for "hot spots" in the data. If theapplication reads some entities often, NDB can read them quickly from cache.
Using Django with NDB
To use NDB with the Django web framework, addgoogle.appengine.ext.ndb.django_middleware.NdbDjangoMiddleware to theMIDDLEWARE_CLASSES entry in your Djangosettings.py file. It's best toinsert it in front of any other middleware classes, because some othermiddleware might make datastore calls and those won't be handled properly ifthat middleware is invoked before this middleware. You can learn more aboutDjango middleware.
What's Next?
Learn more about:
- Creatingentities in NDB.
- Howtransactions are processed in Datastore.
- How tocreate and format a query with the NDB Client Library.
- Caching data using NDB and the underlying Memcache infrastructure.
- Administrating and managing stored data in Datastore.
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.