Search code examples
pythongoogle-app-enginegoogle-cloud-endpointsendpoints-proto-datastore

Setting parent key but not child key while using endpoint-proto-datastore


How can I set a parent/ancestor for an EndpointsModel and have the entity ID/Key generated automatically by datastore?

I've tried to adapt the keys_with_ancestors sample but seem to be hitting a bit of a block because it requires both id and parent to be specified. I'd like to do something similar except provide only parent id or key and have entity id/key auto-generated by the app engine datastore.

This following shows how I would do it using just NDB.

class Parent(ndb.Model):
    name = ndb.StringProperty()

class MyModel(ndb.Model):
    attr1 = ndb.StringProperty()
    attr2 = ndb.StringProperty()

p = Parent(name="Jerry")
p_key = p.put()  # or retrieve the key from somewhere else

mod = MyModel(parent=p_key)
mod.put()

Is this possible and could someone point me in the right direction? Thanks.


Solution

  • Following the keys_with_ancestors sample, let's assume we have the same imports and have defined the class MyParent in the same way it is defined there.

    The TL;DR answer is essentially that passing parent= to the model constructor is equivalent to creating a key with None as the last ID in the list of kind, ID pairs. For example, for a class MyModel:

    >>> parent = ndb.Key(MyModel, 1)
    >>> child = MyModel(parent=parent)
    >>> print child.key
    ndb.Key('MyModel', 1, 'MyModel', None)
    

    In order to do this with the sample, we could simply ignore the id:

    class MyModel(EndpointsModel):
      _parent = None
    
      attr1 = ndb.StringProperty()
      attr2 = ndb.StringProperty()
      created = ndb.DateTimeProperty(auto_now_add=True)
    

    and in the setter simply set the half-baked key and don't try to retrieve from the datastore (since the key is not complete):

      def ParentSet(self, value):
        if not isinstance(value, basestring):
          raise TypeError('Parent name must be a string.')
    
        self._parent = value
        if ndb.Key(MyParent, value).get() is None:
          raise endpoints.NotFoundException('Parent %s does not exist.' % value)
        self.key = ndb.Key(MyParent, self._parent, MyModel, None)
    
        self._endpoints_query_info.ancestor = ndb.Key(MyParent, value)
    

    Similarly, in the getter, you can just retrieve the parent directly from the key (though this doesn't guarantee there is only a single pair as the parent):

      @EndpointsAliasProperty(setter=ParentSet, required=True)
      def parent(self):
        if self._parent is None and self.key is not None:
          self._parent = self.key.parent().string_id()
        return self._parent
    

    Having done this you won't need to change any of the API code and the example will work as expected.