get_by_id and parent Key - google-app-engine

I ever use get_by_id for get an entity from datastore and never use Ancestor Paths. But the complexity of my models has made it necessary. In fact Ancestor Paths solve a big problem but now when I try to get_by_id an entity return None if the entity have the parent key. This means that I need to add the parent Key:
entity = MyModel.get_by_id(id)
This would becomes:
entity = MyModel.get_by_id(id, parent=key)
How to build the parent Key?
edit:
At this point I prefer to leave ancestor paths and add another keyproperty.

If you want using entity group you can simply create a key like that:
key_parent = db.Key.from_path('MyModelParent', 'id_parent') # You don't have to create this kind in the datastore.
id = int(self.request.get('id'))
entity = MyModel.get_by_id(id, parent=key_parent)
I notice that: You use an upper case for the key parameter. It's parent not Parent.
entity = MyModel.get_by_id(id, Parent=key) # Wrong
entity = MyModel.get_by_id(id, parent=key)
Oh, you are using NDB:
key_parent = ndb.Key('MyModelParent', 'id_parent')

Related

Modelling with ndb

I'm quite new to ndb. This is how my structure looks like in general:
a = [b, c]
b = [d, e, f]
d = [g, h]
e = [k, l, m, n]
f = [o]
c = [p, r, t]
I have the following model.
class Child(ndb.Model):
name = ndb.StringProperty()
child = ndb.KeyProperty(kind="Child", repeated=True)
class Root(ndb.Model):
name = ndb.StringProperty()
child = db.StructuredProperty(Child, repeated=True)
I can't do this since ndb won't allow me to repeat it because I already repeat Child.
What would be the proper way to model this structure?
Since the entities of the Root and Child kinds are almost the same, The data I see you are trying to model is a classic example of one-to-many relationship between entities of the same kind. The modelling for this sort of relationship is below:
class RelatedKind(ndb.Model):
name = ndb.StringProperty()
root = ndb.KeyProperty(kind="RelatedKind")
To create entities:
a = RelatedKind(name='a')
a_key = a.put()
b = RelatedKind(name='b', root=a_key)
b_key = b.put()
c = RelatedKind(name='c', root=a_key)
c_key = c.put()
# To get all 'child' of a;
child_a = RelatedKind.query(root == a_key).fetch()
print(child_a)
# >>> [b_key, c_key]
With datastore query, and just keyproperty, you achieve the same modelling without using repeated.
If you just want to be able to store many 'Child' entities on a single 'Root', you can use a LocalStructuredProperty to contain the Child model instead (but this means it won't be indexed). There's a hint to this behavior in the App Engine NDB docs when it discusses nested structured properties:
Although a StructuredProperty can be repeated and a StructuredProperty can contain another StructuredProperty, beware: if one structured property contains another, only one of them can be repeated. A work-around is to use LocalStructuredProperty, which does not have this constraint (but does not allow queries on its property values).
Another option for modeling nested relationships like this would be to use ancestors on the keys. So, for example, let's say your Root key path were: ('Root', 1). You could add children below it with keys ('Root', 1, 'Child', 1), ('Root', 1, 'Child', 5), and so on, appending 'Child' to the keypath each time. Then, we you wanted to query for the children of an object, you could just use an ancestor query, e.g.:
def create_child(parent, name):
new_child = Child(parent=parent.key, name=name)
new_child.put()
return new_child
def get_children(parent):
return Child.query(ancestor=parent.key)
class Child(ndb.Model):
name = ndb.StringProperty()
class Root(ndb.Model):
name = ndb.StringProperty()
You don't really even need to have a Root anymore at this point, because you can assign any arbitrary keypath, and you could also use the name as an ID instead and store less information.
That said, it's really completely dependent on what you're actually trying to model, there's not really enough information here to understand what you mean.
I don't see why you need a KeyProperty on the child. You could model your relationship like so:
class Child(ndb.Model):
name = ndb.StringProperty()
class Root(ndb.Model):
name = ndb.StringProperty()
child = ndb.KeyProperty(repeated=True)
c1 = Child(name="b").put()
c2 = Child(name="c").put()
a = Root(child=[c1,c2]).put() # put returns the key; otherwise you would need c1.key() here
children_keys = a.get().child # [Key(Child, 1234), Key(Child, 4567)]
# to retrieve the children, you could do
children = [ key.get() for key in children_keys ]
Keep in mind a few things. Suppose that you imagine records as being like files on your filesystem.
A KeyProperty is a pointer to another file.
A repeated property just stores multiple values.
There's no reason to use a structured property at all in this example, so let's skip that.
So, if you have the "root" object "contain" all the children via a repeated property, that'll result in you having a root file that can only be updated once every second or so, and it'll eventually grow too large.
So, in lieu of that, you have a few choices. You can use use ancestor queries, like Jeff mentioned. Or, you can just use all pointers and use a query to child any node's children:
class Node(ndb.Model):
parent = ndb.KeyProperty(kind='Node')
def get_children(self):
return Node.query().filter(Node.parent == self.key)
You can use get_children to fetch any node's children. Note that this part is eventually consistent, so recently added nodes won't necessarily show up in get_children for generally only a second or so.
root = Node(parent=None)
child1 = Node(parent=root)
child2 = Node(parent=root)
child3 = Node(parent=root)
sub_child1 = Node(parent=child1)

How to remove value from GAE NDB Property (type BlobKeyProperty)

It might be the most dumb question and my apologies for the same but I am confused
I have the following entity:
class Profile(ndb.Model):
name = ndb.StringProperty()
identifier = ndb.StringProperty()
pic = ndb.BlobKeyProperty() # stores the key to the profile picture blob
I want to delete the "pic" property value of the above entity so that it should look as fresh as if "pic" was never assigned any value. I do not intend to delete the complete entity. Is the below approach correct:
qry = Profile.query(Profile.identifier==identifier)
result_record_list = qry.fetch()
if result_record_list:
result_record_list[0].pic.delete() # or result_record_list[0].pic = none # or undefined or null
I am deleting the actual blob referred by this blob key separately
assign None to it and put it back to the datastore.
result_record_list[0].pic = None
result_record_list[0].put()
The datastore is an OO schemaless databse. So you can add and remove properties from the the Kind (ndb.Model) without the need of a schema update.
If you also want to cleanup the entities look at this anwser from Guido

Why does db.Model.get_by_id() return None when no parent is specified?

I'm running the following code in the GAE interactive console (/_ah/admin/interactive), and I do not understand why get_by_id() returns None when the parent is not specified. The docs do not make this limitation clear and I can't think of a reason to enforce it.
import my_model
print my_model.all().fetch(1)[0].key().id() # Returns 33006, used later
print my_model.get_by_id(33006)
print my_model.get_by_id(my_model.all().fetch(1)[0].key().id())
parent = my_model.all().fetch(1)[0].parent()
print my_model.get_by_id(33006, parent=parent)
Output:
33006
None
None
<my_model object at 0x109a6a690>
db.Model definition and code showing object creation with ancestor:
class my_model(db.Model):
user_id = db.StringProperty(indexed=True)
email = db.StringProperty(indexed=True, default=None)
def create(parent):
obj = my_model(user_id='x', email='y', parent=parent)
obj.put()
The answer to your question is: because the same ID could be in another entity but with a different parent.
The IDs will be all different with the same parent or for all entities without a parent, but if there is an ancestor then your numerical IDs are not unique.

db.Key.from_path() does not return correct key

I am using db.Key.from_path(Model, key_name) in several different places in my code and then call either db.get() or Model.get_by_key_name(). I noticed that these latter commands were always returning 0 items even though I knew for sure I should be getting something back. Upon closer inspection, I noticed that the db.Key.from_path() command was not returning the correct key. The key returned looks very similar, especially at the beginning, but some of the characters are different and it is about 75% shorter than the key shown in the datastore viewer. Has anyone else encountered this? Thanks.
Here is some sample code:
class Root(db.Model):
pass
class Parent(db.Model):
pass
class MyModel(db.Model):
pass
root = Root().put()
parent = Parent(key_name=parentname,parent=root).put()
mymodel = MyModel(key_name=mymodelname,parent=parent).put()
mymodel_k = db.Key.from_path('Parent','parentname','MyModel','mymodelname')
mymodel = db.get(mymodel_k)
mymodel is None
you are not constructing the path correctly with all the ancestors.
root = Root().put()
parent = Parent(key_name=parentname, parent=root).put()
mymodel = MyModel(key_name=mymodelname, parent=parent).put()
Root -> has no parents
Parent -> has parent Root
MyModel -> has parent Parent
db.Key.from_path('Parent','parentname','MyModel','mymodelname')
this one misses the Root ancestor which is contained in the Parent Key.
the right key would be:
db.Key.from_path('MyModel', 'mymodelname', parent=parent)
and this is why they key you create is shorter! one ancestor is missing.
It looks like there are two ancestor levels, so you should use this:
Key.from_path('Root', root.key().id(), 'Parent','parentname','MyModel','mymodelname')
or
Key.from_path('Parent','parentname','MyModel','mymodelname', parent=root)

Python - read with key in App Engine

I have a python program in Google App Engine
When finding an object in the datastore when I have the key as a string, how can I do a direct read. Below is my code which is performing a loop, not good.....
class Opportunity(db.Model):
customer = db.ReferenceProperty(Customer,collection_name='opportunitys')
BNusername = db.StringProperty()
opportunity_no = db.StringProperty()
# etc etc etc.....
#BnPresets holds the object key as a string
opportunitys = Opportunity.all()
opportunitys.filter('BNusername =',BnPresets.myusername)
for oprec in opportunitys:
if str(oprec.key()) == BnPresets.recordkey:
opportunity = oprec
# I have the object here and can process etc etc
You can instantiate db.Key from string by passing it directly to the constructor:
opportunity_key = db.Key(BnPresets.recordkey)
Once you have that, simply db.get to obtain the entity identified by this key:
opportunity = db.get(opportunity_key)
I guess (by looking at the query you use) that you also want to verify the username of the object you got:
if opportunity.BNusername == BnPresets.myusername
process_opportunity(opportunity)
That should be pretty much it. The bottom line is that you should use the key first - as it uniquely identifies your entity - rather than querying for some other property and iterating through results.

Resources