重复 ndb.StructuredProperty 的验证器无法触发 [英] validator for repeated ndb.StructuredProperty fails to fire

查看:25
本文介绍了重复 ndb.StructuredProperty 的验证器无法触发的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我的 ndb 模型

Here is my ndb Model

from google.appengine.ext import ndb
from mainsite.rainbow.models.CFCSocialUser import CFCSocialUser


class CFCSocialGroup(ndb.Model):

    def remove_duplicate(self, value):
        raise Exception("Duplicate user detected")

    name = ndb.StringProperty(required=True)
    created_on = ndb.DateTimeProperty(auto_now_add=True)
    updated_on = ndb.DateTimeProperty(auto_now=True)
    created_by = ndb.StructuredProperty(CFCSocialUser)
    members = ndb.StructuredProperty(CFCSocialUser, repeated=True, validator=remove_duplicate)

    @staticmethod
    def create_group(name):
        """Create a new group"""
        group = CFCSocialGroup(name=name)
        return group

    def add_member(self, social_user):
        """Add a member to the local group"""
        self.members.append(social_user)

我正在努力确保不会将同一用户添加到给定组.所以我试图验证成员属性(StructuredProperty)的值.

I am trying to ensure that I do not add the same user to a given group. So I trying to validate the value of members property (StructuredProperty).

我的测试是

from unittest import TestCase
from mainsite.rainbow.models.CFCSocialGroup import CFCSocialGroup
from tests.test_CFCSocialUser import create_user
from tests.cfcsocialtests.testbase import CFCTestBase_NDB
from nose.tools import *
from nose.plugins.attrib import attr


class TestCFCSocialGroup(CFCTestBase_NDB):
    @attr("CRUD")
    @raises(Exception)
    def test_duplicate_addition(self):
        """Test to detect duplicate users in groups"""
        user1 = create_user()
        user2 = create_user()
        group = CFCSocialGroup.create_group('Group1')
        group.add_member(user1)
        group.add_member(user2)

测试未能引发异常.

这里是调试代码

    FAILED (errors=1)
MacBook-Pro:tests vinay$ nosetests -v test_CFCSocialGroup.py
Test to detect duplicate users in groups ... FAIL

======================================================================
FAIL: Test to detect duplicate users in groups
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Python/2.7/site-packages/nose/tools/nontrivial.py", line 67, in newfunc
    raise AssertionError(message)
AssertionError: test_duplicate_addition() did not raise Exception
-------------------- >> begin captured logging << --------------------
root: DEBUG: Using threading.local
root: WARNING: No ssl package found. urlfetch will not be able to validate SSL certificates.
root: DEBUG: all_pending: add <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator put(context.py:787)
root: DEBUG: all_pending: add <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: AutoBatcher(_memcache_set_tasklet): creating new queue for ('set', 32, '', None)
root: DEBUG: initial generator put(context.py:787) yielded <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:810); pending> is now blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_memcache_set_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d0ec250 created by run_queue(context.py:185) for tasklet _memcache_set_tasklet(context.py:1111); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _memcache_set_tasklet(context.py:1111)
root: DEBUG: initial generator _memcache_set_tasklet(context.py:1111) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d0ec490>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: memcache.Set
root: DEBUG: Sending {'NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw': 1} to suspended generator _memcache_set_tasklet(context.py:1122)
root: DEBUG: all_pending: success: remove <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); result True>
root: DEBUG: suspended generator _memcache_set_tasklet(context.py:1122) returned None
root: DEBUG: all_pending: success: remove <Future 10d0ec250 created by run_queue(context.py:185) for tasklet _memcache_set_tasklet(context.py:1111); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); result True>
root: DEBUG: Sending True to suspended generator put(context.py:810)
root: DEBUG: all_pending: add <Future 10d0ec510 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: AutoBatcher(_put_tasklet): creating new queue for None
root: DEBUG: suspended generator put(context.py:810) yielded <Future 10d0ec510 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:824); pending> is now blocked waiting for <Future 10d0ec510 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: nowevent: _finished_callback
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_put_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d0ec610 created by run_queue(context.py:185) for tasklet _put_tasklet(context.py:348); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _put_tasklet(context.py:348)
root: DEBUG: initial generator _put_tasklet(context.py:348) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d0ec890>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: datastore_v3.Put
root: DEBUG: Sending [Key('CFCSocialUser', 'Vinay Joseph')] to suspended generator _put_tasklet(context.py:358)
root: DEBUG: all_pending: success: remove <Future 10d0ec510 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); result Key('CFCSocialUser', 'Vinay Joseph')>
root: DEBUG: suspended generator _put_tasklet(context.py:358) returned None
root: DEBUG: all_pending: success: remove <Future 10d0ec610 created by run_queue(context.py:185) for tasklet _put_tasklet(context.py:348); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10d0ec510 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); result Key('CFCSocialUser', 'Vinay Joseph')>
root: DEBUG: Sending Key('CFCSocialUser', 'Vinay Joseph') to suspended generator put(context.py:824)
root: DEBUG: all_pending: add <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: AutoBatcher(_memcache_del_tasklet): creating new queue for (0, '', None)
root: DEBUG: suspended generator put(context.py:824) yielded <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:833); pending> is now blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: nowevent: _finished_callback
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_memcache_del_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d0ec850 created by run_queue(context.py:185) for tasklet _memcache_del_tasklet(context.py:1130); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _memcache_del_tasklet(context.py:1130)
root: DEBUG: initial generator _memcache_del_tasklet(context.py:1130) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d0ec210>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: memcache.Delete
root: DEBUG: Sending [2] to suspended generator _memcache_del_tasklet(context.py:1141)
root: DEBUG: all_pending: success: remove <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); result 2>
root: DEBUG: suspended generator _memcache_del_tasklet(context.py:1141) returned None
root: DEBUG: all_pending: success: remove <Future 10d0ec850 created by run_queue(context.py:185) for tasklet _memcache_del_tasklet(context.py:1130); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); result 2>
root: DEBUG: Sending 2 to suspended generator put(context.py:833)
root: DEBUG: suspended generator put(context.py:833) returned Key('CFCSocialUser', 'Vinay Joseph')
root: DEBUG: all_pending: success: remove <Future 10d0d2e90 created by _put_async(model.py:3467) for tasklet put(context.py:787); result Key('CFCSocialUser', 'Vinay Joseph')>
root: DEBUG: all_pending: add <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending>
root: DEBUG: nowevent: _finished_callback
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator put(context.py:787)
root: DEBUG: all_pending: add <Future 10d0ec990 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: AutoBatcher(_memcache_set_tasklet): creating new queue for ('set', 32, '', None)
root: DEBUG: initial generator put(context.py:787) yielded <Future 10d0ec990 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:810); pending> is now blocked waiting for <Future 10d0ec990 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); pending>
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_memcache_set_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d0ecd10 created by run_queue(context.py:185) for tasklet _memcache_set_tasklet(context.py:1111); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _memcache_set_tasklet(context.py:1111)
root: DEBUG: initial generator _memcache_set_tasklet(context.py:1111) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d0ecf50>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: memcache.Set
root: DEBUG: Sending {'NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw': 1} to suspended generator _memcache_set_tasklet(context.py:1122)
root: DEBUG: all_pending: success: remove <Future 10d0ec990 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); result True>
root: DEBUG: suspended generator _memcache_set_tasklet(context.py:1122) returned None
root: DEBUG: all_pending: success: remove <Future 10d0ecd10 created by run_queue(context.py:185) for tasklet _memcache_set_tasklet(context.py:1111); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10d0ec990 created by add(context.py:211) for AutoBatcher(_memcache_set_tasklet).add(('NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw', 0), ('set', 32, '', None)); result True>
root: DEBUG: Sending True to suspended generator put(context.py:810)
root: DEBUG: all_pending: add <Future 10d0ecfd0 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: AutoBatcher(_put_tasklet): creating new queue for None
root: DEBUG: suspended generator put(context.py:810) yielded <Future 10d0ecfd0 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:824); pending> is now blocked waiting for <Future 10d0ecfd0 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); pending>
root: DEBUG: nowevent: _finished_callback
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_put_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d132110 created by run_queue(context.py:185) for tasklet _put_tasklet(context.py:348); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _put_tasklet(context.py:348)
root: DEBUG: initial generator _put_tasklet(context.py:348) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d132450>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: datastore_v3.Put
root: DEBUG: Sending [Key('CFCSocialUser', 'Vinay Joseph')] to suspended generator _put_tasklet(context.py:358)
root: DEBUG: all_pending: success: remove <Future 10d0ecfd0 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); result Key('CFCSocialUser', 'Vinay Joseph')>
root: DEBUG: suspended generator _put_tasklet(context.py:358) returned None
root: DEBUG: all_pending: success: remove <Future 10d132110 created by run_queue(context.py:185) for tasklet _put_tasklet(context.py:348); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10d0ecfd0 created by add(context.py:211) for AutoBatcher(_put_tasklet).add(CFCSocialUser(key=Key('CFCSocialUser', 'Vinay Joseph'), date_of_birth=datetime.date(1900, 3, 2), email='vinay@vinayjoseph.com', username='Vinay Joseph'), None); result Key('CFCSocialUser', 'Vinay Joseph')>
root: DEBUG: Sending Key('CFCSocialUser', 'Vinay Joseph') to suspended generator put(context.py:824)
root: DEBUG: all_pending: add <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: AutoBatcher(_memcache_del_tasklet): creating new queue for (0, '', None)
root: DEBUG: suspended generator put(context.py:824) yielded <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787) suspended generator put(context.py:833); pending> is now blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); pending>
root: DEBUG: nowevent: _finished_callback
root: DEBUG: idler: _on_idle
root: DEBUG: AutoBatcher(_memcache_del_tasklet): 1 items
root: DEBUG: all_pending: add <Future 10d0eca10 created by run_queue(context.py:185) for tasklet _memcache_del_tasklet(context.py:1130); pending>
root: DEBUG: nowevent: _help_tasklet_along
root: DEBUG: Sending None to initial generator _memcache_del_tasklet(context.py:1130)
root: DEBUG: initial generator _memcache_del_tasklet(context.py:1130) yielded <google.appengine.api.apiproxy_stub_map.UserRPC object at 0x10d0ece10>
root: DEBUG: idler: _on_idle
root: DEBUG: idler _on_idle removed
root: DEBUG: rpc: memcache.Delete
root: DEBUG: Sending [2] to suspended generator _memcache_del_tasklet(context.py:1141)
root: DEBUG: all_pending: success: remove <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); result 2>
root: DEBUG: suspended generator _memcache_del_tasklet(context.py:1141) returned None
root: DEBUG: all_pending: success: remove <Future 10d0eca10 created by run_queue(context.py:185) for tasklet _memcache_del_tasklet(context.py:1130); result None>
root: DEBUG: nowevent: _on_future_completion
root: DEBUG: <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787); pending> is no longer blocked waiting for <Future 10b0ae1d0 created by add(context.py:211) for AutoBatcher(_memcache_del_tasklet).add(NDB9:agx0ZXN0YmVkLXRlc3RyHwsSDUNGQ1NvY2lhbFVzZXIiDFZpbmF5IEpvc2VwaAw, (0, '', None)); result 2>
root: DEBUG: Sending 2 to suspended generator put(context.py:833)
root: DEBUG: suspended generator put(context.py:833) returned Key('CFCSocialUser', 'Vinay Joseph')
root: DEBUG: all_pending: success: remove <Future 10d0ec150 created by _put_async(model.py:3467) for tasklet put(context.py:787); result Key('CFCSocialUser', 'Vinay Joseph')>
--------------------- >> end captured logging << ---------------------

----------------------------------------------------------------------
Ran 1 test in 0.035s

FAILED (failures=1)

推荐答案

鉴于 models.py 中的这段代码:

Given this code in models.py:

class Member(ndb.Model):

    name = ndb.StringProperty()


def remove_duplicates(prop, value):
    raise Exception('Duplicate')


class Club1(ndb.Model):

    members = ndb.StructuredProperty(Member, repeated=True, validator=remove_duplicates)  

我可以创建一个Member 实例

<代码>>m = Member(name='Alice')

用这个 Member 实例创建一个 Club1 实例会触发验证:

creating a Club1 instance with this Member instance triggers the validation:

> c1 = models.Club1(members=[m])
Traceback (most recent call last):
  <snip>
  File "models.py", line 60, in remove_duplicates
    raise Exception('Duplicate')
Exception: Duplicate

但是,创建一个空的 Club1 实例然后附加一个 Member 不会:这实际上是您的测试用例.

However, creating an empty Club1 instance and then appending a Member does not: this is effectively your test case.

> c1 = models.Club1()
> c1.members.append(m)
> c1.put()
Key('Club1', 6682831673622528)

我们可以子类化ndb.StructuredProperty并将验证放在子类中:

We can subclass ndb.StructuredProperty and put the validation in the subclass:

class MembersStructuredProperty(ndb.StructuredProperty):

    def _validate(self, value):
        raise Exception('Duplicate')


class Club2(ndb.Model):

    members = MembersStructuredProperty(Member, repeated=True)

使用 Member 创建一个 Club2 实例会像以前一样触发验证:

Creating a Club2 instance with a Member triggers the validation as before:

> c2 = models.Club2(members=[m])
Traceback (most recent call last):
  <snip>
  File "models.py", line 56, in _validate
    raise Exception('Duplicate')
Exception: Duplicate

现在附加一个 Member 然后尝试写入数据存储区也是如此:

And now so does appending a Member and then trying to write to the Datastore:

> c2 = models.Club2()
> c2.members.append(m)
> c2.put()
Traceback (most recent call last):
  <snip>
  File "models.py", line 56, in _validate
    raise Exception('Duplicate')
Exception: Duplicate

因此子类化 ndb.StructuredProperty 应该允许您的测试通过.

So subclassing ndb.StructuredProperty should allow your test to pass.

我不知道为什么 ndb 的属性验证会这样,可以说这是一个错误,或者至少是未记录的行为.

正如@DanCornilescu 在评论中所观察到的,这是一个已知错误 在 SDK 中

As @DanCornilescu observes in the comments, this is a known bug in the SDK

这篇关于重复 ndb.StructuredProperty 的验证器无法触发的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆