From: Mike Bayer Date: Sat, 26 Jul 2008 16:23:14 +0000 (+0000) Subject: - Class-bound attributes sent as arguments to X-Git-Tag: rel_0_4_7~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3daaad30a4b1e633b367eedeac69dcb7dea4e6aa;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Class-bound attributes sent as arguments to relation()'s remote_side and foreign_keys parameters are now accepted, allowing them to be used with declarative (and therefore self-referential many-to-one relations); merged from 0.5. --- diff --git a/CHANGES b/CHANGES index 4973436de0..be67d3aed6 100644 --- a/CHANGES +++ b/CHANGES @@ -128,6 +128,12 @@ CHANGES variable indicating their creation order, which declarative_base() maintains when generating Table constructs. + + - Class-bound attributes sent as arguments to + relation()'s remote_side and foreign_keys parameters + are now accepted, allowing them to be used with + declarative (and therefore self-referential many-to-one + relations); merged from 0.5. 0.4.6 ===== diff --git a/lib/sqlalchemy/orm/properties.py b/lib/sqlalchemy/orm/properties.py index 8d56cb2bf0..c0fea33704 100644 --- a/lib/sqlalchemy/orm/properties.py +++ b/lib/sqlalchemy/orm/properties.py @@ -12,7 +12,7 @@ invidual ORM-mapped attributes. from sqlalchemy import sql, schema, util, exceptions, logging from sqlalchemy.sql.util import ClauseAdapter, criterion_as_pairs, find_columns -from sqlalchemy.sql import visitors, operators, ColumnElement +from sqlalchemy.sql import visitors, operators, ColumnElement, expression from sqlalchemy.orm import mapper, sync, strategies, attributes, dependency, object_mapper from sqlalchemy.orm import session as sessionlib from sqlalchemy.orm.mapper import _class_to_mapper @@ -551,7 +551,7 @@ class PropertyLoader(StrategizedProperty): if self._legacy_foreignkey and not self._refers_to_parent_table(): self.foreign_keys = self._legacy_foreignkey - arg_foreign_keys = self.foreign_keys + arg_foreign_keys = set(expression._literal_as_column(x) for x in util.to_set(self.foreign_keys)) if self._arg_local_remote_pairs: if not arg_foreign_keys: @@ -613,10 +613,12 @@ class PropertyLoader(StrategizedProperty): else: eq_pairs = self._arg_local_remote_pairs elif self.remote_side: + remote_side = set(expression._literal_as_column(x) for x in util.to_set(self.remote_side)) + if self.direction is MANYTOONE: - eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_referenced_keys=self.remote_side, any_operator=True) + eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_referenced_keys=remote_side, any_operator=True) else: - eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=self.remote_side, any_operator=True) + eq_pairs = criterion_as_pairs(self.primaryjoin, consider_as_foreign_keys=remote_side, any_operator=True) else: if self.viewonly: eq_pairs = self.synchronize_pairs diff --git a/test/ext/declarative.py b/test/ext/declarative.py index ab07627dda..b678c7f9dd 100644 --- a/test/ext/declarative.py +++ b/test/ext/declarative.py @@ -82,6 +82,42 @@ class DeclarativeTest(TestBase, AssertsExecutionResults): assert User.addresses assert mapperlib._new_mappers is False + def test_uncompiled_attributes_in_relation(self): + class Address(Base, Fixture): + __tablename__ = 'addresses' + id = Column(Integer, primary_key=True) + email = Column(String(50)) + user_id = Column(Integer, ForeignKey('users.id')) + + class User(Base, Fixture): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + name = Column(String(50)) + addresses = relation("Address", order_by=Address.email, + foreign_keys=Address.user_id, + remote_side=Address.user_id, + ) + + # get the mapper for User. User mapper will compile, + # "addresses" relation will call upon Address.user_id for + # its clause element. Address.user_id is a _CompileOnAttr, + # which then calls class_mapper(Address). But ! We're already + # "in compilation", but class_mapper(Address) needs to initialize + # regardless, or COA's assertion fails + # and things generally go downhill from there. + class_mapper(User) + + Base.metadata.create_all() + + sess = create_session() + u1 = User(name='ed', addresses=[Address(email='abc'), Address(email='xyz'), Address(email='def')]) + sess.save(u1) + sess.flush() + sess.clear() + self.assertEquals(sess.query(User).filter(User.name == 'ed').one(), + User(name='ed', addresses=[Address(email='abc'), Address(email='def'), Address(email='xyz')]) + ) + def test_nice_dependency_error(self): class User(Base): __tablename__ = 'users'