From 4888c89ce53b35994f2d8b4a1116fd9d7c2a5804 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Tue, 1 Sep 2009 22:26:23 +0000 Subject: [PATCH] - A column can be added to a joined-table subclass after the class has been constructed (i.e. via class-level attribute assignment). The column is added to the underlying Table as always, but now the mapper will rebuild its "join" to include the new column, instead of raising an error about "no such column, use column_property() instead". [ticket:1523] - added an additional test in test_mappers for "added nonexistent column", even though this test is already in test_query its more appropriate within "mapper configuration" tests. --- CHANGES | 8 ++++++++ lib/sqlalchemy/orm/mapper.py | 13 ++++++++++--- test/ext/test_declarative.py | 27 +++++++++++++++++++++++++++ test/orm/test_mapper.py | 6 ++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 6d680cfa80..f8786a1d0a 100644 --- a/CHANGES +++ b/CHANGES @@ -479,6 +479,14 @@ CHANGES __table_args__ is passed as a tuple with no dict argument. Improved documentation. [ticket:1468] + - A column can be added to a joined-table subclass after + the class has been constructed (i.e. via class-level + attribute assignment). The column is added to the underlying + Table as always, but now the mapper will rebuild its + "join" to include the new column, instead of raising + an error about "no such column, use column_property() + instead". [ticket:1523] + - test - Added examples into the test suite so they get exercised regularly and cleaned up a couple deprecation warnings. diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 28565bd82f..907fcd5c3a 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -549,9 +549,16 @@ class Mapper(object): for c in columns: mc = self.mapped_table.corresponding_column(c) if not mc: - raise sa_exc.ArgumentError("Column '%s' is not represented in mapper's table. " - "Use the `column_property()` function to force this column " - "to be mapped as a read-only attribute." % c) + mc = self.local_table.corresponding_column(c) + if mc: + # if the column is in the local table but not the mapped table, + # this corresponds to adding a column after the fact to the local table. + self.mapped_table._reset_exported() + mc = self.mapped_table.corresponding_column(c) + if not mc: + raise sa_exc.ArgumentError("Column '%s' is not represented in mapper's table. " + "Use the `column_property()` function to force this column " + "to be mapped as a read-only attribute." % c) mapped_column.append(mc) prop = ColumnProperty(*mapped_column) else: diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py index 63beb54e7a..b64ad23e1c 100644 --- a/test/ext/test_declarative.py +++ b/test/ext/test_declarative.py @@ -956,7 +956,34 @@ class DeclarativeInheritanceTest(DeclarativeTestBase): def go(): assert sess.query(Person).filter(Manager.name=='dogbert').one().id self.assert_sql_count(testing.db, go, 1) + + def test_add_subcol_after_the_fact(self): + class Person(Base, ComparableEntity): + __tablename__ = 'people' + id = Column('id', Integer, primary_key=True, test_needs_autoincrement=True) + name = Column('name', String(50)) + discriminator = Column('type', String(50)) + __mapper_args__ = {'polymorphic_on':discriminator} + class Engineer(Person): + __tablename__ = 'engineers' + __mapper_args__ = {'polymorphic_identity':'engineer'} + id = Column('id', Integer, ForeignKey('people.id'), primary_key=True) + + Engineer.primary_language = Column('primary_language', String(50)) + + Base.metadata.create_all() + + sess = create_session() + e1 = Engineer(primary_language='java', name='dilbert') + sess.add(e1) + sess.flush() + sess.expunge_all() + + eq_(sess.query(Person).first(), + Engineer(primary_language='java', name='dilbert') + ) + def test_subclass_mixin(self): class Person(Base, ComparableEntity): __tablename__ = 'people' diff --git a/test/orm/test_mapper.py b/test/orm/test_mapper.py index 250992049f..146baebd24 100644 --- a/test/orm/test_mapper.py +++ b/test/orm/test_mapper.py @@ -158,6 +158,12 @@ class MapperTest(_fixtures.FixtureTest): assert 'email_addres' not in a.__dict__ eq_(a.email_address, 'jack@bean.com') + @testing.resolve_artifact_names + def test_column_not_present(self): + assert_raises_message(sa.exc.ArgumentError, "not represented in mapper's table", mapper, User, users, properties={ + 'foo':addresses.c.user_id + }) + @testing.resolve_artifact_names def test_bad_constructor(self): """If the construction of a mapped class fails, the instance does not get placed in the session""" -- 2.47.3