这个问题完全来自我在这里提出(并得到回答)的一个相关问题:尝试检索单个实体时出错
据我了解,要使用已提供的辅助方法以外的属性(例如)从数据存储中检索单个实体,'id'
需要将简单的数据属性转换为EndpointsAliasProperty
? 如果是,我将如何去做?还是我们只能使用'id'
(由 提供的辅助方法EndpointsModel
)而不能使用我们定义的任何属性(在这种情况下'title'
)?
这个问题完全来自我在这里提出(并得到回答)的一个相关问题:尝试检索单个实体时出错
据我了解,要使用已提供的辅助方法以外的属性(例如)从数据存储中检索单个实体,'id'
需要将简单的数据属性转换为EndpointsAliasProperty
? 如果是,我将如何去做?还是我们只能使用'id'
(由 提供的辅助方法EndpointsModel
)而不能使用我们定义的任何属性(在这种情况下'title'
)?
custom 和您定义的数据属性之一之间的区别在于EndpointsAliasProperty
它们的使用方式。它们都用于创建protorpc
消息,然后将该消息转换为EndpointsModel
包含您的自定义数据的消息。这就是魔法发生的地方。
将其分解为步骤:
from google.appengine.ext import ndb
from endpoints_proto_datastore.ndb import EndpointsModel
class MyModel(EndpointsModel):
my_attr = ndb.StringProperty()
class MyApi(...):
@MyModel.method(request_fields=('id', 'my_attr'), ...)
def my_method(self, my_model_entity):
...
protorpc
消息类是从您的字段中定义的>>> request_message_class = MyModel.ProtoModel(fields=('id', 'my_attr'))
>>> request_message_class
<class '.MyModelProto_id_my_attr'>
>>> for field in request_message_class.all_fields():
... print field.name, ':', field.variant
...
id : INT64
my_attr : STRING
每次请求由装饰有 的方法处理时都会发生这种情况@MyModel.method
。
使用protorpc
消息类,从传递到您的端点SPI(由 创建endpoints.api_server
)的 JSON 中解析一个消息实例。
当请求进入您protorpc.remote.Service
时,它会被解码:
>>> from protorpc import remote
>>> protocols = remote.Protocols.get_default()
>>> json_protocol = protocols.lookup_by_content_type('application/json')
>>> request_message = json_protocol.decode_message(
... request_message_class,
... '{"id": 123, "my_attr": "some-string"}'
... )
>>> request_message
<MyModelProto_id_my_attr
id: 123
my_attr: u'some-string'>
protorpc
消息转换为数据存储模型entity = MyModel.FromMessage(request_message)
这是您真正关心的步骤。类方法(也作为的FromMessage
一部分提供EndpointsModel
)循环遍历所有字段
for field in sorted(request_message_class.all_fields(),
key=lambda field: field.number):
对于每个设置了值的字段,将值转换为要添加到实体中的内容,并根据属性是否为 an 进行分离EndpointsAliasProperty
:
if isinstance(value_property, EndpointsAliasProperty):
alias_args.append((local_name, to_add))
else:
entity_kwargs[local_name] = to_add
完成此循环后,我们将alias_args
获得所有键、值对的有序列表以及entity_kwargs
从消息中解析的数据属性的字典。
使用这些,首先创建一个简单的实体
entity = MyModel(**entity_kwargs)
然后按顺序设置每个别名属性值:
for name, value in alias_args:
setattr(entity, name, value)
扩展行为发生在setattr(entity, name, value)
. 因为它EndpointsAliasProperty
是 的子类property
,所以它是一个描述符,并且它有一个setter
可以执行一些自定义行为,而不仅仅是设置一个值。
例如,id
属性定义为:
@EndpointsAliasProperty(setter=IdSet, property_type=messages.IntegerField)
def id(self):
setter
除了简单地设置数据之外,还执行操作:
def IdSet(self, value):
self.UpdateFromKey(ndb.Key(self.__class__, value))
此特定方法尝试使用id
数据存储区中未包含在从请求解析的实体中的任何值中的和补丁来检索存储在数据存储区中的实体。
如果您想对类似 的字段执行此操作,则my_attr
需要构建一个自定义查询,该查询可以检索具有该唯一my_attr
值的项目(如果不完全存在这样的实体,则失败)。
这是有问题的,您最好使用唯一字段,例如用于将实体存储在数据存储中的键或 ID。
带有祖先的键示例提供了创建您自己的自定义属性的一个很好的示例。
如果您真的坚持使用my_attr
来检索实体,则可以使用不同的属性名称(因为my_attr
已用于数据属性),例如fromMyAttr
:
class MyModel(EndpointsModel):
def MyAttrSet(self, value):
...
@EndpointsAliasProperty(setter=MyAttrSet)
def fromMyAttr(self):
...
在这里,MyAttrSet
实例方法将形成查询:
def MyAttrSet(self, value):
query = MyModel.query(MyModel.my_attr == value)
results = query.fetch(2)
拒绝非唯一的结果my_attr
:
if len(results) == 0:
raise endpoints.NotFoundException('Not found.')
if len(results) == 2:
raise endpoints.BadRequestException('Colliding results.')
如果我们确实找到了一个唯一的实体,则复制已存储实体的值:
matching_entity = results[0]
self._CopyFromEntity(matching_entity)
self._from_datastore = True