Files
sqlalchemy/test/orm/test_validators.py
T
Mike Bayer 50e3847f09 - Added new argument `include_backrefs=True` to the
:func:`.validates` function; when set to False, a validation event
will not be triggered if the event was initated as a backref to
an attribute operation from the other side. [ticket:1535]
- break out validation tests into an updated module test_validators
2013-12-02 12:40:50 -05:00

282 lines
9.7 KiB
Python

from test.orm import _fixtures
from sqlalchemy.testing import fixtures, assert_raises, eq_, ne_
from sqlalchemy.orm import mapper, Session, validates, relationship
from sqlalchemy.testing.mock import Mock, call
class ValidatorTest(_fixtures.FixtureTest):
def test_scalar(self):
users = self.tables.users
canary = Mock()
class User(fixtures.ComparableEntity):
@validates('name')
def validate_name(self, key, name):
canary(key, name)
ne_(name, 'fred')
return name + ' modified'
mapper(User, users)
sess = Session()
u1 = User(name='ed')
eq_(u1.name, 'ed modified')
assert_raises(AssertionError, setattr, u1, "name", "fred")
eq_(u1.name, 'ed modified')
eq_(canary.mock_calls, [call('name', 'ed'), call('name', 'fred')])
sess.add(u1)
sess.commit()
eq_(
sess.query(User).filter_by(name='ed modified').one(),
User(name='ed')
)
def test_collection(self):
users, addresses, Address = (self.tables.users,
self.tables.addresses,
self.classes.Address)
canary = Mock()
class User(fixtures.ComparableEntity):
@validates('addresses')
def validate_address(self, key, ad):
canary(key, ad)
assert '@' in ad.email_address
return ad
mapper(User, users, properties={
'addresses': relationship(Address)}
)
mapper(Address, addresses)
sess = Session()
u1 = User(name='edward')
a0 = Address(email_address='noemail')
assert_raises(AssertionError, u1.addresses.append, a0)
a1 = Address(id=15, email_address='foo@bar.com')
u1.addresses.append(a1)
eq_(canary.mock_calls, [call('addresses', a0), call('addresses', a1)])
sess.add(u1)
sess.commit()
eq_(
sess.query(User).filter_by(name='edward').one(),
User(name='edward', addresses=[Address(email_address='foo@bar.com')])
)
def test_validators_dict(self):
users, addresses, Address = (self.tables.users,
self.tables.addresses,
self.classes.Address)
class User(fixtures.ComparableEntity):
@validates('name')
def validate_name(self, key, name):
ne_(name, 'fred')
return name + ' modified'
@validates('addresses')
def validate_address(self, key, ad):
assert '@' in ad.email_address
return ad
def simple_function(self, key, value):
return key, value
u_m = mapper(User, users, properties={
'addresses': relationship(Address)
}
)
mapper(Address, addresses)
eq_(
dict((k, v[0].__name__) for k, v in list(u_m.validators.items())),
{'name': 'validate_name',
'addresses': 'validate_address'}
)
def test_validator_w_removes(self):
users, addresses, Address = (self.tables.users,
self.tables.addresses,
self.classes.Address)
canary = Mock()
class User(fixtures.ComparableEntity):
@validates('name', include_removes=True)
def validate_name(self, key, item, remove):
canary(key, item, remove)
return item
@validates('addresses', include_removes=True)
def validate_address(self, key, item, remove):
canary(key, item, remove)
return item
mapper(User, users, properties={
'addresses': relationship(Address)
})
mapper(Address, addresses)
u1 = User()
u1.name = "ed"
u1.name = "mary"
del u1.name
a1, a2, a3 = Address(), Address(), Address()
u1.addresses.append(a1)
u1.addresses.remove(a1)
u1.addresses = [a1, a2]
u1.addresses = [a2, a3]
eq_(canary.mock_calls, [
call('name', 'ed', False),
call('name', 'mary', False),
call('name', 'mary', True),
# append a1
call('addresses', a1, False),
# remove a1
call('addresses', a1, True),
# set to [a1, a2] - this is two appends
call('addresses', a1, False), call('addresses', a2, False),
# set to [a2, a3] - this is a remove of a1,
# append of a3. the appends are first.
call('addresses', a3, False),
call('addresses', a1, True),
]
)
def test_validator_wo_backrefs_wo_removes(self):
self._test_validator_backrefs(False, False)
def test_validator_wo_backrefs_w_removes(self):
self._test_validator_backrefs(False, True)
def test_validator_w_backrefs_wo_removes(self):
self._test_validator_backrefs(True, False)
def test_validator_w_backrefs_w_removes(self):
self._test_validator_backrefs(True, True)
def _test_validator_backrefs(self, include_backrefs, include_removes):
users, addresses = (self.tables.users,
self.tables.addresses)
canary = Mock()
class User(fixtures.ComparableEntity):
if include_removes:
@validates('addresses', include_removes=True,
include_backrefs=include_backrefs)
def validate_address(self, key, item, remove):
canary(key, item, remove)
return item
else:
@validates('addresses', include_removes=False,
include_backrefs=include_backrefs)
def validate_address(self, key, item):
canary(key, item)
return item
class Address(fixtures.ComparableEntity):
if include_removes:
@validates('user', include_backrefs=include_backrefs,
include_removes=True)
def validate_user(self, key, item, remove):
canary(key, item, remove)
return item
else:
@validates('user', include_backrefs=include_backrefs)
def validate_user(self, key, item):
canary(key, item)
return item
mapper(User, users, properties={
'addresses': relationship(Address, backref="user")
})
mapper(Address, addresses)
u1 = User()
u2 = User()
a1, a2 = Address(), Address()
# 3 append/set, two removes
u1.addresses.append(a1)
u1.addresses.append(a2)
a2.user = u2
del a1.user
u2.addresses.remove(a2)
# copy, so that generation of the
# comparisons don't get caught
calls = list(canary.mock_calls)
if include_backrefs:
if include_removes:
eq_(calls,
[
# append #1
call('addresses', Address(), False),
# backref for append
call('user', User(addresses=[]), False),
# append #2
call('addresses', Address(user=None), False),
# backref for append
call('user', User(addresses=[]), False),
# assign a2.user = u2
call('user', User(addresses=[]), False),
# backref for u1.addresses.remove(a2)
call('addresses', Address(user=None), True),
# backref for u2.addresses.append(a2)
call('addresses', Address(user=None), False),
# del a1.user
call('user', User(addresses=[]), True),
# backref for u1.addresses.remove(a1)
call('addresses', Address(), True),
# u2.addresses.remove(a2)
call('addresses', Address(user=None), True),
# backref for a2.user = None
call('user', None, False)
]
)
else:
eq_(calls,
[
call('addresses', Address()),
call('user', User(addresses=[])),
call('addresses', Address(user=None)),
call('user', User(addresses=[])),
call('user', User(addresses=[])),
call('addresses', Address(user=None)),
call('user', None)
]
)
else:
if include_removes:
eq_(calls,
[
call('addresses', Address(), False),
call('addresses', Address(user=None), False),
call('user', User(addresses=[]), False),
call('user', User(addresses=[]), True),
call('addresses', Address(user=None), True)
]
)
else:
eq_(calls,
[
call('addresses', Address()),
call('addresses', Address(user=None)),
call('user', User(addresses=[]))
]
)