Ivan Lincoln
Ivan Lincoln

Reputation: 1

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?

Upvotes: 0

Views: 196

Answers (4)

Harvey
Harvey

Reputation: 71

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.

Upvotes: 3

Ken Kinder
Ken Kinder

Reputation: 13130

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)

Upvotes: 0

mdmoskwa
mdmoskwa

Reputation: 138

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 ]

Upvotes: 0

Jeff Tratner
Jeff Tratner

Reputation: 17076

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.

Upvotes: 2

Related Questions