I'm building an API using Endpoints and Endpoints Proto Datastore and testing with the App Engine Launcher. The code below is what I'm using to build nested entities two layers deep (Grandparent/Parent/Child).
The variable _child
returns 'None' (set on line 98) seemingly at random, this breaks ndb.Key
on lines 139 and 143 and causes BadArgumentError: Incomplete Key entry must be last
.
import endpoints
from google.appengine.ext import ndb
from protorpc import remote
from endpoints_proto_datastore.ndb import EndpointsAliasProperty
from endpoints_proto_datastore.ndb import EndpointsModel
import logging
project_api = endpoints.api(name='project', version='v1', description='Project API')
""" Grandparent """
class GrandparentModel(EndpointsModel):
_message_fields_schema = ('grandparent',)
def set_grandparent(self, value):
if not isinstance(value, basestring):
raise TypeError('Grandparent name must be a string.')
self.UpdateFromKey(ndb.Key(GrandparentModel, value))
@EndpointsAliasProperty(setter=set_grandparent, required=True)
def grandparent(self):
if self.key is not None:
return self.key.string_id()
""" Parent """
class ParentModel(EndpointsModel):
_message_fields_schema = ('grandparent', 'parent',)
_grandparent = None
_parent = None
def set_key(self):
if self._grandparent is not None and self._parent is not None:
key = ndb.Key(GrandparentModel, self._grandparent, ParentModel, self._parent)
self.UpdateFromKey(key)
def set_parts(self):
if self.key is not None:
grandparent_pair, parent_pair = self.key.pairs()
self._grandparent = grandparent_pair[1]
self._parent = parent_pair[1]
# Grandparent
def set_grandparent(self, value):
if not isinstance(value, basestring):
raise TypeError('Grandparent name must be a string.')
self._grandparent = value
if ndb.Key(GrandparentModel, value).get() is None:
raise endpoints.NotFoundException('Grandparent %s does not exist.' % value)
self.set_key()
self._endpoints_query_info.ancestor = ndb.Key(GrandparentModel, value)
@EndpointsAliasProperty(setter=set_grandparent, required=True)
def grandparent(self):
if self._grandparent is None:
self.set_parts()
return self._grandparent
# Parent
def set_parent(self, value):
if not isinstance(value, basestring):
raise TypeError('Parent must be a string.')
self._parent = value
self.set_key()
@EndpointsAliasProperty(setter=set_parent, required=True)
def parent(self):
if self._parent is None:
self.set_parts()
return self._parent
""" Child """
class ChildModel(EndpointsModel):
_message_fields_schema = ('grandparent', 'parent', 'child',)
_grandparent = None
_parent = None
_child = None
def set_key(self):
if self._grandparent is not None and self._parent is not None and self._child is not None:
key = ndb.Key(GrandparentModel, self._grandparent, ParentModel, self._parent, ChildModel, self._child)
self.UpdateFromKey(key)
def set_parts(self):
if self.key is not None:
grandparent_pair, parent_pair, child_pair = self.key.pairs()
self._grandparent = grandparent_pair[1]
self._parent = parent_pair[1]
self._child = child_pair[1]
# Grandparent
def set_grandparent(self, value):
if not isinstance(value, basestring):
raise TypeError('Grandparent must be a string.')
self._grandparent = value
self.set_key()
@EndpointsAliasProperty(setter=set_grandparent, required=True)
def grandparent(self):
if self._grandparent is None:
self.set_parts()
return self._grandparent
# Parent
def set_parent(self, value):
if not isinstance(value, basestring):
raise TypeError('Parent name must be a string.')
self._parent = value
logging.warning('Check _grandparent value: %s' % self._grandparent)
logging.warning('Check _parent value: %s' % self._parent)
if ndb.Key(GrandparentModel, self._grandparent, ParentModel, value).get() is None:
raise endpoints.NotFoundException('Key %s does not exist.' % value)
self.set_key()
self._endpoints_query_info.ancestor = ndb.Key(GrandparentModel, self._grandparent, ParentModel, value)
@EndpointsAliasProperty(setter=set_parent, required=True)
def parent(self):
if self._parent is None:
self.set_parts()
return self._parent
# Child
def set_child(self, value):
if not isinstance(value, basestring):
raise TypeError('Child must be a string.')
self._child = value
self.set_key()
@EndpointsAliasProperty(setter=set_child, required=True)
def child(self):
if self._child is None:
self.set_parts()
return self._child
@project_api.api_class(resource_name='grandparent')
class Grandparent(remote.Service):
@GrandparentModel.method(name='insert', path='grandparent')
def grandparent_insert(self, grandparent):
grandparent.put()
return grandparent
@GrandparentModel.query_method(name='list', path='grandparent')
def grandparent_list(self, query):
return query
@project_api.api_class(resource_name='parent', path='grandparent')
class Parent(remote.Service):
@ParentModel.method(name='insert', path='{grandparent}/parent')
def parent_insert(self, parent):
parent.put()
return parent
@ParentModel.query_method(name='list', path='{grandparent}/parent', query_fields=('grandparent',))
def parent_list(self, query):
return query
@project_api.api_class(resource_name='child', path='grandparent')
class Child(remote.Service):
@ChildModel.method(name='insert', path='{grandparent}/parent/{parent}/child')
def child_insert(self, child):
child.put()
return child
@ChildModel.query_method(name='list', path='{grandparent}/parent/{parent}/child',
query_fields=('grandparent', 'parent',))
def child_list(self, query):
return query
application = endpoints.api_server([project_api], restricted=False)
Here's the log from when it breaks:
INFO 2015-08-16 10:26:50,503 module.py:809] default: "POST /_ah/spi/BackendService.getApiConfigs HTTP/1.1" 200 8577 INFO 2015-08-16 09:26:52,099 main.py:136] Check _grandparent value: g INFO 2015-08-16 09:26:52,099 main.py:137] Check _parent value: p INFO 2015-08-16 10:26:52,108 module.py:809] default: "POST /_ah/spi/Child.child_list HTTP/1.1" 200 2 INFO 2015-08-16 10:26:52,109 module.py:809] default: "GET /_ah/api/project/v1/grandparent/g/parent/p/child HTTP/1.1" 200 2 INFO 2015-08-16 10:26:59,165 module.py:809] default: "POST /_ah/spi/BackendService.getApiConfigs HTTP/1.1" 200 8577 INFO 2015-08-16 09:26:59,168 main.py:136] Check _grandparent value: None INFO 2015-08-16 09:26:59,168 main.py:137] Check _parent value: p ERROR 2015-08-16 09:26:59,168 service.py:191] Encountered unexpected error from ProtoRPC method implementation: BadArgumentError (Incomplete Key entry must be last) Traceback (most recent call last): File "C:\Development\Google\google_appengine\lib\protorpc-1.0\protorpc\wsgi\service.py", line 181, in protorpc_service_app response = method(instance, request) File "C:\Development\Google\google_appengine\lib\endpoints-1.0\endpoints\api_config.py", line 1332, in invoke_remote return remote_method(service_instance, request) File "C:\Development\Google\google_appengine\lib\protorpc-1.0\protorpc\remote.py", line 414, in invoke_remote_method response = method(service_instance, request) File "C:\Development\_Projects\API\project\endpoints_proto_datastore\ndb\model.py", line 1574, in QueryFromRequestMethod request_entity = cls.FromMessage(request) File "C:\Development\_Projects\API\project\endpoints_proto_datastore\ndb\model.py", line 1245, in FromMessage setattr(entity, name, value) File "C:\Development\_Projects\API\project\main.py", line 139, in set_parent if ndb.Key(GrandparentModel, self._grandparent, ParentModel, value).get() is None: File "C:\Development\Google\google_appengine\google\appengine\ext\ndb\key.py", line 220, in __new__ self.__namespace) = self._parse_from_args(**kwargs) File "C:\Development\Google\google_appengine\google\appengine\ext\ndb\key.py", line 245, in _parse_from_args 'Incomplete Key entry must be last') BadArgumentError: Incomplete Key entry must be last INFO 2015-08-16 10:26:59,174 module.py:809] default: "POST /_ah/spi/Child.child_list HTTP/1.1" 500 512 INFO 2015-08-16 10:26:59,174 module.py:809] default: "GET /_ah/api/project/v1/grandparent/g/parent/p/child HTTP/1.1" 503 196
I've been unable to figure it out on my own so far, so any help would be appreciated.
Thanks!