Python 使用自定义验证器存储重复的KeyProperty时出现BadValueError
我试图通过使用一个自定义验证器来简化ndb KeyProperties的JSON序列化和反序列化,该验证器将字符串转换为相应类型的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
键
我们的想法是拥有这样一个财产:
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'))