2

Я пытаюсь упростить сериализацию JSON и де-сериализацию ndb KeyProperties, используя специальный валидатор, который преобразует строку в Key соответствующего типа.BadValueError при сохранении повторяющегося KeyProperty с пользовательским валидатором

Идея заключается в том, чтобы иметь свойство как это:

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')) 

Как вы можете видеть, что валидатор преобразует любую строку в Key данного вида. Цель состоит в том, чтобы иметь возможность передать объект JSON, который содержит идентификатор ключа к populate метода, как так:

bar = Bar() 
bar.populate(json.loads('{"foo": "1234"}')) 

Что должен делать это эффективно:

bar = Bar() 
bar.foo = ndb.Key("Foo", "1234") 

Проблема в том, что для этого требуется переопределить KeyProperty, потому что валидатор вызывается после выполнения некоторой базовой проверки, что не работает, потому что "1234", по-видимому, не является Key, см. issue 268.

Итак, чтобы сделать эту работу я создал «ValidationMixin» и новый KeyProperty, который вызывает валидатор перед тем принимает любое другое подтверждение места (а также сериализует Key только идентификатор).

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 

Используя этот KeyProperty работает как шарм для неповторяющихся свойств. К сожалению, он плохо справляется со свойствами, которые имеют repeated=True.

следующее исключение, когда я звоню bar.populate(json.loads('[{"foo": "1234"}]')) с последующим 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')] 

Как вы можете видеть, что жалуется значение является список вместо одного Key. Обратите внимание, что Исключение выбрано в put() не в populate, поэтому начальная проверка, выполненная _set_value, преуспела.

Так что мой вопрос: мой подход сломан или должен работать? Если он должен работать, почему он не работает и как он может быть исправлен?

обновление

Согласно стека проследить выполнение кода проходит model.py, line 1355, что странно, потому что свойство повторяется, и если другая ветвь в model.py, line 1347

обновления 2

Я только что обнаружил, что он работает, когда я удаляю пыльник, не повторяющийся KeyProperty, из модели. Похоже, что сериализация сломана и неправильный экземпляр KeyProperty передан методу _seralize

ответ

1

Хорошо, нашел его. KeyProperty имеет этот действительно странный конструктор «магия подписи» (model.py, line 1963).

Дело в том, что если первый параметр является строкой, он становится полем имени свойства, а не вида!Если вы хотите указать тип по строке, вы должны использовать аргумент ключевого слова, иначе параметр вида должен быть фактическим типом, а не только именем. Исправьте меня, если я ошибаюсь, но это не является частью публичной документации. Это действительно сбивает с толку, потому что с ndb.Key вы действительно можете указать тип как строку в качестве первого позиционного параметра.

Как оказалось, у меня было 3 KeyProperties с тем же видом, но с разными именами атрибутов. Однако, поскольку я задал тип как строку, это фактически стало именем. Таким образом, все три свойства использовали одно и то же имя. В результате повторное значение свойства было сериализовано с повторным экземпляром KeyProperty, в результате чего произошел сбой.

Решение было указать вид с ключевыми словами аргумент:

foo = ndb.KeyProperty(kind='Foo', validator=key_validator('Foo')) 

сериализации KeyProperties из/в формате JSON хорошо работает в настоящее время.

Смежные вопросы