No Datastore changes needed
In case you wondered, despite the different APIs, NDB and the old ext.db package write exactly the same data to the Datastore. That means you don’t have to do any conversion to your datastore, and you can happily mix and match NDB and ext.db code, as long as the schema you use is equivalent. You can even convert between ext.db and NDB keys using ndb.Key.from_old_key() and key.to_old_key().
General differences
NDB is picky about types. E.g. in db, when a key is required, you can also pass an entity or a string. In NDB you must pass a key.
NDB is picky about lists. E.g. in db,
db.put()
takes either an entity or a list of entities. In NDB, you useentity.put()
to put a single entity, butndb.put_multi(<list>)
to put a list of entities.NDB prefers methods over functions. E.g. instead of
db.get(key)
, anddb.put(entity)
, NDB useskey.get()
andentity.put()
.NDB doesn't like offering two APIs that do the same thing. (On the other hand it does sometimes offer two APIs that do slightly different things.)
Side-by-side API call comparison
The tables below show similarities and differences between ndb and the old ext.db module. See the Official NDB Docs for an introduction to and reference for NDB.
Model class
google.appengine.ext.db | ndb.model |
class MyModel(db.Model): foo = db.StringProperty() |
class MyModel(ndb.Model): foo = ndb.StringProperty() |
@classmethod def kind(cls): return 'Foo' |
@classmethod def _get_kind(cls): return 'Foo' |
MyModel.kind() |
MyModel._get_kind() |
MyModel.properties() model_instance.properties() |
MyModel._properties # No () !! model_entity._properties |
MyExpando.dynamic_properties() |
MyExpando._properties # No () !! |
Entities
google.appengine.ext.db | ndb.model |
MyModel(key_name='my_key') |
MyModel(id='my_key') |
MyModel(key_name='my_key', parent=model_instance) |
MyModel(id='my_key', parent=model_instance.key) |
key = model_instance.key() |
key = model_instance.key # No () !! |
model_instance = MyModel( foo='foo', bar='bar', baz='baz') |
model_instance = MyModel( foo='foo', bar='bar', baz='baz') |
model_instance.foo = 'foo' model_instance.bar = 'bar' model_instance.baz = 'baz' |
model_instance.foo = 'foo' model_instance.bar = 'bar' model_instance.baz = 'baz' # or a shortcut... model_instance.populate( foo='foo', bar='bar', baz='baz') |
model_instance.is_saved() |
No direct equivalent; see Stack Overflow for a possible solution. |
Get
google.appengine.ext.db | ndb.model |
MyModel.get_by_key_name('my_key') |
MyModel.get_by_id('my_key') |
MyModel.get_by_id(42) |
MyModel.get_by_id(42) |
db.get(key) |
key.get() |
MyModel.get(key) |
key.get() |
db.get(model_instance) |
model_instance.key.get() |
db.get(list_of_keys) |
ndb.get_multi(list_of_keys) |
db.get(list_of_instances) |
ndb.get_multi([x.key for x in list_of_instances]) |
MyModel.get_or_insert('my_key', parent=model_instance, foo='bar') |
MyModel.get_or_insert('my_key', parent=model_instance.key, foo='bar') |
Put
google.appengine.ext.db | ndb.model |
db.put(model_instance) |
model_instance.put() |
db.put(list_of_model_instances) |
ndb.put_multi( list_of_model_instances) |
Delete
google.appengine.ext.db | ndb.model |
model_instance.delete() |
model_instance.key.delete() |
db.delete(model_instance) |
model_instance.key.delete() |
db.delete(key) |
key.delete() |
db.delete(list_of_model_instances) |
ndb.delete_multi([m.key for m in list_of_model_instances]) |
db.delete(list_of_keys) |
ndb.delete_multi(list_of_keys) |
Properties
google.appengine.ext.db | ndb.model |
db.BlobProperty() |
ndb.BlobProperty() |
db.BooleanProperty() |
ndb.BooleanProperty() |
db.ByteStringProperty() |
ndb.BlobProperty(indexed=True) |
db.CategoryProperty() |
ndb.StringProperty() |
db.DateProperty() |
ndb.DateProperty() |
db.DateTimeProperty() |
ndb.DateTimeProperty() |
db.EmailProperty() |
ndb.StringProperty() |
db.FloatProperty() |
ndb.FloatProperty() |
db.GeoPtProperty() |
ndb.GeoPtProperty() |
db.IMProperty() |
No equivalent. |
db.IntegerProperty() |
ndb.IntegerProperty() |
db.LinkProperty() |
ndb.StringProperty() Has a max size of 500. For longer
URLs, use |
db.ListProperty(bool) db.ListProperty(float) db.ListProperty(int) db.ListProperty(db.Key) # etc. |
ndb.BooleanProperty(repeated=True) ndb.FloatProperty(repeated=True) ndb.IntegerProperty(repeated=True) ndb.KeyProperty(repeated=True) # etc. |
db.PhoneNumberProperty() |
ndb.StringProperty() |
db.PostalAddressProperty() |
ndb.StringProperty() |
db.RatingProperty() |
ndb.IntegerProperty() |
db.ReferenceProperty(AnotherModel) model_instance.prop MyModel.prop \ .get_value_for_datastore \ (model_instance) |
ndb.KeyProperty(kind=AnotherModel) model_instance.prop.get() model_instance.prop |
# Using the backreference set other = model_instance.prop other.prop_set.fetch(N) |
# No direct equivalent; emulation: other = model_instance.prop.get() MyModel.query( MyModel.prop == other.key).fetch(N) |
db.SelfReferenceProperty() |
ndb.KeyProperty(kind='ThisModelClass') |
db.StringProperty() |
ndb.StringProperty() |
db.StringProperty(multiline=True) |
Not supported; strings are always allowed to contain |
db.StringListProperty() |
ndb.StringProperty(repeated=True) |
db.TextProperty() |
ndb.TextProperty() |
db.TimeProperty() |
ndb.TimeProperty() |
db.UserProperty() |
ndb.UserProperty() |
blobstore.BlobReferenceProperty() |
ndb.BlobKeyProperty() |
Building a Key
google.appengine.ext.db | ndb.model |
key = db.Key(encoded_key) |
key = ndb.Key(urlsafe=encoded_key) |
key = db.Key.from_path( 'MyKind', 'some_id', 'MyKind', 'some_id') |
key = ndb.Key( 'MyKind', 'some_id', 'MyKind', 'some_id') |
key = db.Key.from_path( MyModel, 'some_id', parent=model_instance, namespace='my_namespace') |
key = ndb.Key( MyModel, 'some_id', parent=model_instance.key, namespace='my_namespace') |
Key operations
google.appengine.ext.db | ndb.model |
key.id_or_name() |
key.id() |
key.id() |
key.integer_id() |
key.name() |
key.string_id() |
key.has_id_or_name() |
key.id() is None # or... model_instance.has_complete_key() |
key.app(), key.namespace(), key.parent(), key.kind() |
Same. |
str(key) |
key.urlsafe() |
key.to_path() |
key.flat() |
db.allocate_ids(MyModel, size) |
S, E = MyModel.allocate_ids(size) |
db.allocate_id_range(MyModel,X,Y) |
S, E = MyModel.allocate_ids(max=Y) assert S <= X |
Transactions
google.appengine.ext.db | ndb.model |
db.run_in_transaction(function) |
ndb.transaction(function) |
db.run_in_transaction( function, *args, **kwds) |
ndb.transaction( lambda: function(*args, **kwds)) |
db.run_in_transaction_custom_retries(n, function) |
ndb.transaction(function, retries=n) |
opts = \ db.create_transaction_options(xg=True) db.run_in_transaction_options(opts, fun) |
ndb.transaction(fun, xg=True) |
Queries
google.appengine.ext.db | ndb.model |
q = MyModel.all() |
q = MyModel.query() |
for result in q.run(): ... |
for result in q.iter(): ... |
q = MyModel.all() \ .filter('foo =', 'bar') \ .filter('baz >=', 'ding') |
q = MyModel.query( MyModel.foo == 'bar', MyModel.baz >= 'ding') |
q = MyModel.all() q.filter('foo =', 'bar') q.filter('baz >=', 'ding') q.order('-foo') results = q.fetch(10) |
q = MyModel.query() q = q.filter(MyModel.foo == 'bar') q = q.filter(MyModel.baz >= 'ding') q = q.order(-MyModel.foo) results = q.fetch(10) |
q.filter('__key__', k) # k is a db.Key instance |
q = q.filter(MyModel._key == k) # k is an ndb.Key instance |
a.filter('__key__ >=', k) # k is a db.Key instance |
q = q.filter(MyModel._key >= k) # k is an ndb.Key instance |
class MyExpando(Expando): pass q = MyExpando.all() q.filter('foo =', 'bar') |
class MyExpando(Expando): pass q = MyExpando.query( ndb.GenericProperty('foo') == 'bar') |
class Foo(Model): ... class Bar(Model): foo = ReferenceProperty(Foo) myfoo = <some Foo instance> for bar in myfoo.bar_set(): ... |
class Foo(Model): ... class Bar(Model): foo = KeyProperty(kind=Foo) myfoo = <some Foo instance> for bar in \ Bar.query(Bar.foo == myfoo.key): ... |
q = MyModel.all() q.ancestor(ancestor_key) |
q = MyModel.query(ancestor=ancestor_key) |
q = MyModel.all(keys_only=True) r = q.fetch(N) |
r = MyModel.query() \ .fetch(N, keys_only=True) # Alternatively: q = MyModel.query( default_options=QueryOptions( keys_only=True)) r = q.fetch(N) |
q = MyModel.gql(...) |
# same thing |
Cursors
q = MyModel.all() a = q.fetch(20) cur = q.cursor() |
q = MyModel.query() a, cur, more = q.fetch_page(20) In NDB, |
q.with_cursor(cur) b = q.fetch(20) |
b, cur, more = \ q.fetch_page(20, start_cursor=cur) |
q.with_cursor(end_cursor=cur) b = q.fetch(20) |
q.fetch(20, end_cursor=cur) |