From: Mike Bayer Date: Sat, 5 Jan 2008 23:27:02 +0000 (+0000) Subject: - synonyms can now be created against props that don't exist yet, X-Git-Tag: rel_0_4_2b~5 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a78914942ccc26bfad91e367eaaae2dbd29e4439;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - synonyms can now be created against props that don't exist yet, which are later added via add_property(). This commonly includes backrefs. (i.e. you can make synonyms for backrefs without worrying about the order of operations) [ticket:919] --- diff --git a/CHANGES b/CHANGES index fa9ea42d75..872351a13d 100644 --- a/CHANGES +++ b/CHANGES @@ -13,7 +13,12 @@ CHANGES - orm - Fixed cascades on a += assignment to a list-based relation. - + + - synonyms can now be created against props that don't exist yet, + which are later added via add_property(). This commonly includes + backrefs. (i.e. you can make synonyms for backrefs without + worrying about the order of operations) [ticket:919] + - ext - '+', '*', '+=' and '*=' support for association proxied lists. diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 91bf130344..1639055bd4 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -77,10 +77,16 @@ class ProxiedAttribute(InstrumentedAttribute): def __init__(self, key, user_prop, comparator=None): self.user_prop = user_prop - self.comparator = comparator + self._comparator = comparator self.key = key self.impl = ProxiedAttribute.ProxyImpl(key) - + + def comparator(self): + if callable(self._comparator): + self._comparator = self._comparator() + return self._comparator + comparator = property(comparator) + def __get__(self, instance, owner): if instance is None: self.user_prop.__get__(instance, owner) diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 8be8359862..b2028fdcc6 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -155,7 +155,8 @@ class SynonymProperty(MapperProperty): def do_init(self): class_ = self.parent.class_ - aliased_property = self.parent._get_property(self.key, resolve_synonyms=True) + def comparator(): + return self.parent._get_property(self.key, resolve_synonyms=True).comparator self.logger.info("register managed attribute %s on class %s" % (self.key, class_.__name__)) if self.instrument is None: class SynonymProp(object): @@ -169,7 +170,7 @@ class SynonymProperty(MapperProperty): return getattr(obj, self.name) self.instrument = SynonymProp() - sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.instrument, useobject=False, comparator=aliased_property.comparator) + sessionlib.register_attribute(class_, self.key, uselist=False, proxy_property=self.instrument, useobject=False, comparator=comparator) def merge(self, session, source, dest, _recursive): pass diff --git a/test/orm/mapper.py b/test/orm/mapper.py index 28885356cb..c94c46a01d 100644 --- a/test/orm/mapper.py +++ b/test/orm/mapper.py @@ -260,6 +260,59 @@ class MapperTest(MapperSuperTest): assert u.user_name == 'jack' u.user_name = 'jacko' assert m._columntoproperty[users.c.user_name] is m.get_property('_user_name') + + def test_synonym_replaces_backref(self): + assert_calls = [] + class Address(object): + def _get_user(self): + assert_calls.append("get") + return self._user + def _set_user(self, user): + assert_calls.append("set") + self._user = user + user = property(_get_user, _set_user) + + # synonym is created against nonexistent prop + mapper(Address, addresses, properties={ + 'user':synonym('_user') + }) + compile_mappers() + + # later, backref sets up the prop + mapper(User, users, properties={ + 'addresses':relation(Address, backref='_user') + }) + + sess = create_session() + u1 = sess.query(User).get(7) + u2 = sess.query(User).get(8) + # comparaison ops need to work + a1 = sess.query(Address).filter(Address.user==u1).one() + assert a1.address_id == 1 + a1.user = u2 + assert a1.user is u2 + self.assertEquals(assert_calls, ["set", "get"]) + + def test_self_ref_syn(self): + t = Table('nodes', MetaData(), + Column('id', Integer, primary_key=True), + Column('parent_id', Integer, ForeignKey('nodes.id'))) + + class Node(object): + pass + + mapper(Node, t, properties={ + '_children':relation(Node, backref=backref('_parent', remote_side=t.c.id)), + 'children':synonym('_children'), + 'parent':synonym('_parent') + }) + + n1 = Node() + n2 = Node() + n1.children.append(n2) + assert n2.parent is n2._parent is n1 + assert n1.children[0] is n1._children[0] is n2 + self.assertEquals(str(Node.parent == n2), ":param_1 = nodes.parent_id") def test_illegal_non_primary(self): mapper(User, users)