NDB key vs get_by_id - python

Just to know if I'm mistaked or not:
get() operations uses NDB cache, so this (Chapter is ndb.Model class):
# Get the entity
chapter_key = ndb.Key('Book', long(bookId), 'Chapter', long(chapterId))
chapter = chapter_key.get()
can use the ndb cache if is 2nd or more read of the entity.
But if I make this?
Chapter.get_by_id(long(id), parent=ndb.Key('Book', long(bookId)))
is this managed by ndb also, or this operation is a standart db operation and don't use cache?

Model.get_by_id will use the context-cache and memcache in exactly the same way as Key.get

As Greg's answer is correct, I just wanted to mention that instead of putting keys together manually, you may use urlsafe string, and pass it between functions.
Assuming you have a key:
page_key = ndb.Key('Book', long(bookId), 'Chapter', long(chapterId), 'Page', long(pageId))
Create urlsafe from it:
page_url_string = page_key.urlsafe()
To retrive the model, simply use:
page = ndb.Key(urlsafe=page_url_string).get()
Consider it if you're using models with several parentness, there should be no case when putting keys manually would be required, code gets messy really quick, as you need to pass additional variables between functions.

Related

Google App Engine: A handler to delete 'any' entity

My entities can currently be viewed using the path /12345 where 12345 represents its entity ID.
I want to create a handler which deletes any entity using /12345/delete.
The handler would look something like this:
class DeleteHandler(BaseHandler):
def get(self, entity_id):
# cannot retrieve entity using just entity_id
# We cannot use KIND.get_by_id(entity_id), since 'entity kind' is unknown
How can we get the entity kind using the url, so that we may use get_by_id() to retrieve the entity, and then delete it?
Am I approaching this correctly? Or is there a better way?
Entity ids are not unique across kinds, so I don't really know how you are expecting this to work.
The websafe encoded version of the full key, however, does give enough information to uniquely identify the entity. You can then use that to instantiate a key, and you can call delete on that key.
It doesn't feel right.
I would go simply with kind/12345/delete
To answer your question, one approach could be with whitelisting.
Set somewhere the kinds (classes) you want this operation to delete their objects (entities).
Suppose you have these Kinds:
class Foo(ndb.Expando):
pass
class Boo(ndb.Expando):
pass
class Bar(ndb.Expando):
pass
Now since you have the id:
kinds_to_delete = ['Foo', 'Boo', 'Bar']
for kind_to_delete in kinds_to_delete:
ndb.Key(kind_to_delete, entity_id).delete()
Or as a dirty oneliner:
[ndb.Key(kind, entity_id).delete() for kind in ['Foo', 'Boo', 'Bar']]
/<key>/delete
Maybe it's easier if you passed around the base64 representation of the key

Appengine - Upgrading from DB to NDB - Order

dear all
I'm upgrading one of my datastore entities from db to ndb. The order() call is a problem for me. As frontend will transfer a string such as "-created_at" or "title" for ordering. In the past, I basically put this value to query as shown below:
query.order("-created_at")
Now ndb doesn't support above syntax. Is there a recommended approach for translating order("-created_at") to order(-MyModel.created_at) ?
Thanks a lot!
Use dictionary lookup
sort_orders = {
"-created_at": -MyModel.created_at,
"some_other_order": SomeModel.some_property
}
def get_order(input):
return sort_orders.get(input)
That way you only accept valid/predefined set of possible orders.
The - unary operator does work in the dictionary.
e.g.
s~lightning-catfish> -Series.updatedDate
PropertyOrder(<updatedDate>, DESCENDING)
I would personally make the function/method a class_method of the model, and the dictionary of valid orders defined at the class level as well. That way all the definitions are in the same place.

App Engine, Cross reference between two entities

i will like to have two types of entities referring to each other.
but python dont know about name of second entity class in the body of first yet.
so how shall i code.
class Business(db.Model):
bus_contact_info_ = db.ReferenceProperty(reference_class=Business_Info)
class Business_Info (db.Model):
my_business_ = db.ReferenceProperty(reference_class=Business)
if you advice to use reference in only one and use the implicitly created property
(which is a query object) in other.
then i question the CPU quota penalty of using query vs directly using get() on key
Pleas advise how to write this code in python
Queries are a little slower, and so they do use a bit more resources. ReferenceProperty does not require reference_class. So you could always define Business like:
class Business(db.Model):
bus_contact_info_ = db.ReferenceProperty()
There may also be better options for your datastructure too. Check out the modelling relationships article for some ideas.
Is this a one-to-one mapping? If this is a one-to-one mapping, you may be better off denormalizing your data.
Does it ever change? If not (and it is one-to-one), perhaps you could use entity groups and structure your data so that you could just directly use the keys / key names. You might be able to do this by making BusinessInfo a child of Business, then always use 'i' as the key_name. For example:
business = Business().put()
business_info = BusinessInfo(key_name='i', parent=business).put()
# Get business_info from business:
business_info = db.get(db.Key.from_path('BusinessInfo', 'i', parent=business))
# Get business from business_info:
business = db.get(business_info.parent())

Recursive delete in google app engine

I'm using google app engine with django 1.0.2 (and the django-helper) and wonder how people go about doing recursive delete.
Suppose you have a model that's something like this:
class Top(BaseModel):
pass
class Bottom(BaseModel):
daddy = db.ReferenceProperty(Top)
Now, when I delete an object of type 'Top', I want all the associated 'Bottom' objects to be deleted as well.
As things are now, when I delete a 'Top' object, the 'Bottom' objects stay and then I get data that doesn't belong anywhere. When accessing the datastore in a view, I end up with:
Caught an exception while rendering: ReferenceProperty failed to be resolved.
I could of course find all objects and delete them, but since my real model is at least 5 levels deep, I'm hoping there's a way to make sure this can be done automatically.
I've found this article about how it works with Java and that seems to be pretty much what I want as well.
Anyone know how I could get that behavior in django as well?
You need to implement this manually, by looking up affected records and deleting them at the same time as you delete the parent record. You can simplify this, if you wish, by overriding the .delete() method on your parent class to automatically delete all related records.
For performance reasons, you almost certainly want to use key-only queries (allowing you to get the keys of entities to be deleted without having to fetch and decode the actual entities), and batch deletes. For example:
db.delete(Bottom.all(keys_only=True).filter("daddy =", top).fetch(1000))
Actually that behavior is GAE-specific. Django's ORM simulates "ON DELETE CASCADE" on .delete().
I know that this is not an answer to your question, but maybe it can help you from looking in the wrong places.
Reconsider the data structure. If the relationship will never change on the record lifetime, you could use "ancestors" feature of GAE:
class Top(db.Model): pass
class Middle(db.Model): pass
class Bottom(db.Model): pass
top = Top()
middles = [Middle(parent=top) for i in range(0,10)]
bottoms = [Bottom(parent=middle) for i in range(0,10) for middle in middles]
Then querying for ancestor=top will find all the records from all levels. So it will be easy to delete them.
descendants = list(db.Query().ancestor(top))
# should return [top] + middles + bottoms
If your hierarchy is only a small number of levels deep, then you might be able to do something with a field that looks like a file path:
daddy.ancestry = "greatgranddaddy/granddaddy/daddy/"
me.ancestry = daddy.ancestry + me.uniquename + "/"
sort of thing. You do need unique names, at least unique among siblings.
The path in object IDs sort of does this already, but IIRC that's bound up with entity groups, which you're advised not to use to express relationships in the data domain.
Then you can construct a query to return all of granddaddy's descendants using the initial substring trick, like this:
query = Person.all()
query.filter("ancestry >", gdaddy.ancestry + "\U0001")
query.filter("ancestry <", gdaddy.ancestry + "\UFFFF")
Obviously this is no use if you can't fit the ancestry into a 500 byte StringProperty.

How do I get the key value of a db.ReferenceProperty without a database hit?

Is there a way to get the key (or id) value of a db.ReferenceProperty, without dereferencing the actual entity it points to? I have been digging around - it looks like the key is stored as the property name preceeded with an _, but I have been unable to get any code working. Examples would be much appreciated. Thanks.
EDIT: Here is what I have unsuccessfully tried:
class Comment(db.Model):
series = db.ReferenceProperty(reference_class=Series);
def series_id(self):
return self._series
And in my template:
more
The result:
more
Actually, the way that you are advocating accessing the key for a ReferenceProperty might well not exist in the future. Attributes that begin with '_' in python are generally accepted to be "protected" in that things that are closely bound and intimate with its implementation can use them, but things that are updated with the implementation must change when it changes.
However, there is a way through the public interface that you can access the key for your reference-property so that it will be safe in the future. I'll revise the above example:
class Comment(db.Model):
series = db.ReferenceProperty(reference_class=Series);
def series_id(self):
return Comment.series.get_value_for_datastore(self)
When you access properties via the class it is associated, you get the property object itself, which has a public method that can get the underlying values.
You're correct - the key is stored as the property name prefixed with '_'. You should just be able to access it directly on the model object. Can you demonstrate what you're trying? I've used this technique in the past with no problems.
Edit: Have you tried calling series_id() directly, or referencing _series in your template directly? I'm not sure whether Django automatically calls methods with no arguments if you specify them in this context. You could also try putting the #property decorator on the method.

Categories