J'essaie de simplifier la sérialisation JSON et la dé-sérialisation de KeyProperties ndb en utilisant un validateur personnalisé qui convertit une chaîne en Key
du type respectif.BadValueError lors du stockage de KeyProperty répété avec validateur personnalisé
L'idée est d'avoir une propriété comme celui-ci:
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'))
Comme vous pouvez le voir le validateur convertit une chaîne en Key
du genre donné. L'objectif est d'être en mesure de passer un objet JSON qui contient l'identifiant d'une clé à la méthode populate
comme ceci:
bar = Bar()
bar.populate(json.loads('{"foo": "1234"}'))
Ce qui devrait effectivement faire:
bar = Bar()
bar.foo = ndb.Key("Foo", "1234")
Le problème est que ceci nécessite de surcharger KeyProperty
parce que le validateur est appelé après une validation de base qui échoue, car "1234"
n'est apparemment pas un Key
, voir issue 268.
Donc, pour faire ce travail, je l'ai créé un « ValidationMixin » et une nouvelle KeyProperty qui appelle le validateur avant toute autre validation a lieu (et sérialise aussi Key
juste l'id).
class ValidationMixin(object):
# make sure to call _validator before we do as the very first validation step
def _do_validate(self, value):
if self._validator is not None:
newvalue = self._validator(self, value)
if newvalue is not None:
value = newvalue
return super(ValidationMixin, self)._do_validate(value)
# A KeyProperty that allows a validator to generate a Key.
# In addition it serializes to just the id of the key
class KeyProperty(ValidationMixin, ndb.KeyProperty):
# return just the id of the key
def _get_for_dict(self, entity):
value = self._get_value(entity)
if self._repeated:
return [v.id() for v in value]
elif value is not None:
return value.id()
return value
L'utilisation de ce KeyProperty
fonctionne comme un charme pour les propriétés non répétées. Malheureusement, il échoue mal avec les propriétés qui ont repeated=True
.
L'exception suivante est levée lorsque j'appelle bar.populate(json.loads('[{"foo": "1234"}]'))
suivie put()
:
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')]
Comme vous pouvez le voir se plaint de la valeur étant une liste au lieu d'un seul Key
. Notez que l'exception est levée dans put()
pas dans populate
, donc la validation initiale effectuée par _set_value
a réussi. Donc, ma question est la suivante: mon approche est-elle brisée ou cela devrait-il fonctionner? Si cela devrait fonctionner, pourquoi cela ne fonctionne-t-il pas et comment peut-il être réparé?
mise à jour
Selon la pile trace l'exécution de code passe model.py, line 1355, ce qui est étrange, parce que la propriété est répétée et si l'autre branche dans model.py, line 1347
mise à jour 2
Je viens de découvrir cela fonctionne quand je retire l'anthère non répétée KeyProperty
du modèle. Il semble que la sérialisation est rompue et la mauvaise instance KeyProperty
est transmise à la méthode _seralize