From: Mike Bayer Date: Mon, 20 Dec 2010 15:51:24 +0000 (-0500) Subject: - merge versioning example fix from 0.6 X-Git-Tag: rel_0_7b1~141 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=09534eaf9aa9f86f0f75d440e70ec199de69943c;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - merge versioning example fix from 0.6 --- diff --git a/CHANGES b/CHANGES index c4ef9e1a94..300b6b4bb7 100644 --- a/CHANGES +++ b/CHANGES @@ -171,6 +171,10 @@ CHANGES internal "cache" dictionary. Particularly since the join() and select() objects are created in the method itself this was pretty much a pure memory leaking behavior. + +- examples + - The versioning example now supports detection of changes + in an associated relationship(). 0.6.5 ===== diff --git a/examples/versioning/history_meta.py b/examples/versioning/history_meta.py index 0a631e8492..fa95733e29 100644 --- a/examples/versioning/history_meta.py +++ b/examples/versioning/history_meta.py @@ -3,6 +3,7 @@ from sqlalchemy.orm import mapper, class_mapper, attributes, object_mapper from sqlalchemy.orm.exc import UnmappedClassError, UnmappedColumnError from sqlalchemy import Table, Column, ForeignKeyConstraint, Integer from sqlalchemy.orm.interfaces import SessionExtension +from sqlalchemy.orm.properties import RelationshipProperty def col_references_table(col, table): for fk in col.foreign_keys: @@ -147,7 +148,16 @@ def create_version(obj, session, deleted = False): # if the attribute had no value. attr[hist_col.key] = a[0] obj_changed = True - + + if not obj_changed: + # not changed, but we have relationships. OK + # check those too + for prop in obj_mapper.iterate_properties: + if isinstance(prop, RelationshipProperty) and \ + attributes.get_history(obj, prop.key).has_changes(): + obj_changed = True + break + if not obj_changed and not deleted: return diff --git a/examples/versioning/test_versioning.py b/examples/versioning/test_versioning.py index ed88be6e79..c9cb605cd1 100644 --- a/examples/versioning/test_versioning.py +++ b/examples/versioning/test_versioning.py @@ -1,9 +1,9 @@ from sqlalchemy.ext.declarative import declarative_base from history_meta import VersionedMeta, VersionedListener from sqlalchemy import create_engine, Column, Integer, String, ForeignKey -from sqlalchemy.orm import clear_mappers, sessionmaker, deferred -from sqlalchemy.test.testing import TestBase, eq_ -from sqlalchemy.test.entities import ComparableEntity +from sqlalchemy.orm import clear_mappers, sessionmaker, deferred, relationship +from test.lib.testing import TestBase, eq_ +from test.lib.entities import ComparableEntity def setup(): global engine @@ -11,8 +11,11 @@ def setup(): class TestVersioning(TestBase): def setup(self): - global Base, Session - Base = declarative_base(metaclass=VersionedMeta, bind=engine) + global Base, Session, Versioned + Base = declarative_base(bind=engine) + class Versioned(object): + __metaclass__ = VersionedMeta + _decl_class_registry = Base._decl_class_registry Session = sessionmaker(extension=VersionedListener()) def teardown(self): @@ -23,7 +26,7 @@ class TestVersioning(TestBase): Base.metadata.create_all() def test_plain(self): - class SomeClass(Base, ComparableEntity): + class SomeClass(Versioned, Base, ComparableEntity): __tablename__ = 'sometable' id = Column(Integer, primary_key=True) @@ -87,7 +90,7 @@ class TestVersioning(TestBase): ) def test_from_null(self): - class SomeClass(Base, ComparableEntity): + class SomeClass(Versioned, Base, ComparableEntity): __tablename__ = 'sometable' id = Column(Integer, primary_key=True) @@ -107,7 +110,7 @@ class TestVersioning(TestBase): def test_deferred(self): """test versioning of unloaded, deferred columns.""" - class SomeClass(Base, ComparableEntity): + class SomeClass(Versioned, Base, ComparableEntity): __tablename__ = 'sometable' id = Column(Integer, primary_key=True) @@ -138,7 +141,7 @@ class TestVersioning(TestBase): def test_joined_inheritance(self): - class BaseClass(Base, ComparableEntity): + class BaseClass(Versioned, Base, ComparableEntity): __tablename__ = 'basetable' id = Column(Integer, primary_key=True) @@ -215,7 +218,7 @@ class TestVersioning(TestBase): ) def test_single_inheritance(self): - class BaseClass(Base, ComparableEntity): + class BaseClass(Versioned, Base, ComparableEntity): __tablename__ = 'basetable' id = Column(Integer, primary_key=True) @@ -261,7 +264,7 @@ class TestVersioning(TestBase): ) def test_unique(self): - class SomeClass(Base, ComparableEntity): + class SomeClass(Versioned, Base, ComparableEntity): __tablename__ = 'sometable' id = Column(Integer, primary_key=True) @@ -284,3 +287,51 @@ class TestVersioning(TestBase): assert sc.version == 3 + def test_relationship(self): + + class SomeRelated(Base, ComparableEntity): + __tablename__ = 'somerelated' + + id = Column(Integer, primary_key=True) + + class SomeClass(Versioned, Base, ComparableEntity): + __tablename__ = 'sometable' + + id = Column(Integer, primary_key=True) + name = Column(String(50)) + related_id = Column(Integer, ForeignKey('somerelated.id')) + related = relationship("SomeRelated") + + SomeClassHistory = SomeClass.__history_mapper__.class_ + + self.create_tables() + sess = Session() + sc = SomeClass(name='sc1') + sess.add(sc) + sess.commit() + + assert sc.version == 1 + + sr1 = SomeRelated() + sc.related = sr1 + sess.commit() + + assert sc.version == 2 + + eq_( + sess.query(SomeClassHistory).filter(SomeClassHistory.version == 1).all(), + [SomeClassHistory(version=1, name='sc1', related_id=None)] + ) + + sc.related = None + + eq_( + sess.query(SomeClassHistory).order_by(SomeClassHistory.version).all(), + [ + SomeClassHistory(version=1, name='sc1', related_id=None), + SomeClassHistory(version=2, name='sc1', related_id=sr1.id) + ] + ) + + assert sc.version == 3 + diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index 0457efc1f7..667e69190e 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -269,6 +269,16 @@ class AttributeImpl(object): self.expire_missing = expire_missing + def _get_active_history(self): + """Backwards compat for impl.active_history""" + + return self.dispatch.active_history + + def _set_active_history(self, value): + self.dispatch.active_history = value + + active_history = property(_get_active_history, _set_active_history) + def hasparent(self, state, optimistic=False): """Return the boolean value of a `hasparent` flag attached to