[Sqlalchemy-commits] [1251] sqlalchemy/trunk/test: added 'entity_name' keyword argument to mapper.
Brought to you by:
zzzeek
From: <co...@sq...> - 2006-04-03 21:43:34
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg dl { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; } #msg dt { float: left; width: 6em; font-weight: bold; } #msg dt:after { content:':';} #msg dl, #msg dt, #msg ul, #msg li { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; } #msg dl a { font-weight: bold} #msg dl a:link { color:#fc3; } #msg dl a:active { color:#ff0; } #msg dl a:visited { color:#cc6; } h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; } #msg pre { overflow: auto; background: #ffc; border: 1px #fc0 solid; padding: 6px; } #msg ul, pre { overflow: auto; } #patch { width: 100%; } #patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;} #patch .propset h4, #patch .binary h4 {margin:0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;} #patch .propset .diff, #patch .binary .diff {padding:10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;} #patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;} #patch .lines, .info {color:#888;background:#fff;} --></style> <title>[1251] sqlalchemy/trunk/test: added 'entity_name' keyword argument to mapper.</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>1251</dd> <dt>Author</dt> <dd>zzzeek</dd> <dt>Date</dt> <dd>2006-04-03 16:43:22 -0500 (Mon, 03 Apr 2006)</dd> </dl> <h3>Log Message</h3> <pre>added 'entity_name' keyword argument to mapper. a mapper is now associated with a class via the class object as well as the optional entity_name parameter, which is a string defaulting to None. any number of primary mappers can be created for a class, qualified by the entity name. instances of those classes will issue all of their load and save operations through their entity_name-qualified mapper, and maintain separate identity from an otherwise equilvalent object.</pre> <h3>Modified Paths</h3> <ul> <li><a href="#sqlalchemytrunklibsqlalchemyattributespy">sqlalchemy/trunk/lib/sqlalchemy/attributes.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingmapperpy">sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingobjectstorepy">sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py</a></li> <li><a href="#sqlalchemytrunklibsqlalchemymappingpropertiespy">sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py</a></li> <li><a href="#sqlalchemytrunktestalltestspy">sqlalchemy/trunk/test/alltests.py</a></li> </ul> <h3>Added Paths</h3> <ul> <li><a href="#sqlalchemytrunktestentitypy">sqlalchemy/trunk/test/entity.py</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="sqlalchemytrunklibsqlalchemyattributespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/attributes.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/lib/sqlalchemy/attributes.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -72,6 +72,11 @@ </span><span class="cx"> h.append_nohistory(value) </span><span class="cx"> </span><span class="cx"> class ManagedAttribute(object): </span><ins>+ """base class for a "managed attribute", which is attached to individual instances + of a class mapped to the keyname of the property, inside of a dictionary which is + attached to the object via the propertyname "_managed_attributes". Attribute access + which occurs through the SmartProperty property object ultimately calls upon + ManagedAttribute objects associated with the instance via this dictionary.""" </ins><span class="cx"> def __init__(self, obj, key): </span><span class="cx"> self.__obj = weakref.ref(obj) </span><span class="cx"> self.key = key </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingmapperpy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/mapper.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -66,11 +66,12 @@ </span><span class="cx"> self.extension = ext </span><span class="cx"> </span><span class="cx"> self.class_ = class_ </span><ins>+ self.entity_name = entity_name + self.class_key = ClassKey(class_, entity_name) </ins><span class="cx"> self.is_primary = is_primary </span><span class="cx"> self.order_by = order_by </span><span class="cx"> self._options = {} </span><span class="cx"> self.always_refresh = always_refresh </span><del>- self.entity_name = entity_name </del><span class="cx"> self.version_id_col = version_id_col </span><span class="cx"> </span><span class="cx"> if not issubclass(class_, object): </span><span class="lines">@@ -208,12 +209,9 @@ </span><span class="cx"> for primary_key in self.pks_by_table[self.table]: </span><span class="cx"> self._get_clause.clauses.append(primary_key == sql.bindparam("pk_"+primary_key.key)) </span><span class="cx"> </span><del>- if not mapper_registry.has_key(self.class_) or self.is_primary or (inherits is not None and inherits._is_primary_mapper()): </del><ins>+ if not mapper_registry.has_key(self.class_key) or self.is_primary or (inherits is not None and inherits._is_primary_mapper()): </ins><span class="cx"> objectstore.global_attributes.reset_class_managed(self.class_) </span><span class="cx"> self._init_class() </span><del>- self.identitytable = self.primarytable - else: - self.identitytable = mapper_registry[self.class_].primarytable </del><span class="cx"> </span><span class="cx"> if inherits is not None: </span><span class="cx"> for key, prop in inherits.props.iteritems(): </span><span class="lines">@@ -251,41 +249,50 @@ </span><span class="cx"> prop.init(key, self) </span><span class="cx"> </span><span class="cx"> def __str__(self): </span><del>- return "Mapper|" + self.class_.__name__ + "|" + self.primarytable.name </del><ins>+ return "Mapper|" + self.class_.__name__ + "|" + (self.entity_name is not None and "/%s" % self.entity_name or "") + self.primarytable.name </ins><span class="cx"> </span><span class="cx"> def _is_primary_mapper(self): </span><del>- return mapper_registry.get(self.class_, None) is self </del><ins>+ """returns True if this mapper is the primary mapper for its class key (class + entity_name)""" + return mapper_registry.get(self.class_key, None) is self </ins><span class="cx"> </span><span class="cx"> def _primary_mapper(self): </span><del>- return mapper_registry[self.class_] - </del><ins>+ """returns the primary mapper corresponding to this mapper's class key (class + entity_name)""" + return mapper_registry[self.class_key] + + def is_assigned(self, instance): + """returns True if this mapper is the primary mapper for the given instance. this is dependent + not only on class assignment but the optional "entity_name" parameter as well.""" + return instance.__class__ is self.class_ and getattr(instance, '_entity_name', None) == self.entity_name + </ins><span class="cx"> def _init_class(self): </span><span class="cx"> """sets up our classes' overridden __init__ method, this mappers hash key as its </span><span class="cx"> '_mapper' property, and our columns as its 'c' property. if the class already had a </span><span class="cx"> mapper, the old __init__ method is kept the same.""" </span><del>- if not self.class_.__dict__.has_key('_mapper'): - oldinit = self.class_.__init__ - def init(self, *args, **kwargs): - # this gets the AttributeManager to do some pre-initialization, - # in order to save on KeyErrors later on - objectstore.global_attributes.init_attr(self) - - nohist = kwargs.pop('_mapper_nohistory', False) - session = kwargs.pop('_sa_session', objectstore.get_session()) - if not nohist: - # register new with the correct session, before the object's - # constructor is called, since further assignments within the - # constructor would otherwise bind it to whatever get_session() is. - session.register_new(self) - if oldinit is not None: - oldinit(self, *args, **kwargs) - # override oldinit, insuring that its not already one of our - # own modified inits - if oldinit is None or not hasattr(oldinit, '_sa_mapper_init'): - init._sa_mapper_init = True - self.class_.__init__ = init - mapper_registry[self.class_] = self - self.class_.c = self.c </del><ins>+ oldinit = self.class_.__init__ + def init(self, *args, **kwargs): + self._entity_name = kwargs.pop('_sa_entity_name', None) + + # this gets the AttributeManager to do some pre-initialization, + # in order to save on KeyErrors later on + objectstore.global_attributes.init_attr(self) + + nohist = kwargs.pop('_mapper_nohistory', False) + session = kwargs.pop('_sa_session', objectstore.get_session()) + if not nohist: + # register new with the correct session, before the object's + # constructor is called, since further assignments within the + # constructor would otherwise bind it to whatever get_session() is. + session.register_new(self) + if oldinit is not None: + oldinit(self, *args, **kwargs) + # override oldinit, insuring that its not already one of our + # own modified inits + if oldinit is None or not hasattr(oldinit, '_sa_mapper_init'): + init._sa_mapper_init = True + self.class_.__init__ = init + mapper_registry[self.class_key] = self + if self.entity_name is None: + self.class_.c = self.c </ins><span class="cx"> </span><span class="cx"> def set_property(self, key, prop): </span><span class="cx"> self.props[key] = prop </span><span class="lines">@@ -325,7 +332,7 @@ </span><span class="cx"> """returns an instance of the object based on the given identifier, or None </span><span class="cx"> if not found. The *ident argument is a </span><span class="cx"> list of primary key columns in the order of the table def's primary key columns.""" </span><del>- key = objectstore.get_id_key(ident, self.class_) </del><ins>+ key = objectstore.get_id_key(ident, self.class_, self.entity_name) </ins><span class="cx"> #print "key: " + repr(key) + " ident: " + repr(ident) </span><span class="cx"> return self._get(key, ident) </span><span class="cx"> </span><span class="lines">@@ -352,7 +359,7 @@ </span><span class="cx"> </span><span class="cx"> def identity_key(self, *primary_key): </span><span class="cx"> """returns the instance key for the given identity value. this is a global tracking object used by the objectstore, and is usually available off a mapped object as instance._instance_key.""" </span><del>- return objectstore.get_id_key(tuple(primary_key), self.class_) </del><ins>+ return objectstore.get_id_key(tuple(primary_key), self.class_, self.entity_name) </ins><span class="cx"> </span><span class="cx"> def instance_key(self, instance): </span><span class="cx"> """returns the instance key for the given instance. this is a global tracking object used by the objectstore, and is usually available off a mapped object as instance._instance_key.""" </span><span class="lines">@@ -847,7 +854,7 @@ </span><span class="cx"> return statement </span><span class="cx"> </span><span class="cx"> def _identity_key(self, row): </span><del>- return objectstore.get_row_key(row, self.class_, self.pks_by_table[self.table]) </del><ins>+ return objectstore.get_row_key(row, self.class_, self.pks_by_table[self.table], self.entity_name) </ins><span class="cx"> </span><span class="cx"> def _instance(self, row, imap, result = None, populate_existing = False): </span><span class="cx"> """pulls an object instance from the given row and appends it to the given result </span><span class="lines">@@ -886,7 +893,7 @@ </span><span class="cx"> # plugin point </span><span class="cx"> instance = self.extension.create_instance(self, row, imap, self.class_) </span><span class="cx"> if instance is EXT_PASS: </span><del>- instance = self.class_(_mapper_nohistory=True) </del><ins>+ instance = self.class_(_mapper_nohistory=True, _sa_entity_name=self.entity_name) </ins><span class="cx"> imap[identitykey] = instance </span><span class="cx"> isnew = True </span><span class="cx"> else: </span><span class="lines">@@ -1086,7 +1093,16 @@ </span><span class="cx"> if self.next is not None: </span><span class="cx"> self.next.before_delete(mapper, instance) </span><span class="cx"> </span><del>- </del><ins>+class ClassKey(object): + """keys a class and an entity name to a mapper, via the mapper_registry""" + def __init__(self, class_, entity_name): + self.class_ = class_ + self.entity_name = entity_name + def __hash__(self): + return hash((self.class_, self.entity_name)) + def __eq__(self, other): + return self.class_ is other.class_ and self.entity_name == other.entity_name + </ins><span class="cx"> def hash_key(obj): </span><span class="cx"> if obj is None: </span><span class="cx"> return 'None' </span><span class="lines">@@ -1100,11 +1116,14 @@ </span><span class="cx"> def object_mapper(object): </span><span class="cx"> """given an object, returns the primary Mapper associated with the object </span><span class="cx"> or the object's class.""" </span><del>- return class_mapper(object.__class__) </del><ins>+ try: + return mapper_registry[ClassKey(object.__class__, getattr(object, '_entity_name', None))] + except KeyError: + raise InvalidRequestError("Class '%s' entity name '%s' has no mapper associated with it" % (object.__class__.__name__, getattr(object, '_entity_name', None))) </ins><span class="cx"> </span><del>-def class_mapper(class_): - """given a class, returns the primary Mapper associated with the class.""" </del><ins>+def class_mapper(class_, entity_name=None): + """given a ClassKey, returns the primary Mapper associated with the key.""" </ins><span class="cx"> try: </span><del>- return mapper_registry[class_] </del><ins>+ return mapper_registry[ClassKey(class_, entity_name)] </ins><span class="cx"> except (KeyError, AttributeError): </span><del>- raise InvalidRequestError("Class '%s' has no mapper associated with it" % class_.__name__) </del><ins>+ raise InvalidRequestError("Class '%s' entity name '%s' has no mapper associated with it" % (class_.__name__, entity_name)) </ins></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingobjectstorepy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/objectstore.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -51,7 +51,7 @@ </span><span class="cx"> if self.__pushed_count == 0: </span><span class="cx"> for n in self.nest_on: </span><span class="cx"> n.pop_session() </span><del>- def get_id_key(ident, class_): </del><ins>+ def get_id_key(ident, class_, entity_name=None): </ins><span class="cx"> """returns an identity-map key for use in storing/retrieving an item from the identity </span><span class="cx"> map, given a tuple of the object's primary key values. </span><span class="cx"> </span><span class="lines">@@ -60,15 +60,12 @@ </span><span class="cx"> </span><span class="cx"> class_ - a reference to the object's class </span><span class="cx"> </span><del>- table - a Table object where the object's primary fields are stored. - - selectable - a Selectable object which represents all the object's column-based fields. - this Selectable may be synonymous with the table argument or can be a larger construct - containing that table. return value: a tuple object which is used as an identity key. """ - return (class_, tuple(ident)) </del><ins>+ entity_name - optional string name to further qualify the class + """ + return (class_, tuple(ident), entity_name) </ins><span class="cx"> get_id_key = staticmethod(get_id_key) </span><span class="cx"> </span><del>- def get_row_key(row, class_, primary_key): </del><ins>+ def get_row_key(row, class_, primary_key, entity_name=None): </ins><span class="cx"> """returns an identity-map key for use in storing/retrieving an item from the identity </span><span class="cx"> map, given a result set row. </span><span class="cx"> </span><span class="lines">@@ -77,13 +74,12 @@ </span><span class="cx"> </span><span class="cx"> class_ - a reference to the object's class </span><span class="cx"> </span><del>- table - a Table object where the object's primary fields are stored. - - selectable - a Selectable object which represents all the object's column-based fields. - this Selectable may be synonymous with the table argument or can be a larger construct - containing that table. return value: a tuple object which is used as an identity key. </del><ins>+ primary_key - a list of column objects that will target the primary key values + in the given row. + + entity_name - optional string name to further qualify the class </ins><span class="cx"> """ </span><del>- return (class_, tuple([row[column] for column in primary_key])) </del><ins>+ return (class_, tuple([row[column] for column in primary_key]), entity_name) </ins><span class="cx"> get_row_key = staticmethod(get_row_key) </span><span class="cx"> </span><span class="cx"> class SessionTrans(object): </span><span class="lines">@@ -222,11 +218,11 @@ </span><span class="cx"> u.register_new(instance) </span><span class="cx"> return instance </span><span class="cx"> </span><del>-def get_id_key(ident, class_): - return Session.get_id_key(ident, class_) </del><ins>+def get_id_key(ident, class_, entity_name=None): + return Session.get_id_key(ident, class_, entity_name) </ins><span class="cx"> </span><del>-def get_row_key(row, class_, primary_key): - return Session.get_row_key(row, class_, primary_key) </del><ins>+def get_row_key(row, class_, primary_key, entity_name=None): + return Session.get_row_key(row, class_, primary_key, entity_name) </ins><span class="cx"> </span><span class="cx"> def begin(): </span><span class="cx"> """begins a new UnitOfWork transaction. the next commit will affect only </span></span></pre></div> <a id="sqlalchemytrunklibsqlalchemymappingpropertiespy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/lib/sqlalchemy/mapping/properties.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -590,6 +590,8 @@ </span><span class="cx"> objectstore.global_attributes.register_attribute(class_, key, uselist = self.uselist, deleteremoved = self.private, callable_=lambda i: self.setup_loader(i), extension=self.attributeext) </span><span class="cx"> </span><span class="cx"> def setup_loader(self, instance): </span><ins>+ if not self.parent.is_assigned(instance): + return object_mapper(instance).props[self.key].setup_loader(instance) </ins><span class="cx"> def lazyload(): </span><span class="cx"> params = {} </span><span class="cx"> allparams = True </span></span></pre></div> <a id="sqlalchemytrunktestalltestspy"></a> <div class="modfile"><h4>Modified: sqlalchemy/trunk/test/alltests.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/alltests.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/test/alltests.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -44,6 +44,7 @@ </span><span class="cx"> 'cycles', </span><span class="cx"> </span><span class="cx"> # more select/persistence, backrefs </span><ins>+ 'entity', </ins><span class="cx"> 'manytomany', </span><span class="cx"> 'onetoone', </span><span class="cx"> 'inheritance', </span></span></pre></div> <a id="sqlalchemytrunktestentitypy"></a> <div class="addfile"><h4>Added: sqlalchemy/trunk/test/entity.py (1250 => 1251)</h4> <pre class="diff"><span> <span class="info">--- sqlalchemy/trunk/test/entity.py 2006-04-03 21:04:16 UTC (rev 1250) +++ sqlalchemy/trunk/test/entity.py 2006-04-03 21:43:22 UTC (rev 1251) </span><span class="lines">@@ -0,0 +1,130 @@ </span><ins>+from testbase import PersistTest, AssertMixin +import unittest +from sqlalchemy import * +import testbase + +from tables import * +import tables + +class EntityTest(AssertMixin): + """tests mappers that are constructed based on "entity names", which allows the same class + to have multiple primary mappers """ + def setUpAll(self): + global user1, user2, address1, address2 + db = testbase.db + user1 = Table('user1', db, + Column('user_id', Integer, Sequence('user1_id_seq'), primary_key=True), + Column('name', String(60), nullable=False) + ).create() + user2 = Table('user2', db, + Column('user_id', Integer, Sequence('user2_id_seq'), primary_key=True), + Column('name', String(60), nullable=False) + ).create() + address1 = Table('address1', db, + Column('address_id', Integer, Sequence('address1_id_seq'), primary_key=True), + Column('user_id', Integer, ForeignKey(user1.c.user_id), nullable=False), + Column('email', String(100), nullable=False) + ).create() + address2 = Table('address2', db, + Column('address_id', Integer, Sequence('address2_id_seq'), primary_key=True), + Column('user_id', Integer, ForeignKey(user2.c.user_id), nullable=False), + Column('email', String(100), nullable=False) + ).create() + def tearDownAll(self): + address1.drop() + address2.drop() + user1.drop() + user2.drop() + def tearDown(self): + address1.delete().execute() + address2.delete().execute() + user1.delete().execute() + user2.delete().execute() + objectstore.clear() + clear_mappers() + + def testbasic(self): + """tests a pair of one-to-many mapper structures, establishing that both + parent and child objects honor the "entity_name" attribute attached to the object + instances.""" + class User(object):pass + class Address(object):pass + + a1mapper = mapper(Address, address1, entity_name='address1') + a2mapper = mapper(Address, address2, entity_name='address2') + u1mapper = mapper(User, user1, entity_name='user1', properties ={ + 'addresses':relation(a1mapper) + }) + u2mapper =mapper(User, user2, entity_name='user2', properties={ + 'addresses':relation(a2mapper) + }) + + u1 = User(_sa_entity_name='user1') + u1.name = 'this is user 1' + a1 = Address(_sa_entity_name='address1') + a1.email='a1...@fo...' + u1.addresses.append(a1) + + u2 = User(_sa_entity_name='user2') + u2.name='this is user 2' + a2 = Address(_sa_entity_name='address2') + a2.email='a2...@fo...' + u2.addresses.append(a2) + + objectstore.commit() + assert user1.select().execute().fetchall() == [(u1.user_id, u1.name)] + assert user2.select().execute().fetchall() == [(u2.user_id, u2.name)] + assert address1.select().execute().fetchall() == [(u1.user_id, a1.user_id, 'a1...@fo...')] + assert address2.select().execute().fetchall() == [(u2.user_id, a2.user_id, 'a2...@fo...')] + + objectstore.clear() + u1list = u1mapper.select() + u2list = u2mapper.select() + assert len(u1list) == len(u2list) == 1 + assert u1list[0] is not u2list[0] + assert len(u1list[0].addresses) == len(u2list[0].addresses) == 1 + + def testpolymorphic(self): + """tests that entity_name can be used to have two kinds of relations on the same class.""" + class User(object):pass + class Address1(object):pass + class Address2(object):pass + + a1mapper = mapper(Address1, address1) + a2mapper = mapper(Address2, address2) + u1mapper = mapper(User, user1, entity_name='user1', properties ={ + 'addresses':relation(a1mapper) + }) + u2mapper =mapper(User, user2, entity_name='user2', properties={ + 'addresses':relation(a2mapper) + }) + + u1 = User(_sa_entity_name='user1') + u1.name = 'this is user 1' + a1 = Address1() + a1.email='a1...@fo...' + u1.addresses.append(a1) + + u2 = User(_sa_entity_name='user2') + u2.name='this is user 2' + a2 = Address2() + a2.email='a2...@fo...' + u2.addresses.append(a2) + + objectstore.commit() + assert user1.select().execute().fetchall() == [(u1.user_id, u1.name)] + assert user2.select().execute().fetchall() == [(u2.user_id, u2.name)] + assert address1.select().execute().fetchall() == [(u1.user_id, a1.user_id, 'a1...@fo...')] + assert address2.select().execute().fetchall() == [(u2.user_id, a2.user_id, 'a2...@fo...')] + + objectstore.clear() + u1list = u1mapper.select() + u2list = u2mapper.select() + assert len(u1list) == len(u2list) == 1 + assert u1list[0] is not u2list[0] + assert len(u1list[0].addresses) == len(u2list[0].addresses) == 1 + assert isinstance(u1list[0].addresses[0], Address1) + assert isinstance(u2list[0].addresses[0], Address2) + +if __name__ == "__main__": + testbase.main() </ins></span></pre> </div> </div> </body> </html> |