Python 使用自定义验证器存储重复的KeyProperty时出现BadValueError

Python 使用自定义验证器存储重复的KeyProperty时出现BadValueError,python,google-app-engine,google-cloud-datastore,app-engine-ndb,Python,Google App Engine,Google Cloud Datastore,App Engine Ndb,我试图通过使用一个自定义验证器来简化ndb KeyProperties的JSON序列化和反序列化,该验证器将字符串转换为相应类型的键 我们的想法是拥有这样一个财产: def key_validator(kind): def validator(prop, value): if not isinstance(value, ndb.Key): return ndb.Key(kind, value) return value return validator cl

我试图通过使用一个自定义验证器来简化ndb KeyProperties的JSON序列化和反序列化,该验证器将字符串转换为相应类型的

我们的想法是拥有这样一个财产:

def key_validator(kind):
  def validator(prop, value):
    if not isinstance(value, ndb.Key):
      return ndb.Key(kind, value)
    return value
  return validator

class Bar(ndb.Model):

  foo = ndb.KeyProperty('Foo', validator=key_validator('Foo'))
正如您所见,验证器将任何字符串转换为给定类型的
键。
目标是能够将包含键id的JSON对象传递给
populate
方法,如下所示:

bar = Bar()
bar.populate(json.loads('{"foo": "1234"}'))
应该有效地做到这一点:

bar = Bar()
bar.foo = ndb.Key("Foo", "1234")
问题是,这需要重写
KeyProperty
,因为在执行一些基本验证后会调用验证器,而验证失败,因为
“1234”
显然不是
键,请参阅

因此,为了实现这一点,我创建了一个“ValidationMixin”和一个新的KeyProperty,在进行任何其他验证之前调用验证器(并且还将
序列化为id)

使用此
KeyProperty
可以像符咒一样使用非重复属性。不幸的是,当属性具有
repeated=True
时,它会严重失败

调用
bar.populate(json.loads('[{“foo”:“1234”}]')
后接
put()
,会引发以下异常:

正如您所看到的,它抱怨值是一个列表,而不是一个
键。请注意,异常是在
put()
中抛出的,而不是在
populate
中抛出的,因此
\u set\u value
执行的初始验证成功

所以我的问题是,我的方法是失败的还是应该奏效?如果它应该起作用,为什么不起作用,如何修复

更新 根据堆栈跟踪,代码执行通过,这很奇怪,因为该属性是重复的,并且应该将另一个分支插入

更新2
我刚刚发现,当我从模型中删除另一个非重复的
KeyProperty
时,它会起作用。看起来序列化已中断,错误的
KeyProperty
实例被传递到
\u seralize
方法

确定,找到了它
KeyProperty
有一个非常奇怪的构造函数“签名魔法”(SignatureMagic)()

关键是,如果第一个参数是字符串,它将成为属性的字段名,而不是种类!如果要按字符串指定种类,必须使用关键字参数,否则种类参数必须是实际类型,而不仅仅是名称。如果我错了,请纠正我,但这不是公开文档的一部分。这确实令人困惑,因为使用
ndb.Key
实际上可以将种类指定为字符串作为第一个位置参数

碰巧我有3个键属性,它们的类型相同,但属性名称不同。但是,由于我将种类指定为字符串,它实际上成为了名称。所以这三个属性都使用相同的名称。结果,重复的属性值被非重复的
KeyProperty
实例序列化,导致此崩溃

解决方案是使用关键字参数指定种类:

foo = ndb.KeyProperty(kind='Foo', validator=key_validator('Foo'))
将KeyProperties从/序列化到JSON现在可以很好地工作

  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 3451, in _put
    return self._put_async(**ctx_options).get_result()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 383, in get_result
    self.check_success()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 427, in _help_tasklet_along
    value = gen.throw(exc.__class__, exc, tb)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 824, in put
    key = yield self._put_batcher.add(entity, options)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/tasklets.py", line 430, in _help_tasklet_along
    value = gen.send(val)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/context.py", line 358, in _put_tasklet
    keys = yield self._conn.async_put(options, datastore_entities)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/datastore/datastore_rpc.py", line 1852, in async_put
    pbs = [entity_to_pb(entity) for entity in entities]
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 697, in entity_to_pb
    pb = ent._to_pb()
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 3167, in _to_pb
    prop._serialize(self, pb, projection=self._projection)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1422, in _serialize
    values = self._get_base_value_unwrapped_as_list(entity)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1192, in _get_base_value_unwrapped_as_list
    wrapped = self._get_base_value(entity)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1180, in _get_base_value
    return self._apply_to_values(entity, self._opt_call_to_base_type)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1355, in _apply_to_values
    newvalue = function(value)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1234, in _opt_call_to_base_type
    value = _BaseValue(self._call_to_base_type(value))
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1255, in _call_to_base_type
    return call(value)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 1331, in call
    newvalue = method(self, value)
  File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/ext/ndb/model.py", line 2013, in _validate
  raise datastore_errors.BadValueError('Expected Key, got %r' % (value,))
BadValueError: Expected Key, got [Key('Foo', '486944fe896a44c689275e6f19e3084a')]
foo = ndb.KeyProperty(kind='Foo', validator=key_validator('Foo'))