examples.versioned_history.test_versioning — SQLAlchemy 1.2 Documentation

Release: 1.2.8 | Release Date: May 28, 2018

# SQLAlchemy 1.2 Documentation

# Source code for examples.versioned_history.test_versioning

    """Unit tests illustrating usage of the ``history_meta.py``
    module functions."""

    from unittest import TestCase
    from sqlalchemy.ext.declarative import declarative_base
    from .history_meta import Versioned, versioned_session
    from sqlalchemy import create_engine, Column, Integer, String, \
        ForeignKey, Boolean, select
    from sqlalchemy.orm import clear_mappers, Session, deferred, relationship, \
        column_property
    from sqlalchemy.testing import AssertsCompiledSQL, eq_, assert_raises, ne_
    from sqlalchemy.testing.entities import ComparableEntity
    from sqlalchemy.orm import exc as orm_exc
    import warnings

    warnings.simplefilter("error")

    engine = None

    def setup_module():
        global engine
        engine = create_engine('sqlite://', echo=True)

    class TestVersioning(TestCase, AssertsCompiledSQL):
        __dialect__ = 'default'

        def setUp(self):
            self.session = Session(engine)
            self.Base = declarative_base()
            versioned_session(self.session)

        def tearDown(self):
            self.session.close()
            clear_mappers()
            self.Base.metadata.drop_all(engine)

        def create_tables(self):
            self.Base.metadata.create_all(engine)

        def test_plain(self):
            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))

            self.create_tables()
            sess = self.session
            sc = SomeClass(name='sc1')
            sess.add(sc)
            sess.commit()

            sc.name = 'sc1modified'
            sess.commit()

            assert sc.version == 2

            SomeClassHistory = SomeClass.__history_mapper__.class_

            eq_(
                sess.query(SomeClassHistory).filter(
                    SomeClassHistory.version == 1).all(),
                [SomeClassHistory(version=1, name='sc1')]
            )

            sc.name = 'sc1modified2'

            eq_(
                sess.query(SomeClassHistory).order_by(
                    SomeClassHistory.version).all(),
                [
                    SomeClassHistory(version=1, name='sc1'),
                    SomeClassHistory(version=2, name='sc1modified')
                ]
            )

            assert sc.version == 3

            sess.commit()

            sc.name = 'temp'
            sc.name = 'sc1modified2'

            sess.commit()

            eq_(
                sess.query(SomeClassHistory).order_by(
                    SomeClassHistory.version).all(),
                [
                    SomeClassHistory(version=1, name='sc1'),
                    SomeClassHistory(version=2, name='sc1modified')
                ]
            )

            sess.delete(sc)
            sess.commit()

            eq_(
                sess.query(SomeClassHistory).order_by(
                    SomeClassHistory.version).all(),
                [
                    SomeClassHistory(version=1, name='sc1'),
                    SomeClassHistory(version=2, name='sc1modified'),
                    SomeClassHistory(version=3, name='sc1modified2')
                ]
            )

        def test_w_mapper_versioning(self):
            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))

            SomeClass.__mapper__.version_id_col = SomeClass.__table__.c.version

            self.create_tables()
            sess = self.session
            sc = SomeClass(name='sc1')
            sess.add(sc)
            sess.commit()

            s2 = Session(sess.bind)
            sc2 = s2.query(SomeClass).first()
            sc2.name = 'sc1modified'

            sc.name = 'sc1modified_again'
            sess.commit()

            eq_(sc.version, 2)

            assert_raises(
                orm_exc.StaleDataError,
                s2.flush
            )

        def test_from_null(self):
            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))

            self.create_tables()
            sess = self.session
            sc = SomeClass()
            sess.add(sc)
            sess.commit()

            sc.name = 'sc1'
            sess.commit()

            assert sc.version == 2

        def test_insert_null(self):
            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                boole = Column(Boolean, default=False)

            self.create_tables()
            sess = self.session
            sc = SomeClass(boole=True)
            sess.add(sc)
            sess.commit()

            sc.boole = None
            sess.commit()

            sc.boole = False
            sess.commit()

            SomeClassHistory = SomeClass.__history_mapper__.class_

            eq_(
                sess.query(SomeClassHistory.boole).order_by(
                    SomeClassHistory.id).all(),
                [(True, ), (None, )]
            )

            eq_(sc.version, 3)

        def test_deferred(self):
            """test versioning of unloaded, deferred columns."""

            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                data = deferred(Column(String(25)))

            self.create_tables()
            sess = self.session
            sc = SomeClass(name='sc1', data='somedata')
            sess.add(sc)
            sess.commit()
            sess.close()

            sc = sess.query(SomeClass).first()
            assert 'data' not in sc.__dict__

            sc.name = 'sc1modified'
            sess.commit()

            assert sc.version == 2

            SomeClassHistory = SomeClass.__history_mapper__.class_

            eq_(
                sess.query(SomeClassHistory).filter(
                    SomeClassHistory.version == 1).all(),
                [SomeClassHistory(version=1, name='sc1', data='somedata')]
            )

        def test_joined_inheritance(self):
            class BaseClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'basetable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                type = Column(String(20))

                __mapper_args__ = {
                    'polymorphic_on': type,
                    'polymorphic_identity': 'base'}

            class SubClassSeparatePk(BaseClass):
                __tablename__ = 'subtable1'

                id = column_property(
                    Column(Integer, primary_key=True),
                    BaseClass.id
                )
                base_id = Column(Integer, ForeignKey('basetable.id'))
                subdata1 = Column(String(50))

                __mapper_args__ = {'polymorphic_identity': 'sep'}

            class SubClassSamePk(BaseClass):
                __tablename__ = 'subtable2'

                id = Column(
                    Integer, ForeignKey('basetable.id'), primary_key=True)
                subdata2 = Column(String(50))

                __mapper_args__ = {'polymorphic_identity': 'same'}

            self.create_tables()
            sess = self.session

            sep1 = SubClassSeparatePk(name='sep1', subdata1='sep1subdata')
            base1 = BaseClass(name='base1')
            same1 = SubClassSamePk(name='same1', subdata2='same1subdata')
            sess.add_all([sep1, base1, same1])
            sess.commit()

            base1.name = 'base1mod'
            same1.subdata2 = 'same1subdatamod'
            sep1.name = 'sep1mod'
            sess.commit()

            BaseClassHistory = BaseClass.__history_mapper__.class_
            SubClassSeparatePkHistory = \
                SubClassSeparatePk.__history_mapper__.class_
            SubClassSamePkHistory = SubClassSamePk.__history_mapper__.class_
            eq_(
                sess.query(BaseClassHistory).order_by(BaseClassHistory.id).all(),
                [
                    SubClassSeparatePkHistory(
                        id=1, name='sep1', type='sep', version=1),
                    BaseClassHistory(id=2, name='base1', type='base', version=1),
                    SubClassSamePkHistory(
                        id=3, name='same1', type='same', version=1)
                ]
            )

            same1.subdata2 = 'same1subdatamod2'

            eq_(
                sess.query(BaseClassHistory).order_by(
                    BaseClassHistory.id, BaseClassHistory.version).all(),
                [
                    SubClassSeparatePkHistory(
                        id=1, name='sep1', type='sep', version=1),
                    BaseClassHistory(id=2, name='base1', type='base', version=1),
                    SubClassSamePkHistory(
                        id=3, name='same1', type='same', version=1),
                    SubClassSamePkHistory(
                        id=3, name='same1', type='same', version=2)
                ]
            )

            base1.name = 'base1mod2'
            eq_(
                sess.query(BaseClassHistory).order_by(
                    BaseClassHistory.id, BaseClassHistory.version).all(),
                [
                    SubClassSeparatePkHistory(
                        id=1, name='sep1', type='sep', version=1),
                    BaseClassHistory(id=2, name='base1', type='base', version=1),
                    BaseClassHistory(
                        id=2, name='base1mod', type='base', version=2),
                    SubClassSamePkHistory(
                        id=3, name='same1', type='same', version=1),
                    SubClassSamePkHistory(
                        id=3, name='same1', type='same', version=2)
                ]
            )

        def test_joined_inheritance_multilevel(self):
            class BaseClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'basetable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                type = Column(String(20))

                __mapper_args__ = {
                    'polymorphic_on': type,
                    'polymorphic_identity': 'base'}

            class SubClass(BaseClass):
                __tablename__ = 'subtable'

                id = column_property(
                    Column(Integer, primary_key=True),
                    BaseClass.id
                )
                base_id = Column(Integer, ForeignKey('basetable.id'))
                subdata1 = Column(String(50))

                __mapper_args__ = {'polymorphic_identity': 'sub'}

            class SubSubClass(SubClass):
                __tablename__ = 'subsubtable'

                id = Column(Integer, ForeignKey('subtable.id'), primary_key=True)
                subdata2 = Column(String(50))

                __mapper_args__ = {'polymorphic_identity': 'subsub'}

            self.create_tables()

            SubSubHistory = SubSubClass.__history_mapper__.class_
            sess = self.session
            q = sess.query(SubSubHistory)
            self.assert_compile(
                q,

                "SELECT "

                "subsubtable_history.id AS subsubtable_history_id, "
                "subtable_history.id AS subtable_history_id, "
                "basetable_history.id AS basetable_history_id, "

                "subsubtable_history.changed AS subsubtable_history_changed, "
                "subtable_history.changed AS subtable_history_changed, "
                "basetable_history.changed AS basetable_history_changed, "

                "basetable_history.name AS basetable_history_name, "

                "basetable_history.type AS basetable_history_type, "

                "subsubtable_history.version AS subsubtable_history_version, "
                "subtable_history.version AS subtable_history_version, "
                "basetable_history.version AS basetable_history_version, "

                "subtable_history.base_id AS subtable_history_base_id, "
                "subtable_history.subdata1 AS subtable_history_subdata1, "
                "subsubtable_history.subdata2 AS subsubtable_history_subdata2 "
                "FROM basetable_history "
                "JOIN subtable_history "
                "ON basetable_history.id = subtable_history.base_id "
                "AND basetable_history.version = subtable_history.version "
                "JOIN subsubtable_history ON subtable_history.id = "
                "subsubtable_history.id AND subtable_history.version = "
                "subsubtable_history.version"
            )

            ssc = SubSubClass(name='ss1', subdata1='sd1', subdata2='sd2')
            sess.add(ssc)
            sess.commit()
            eq_(
                sess.query(SubSubHistory).all(),
                []
            )
            ssc.subdata1 = 'sd11'
            ssc.subdata2 = 'sd22'
            sess.commit()
            eq_(
                sess.query(SubSubHistory).all(),
                [SubSubHistory(name='ss1', subdata1='sd1',
                                    subdata2='sd2', type='subsub', version=1)]
            )
            eq_(ssc, SubSubClass(
                name='ss1', subdata1='sd11',
                subdata2='sd22', version=2))

        def test_joined_inheritance_changed(self):
            class BaseClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'basetable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                type = Column(String(20))

                __mapper_args__ = {
                    'polymorphic_on': type,
                    'polymorphic_identity': 'base'
                }

            class SubClass(BaseClass):
                __tablename__ = 'subtable'

                id = Column(Integer, ForeignKey('basetable.id'), primary_key=True)

                __mapper_args__ = {'polymorphic_identity': 'sep'}

            self.create_tables()

            BaseClassHistory = BaseClass.__history_mapper__.class_
            SubClassHistory = SubClass.__history_mapper__.class_
            sess = self.session
            s1 = SubClass(name='s1')
            sess.add(s1)
            sess.commit()

            s1.name = 's2'
            sess.commit()

            actual_changed_base = sess.scalar(
                select([BaseClass.__history_mapper__.local_table.c.changed]))
            actual_changed_sub = sess.scalar(
                select([SubClass.__history_mapper__.local_table.c.changed]))
            h1 = sess.query(BaseClassHistory).first()
            eq_(h1.changed, actual_changed_base)
            eq_(h1.changed, actual_changed_sub)

            h1 = sess.query(SubClassHistory).first()
            eq_(h1.changed, actual_changed_base)
            eq_(h1.changed, actual_changed_sub)

        def test_single_inheritance(self):
            class BaseClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'basetable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                type = Column(String(50))
                __mapper_args__ = {
                    'polymorphic_on': type,
                    'polymorphic_identity': 'base'}

            class SubClass(BaseClass):

                subname = Column(String(50), unique=True)
                __mapper_args__ = {'polymorphic_identity': 'sub'}

            self.create_tables()
            sess = self.session

            b1 = BaseClass(name='b1')
            sc = SubClass(name='s1', subname='sc1')

            sess.add_all([b1, sc])

            sess.commit()

            b1.name = 'b1modified'

            BaseClassHistory = BaseClass.__history_mapper__.class_
            SubClassHistory = SubClass.__history_mapper__.class_

            eq_(
                sess.query(BaseClassHistory).order_by(
                    BaseClassHistory.id, BaseClassHistory.version).all(),
                [BaseClassHistory(id=1, name='b1', type='base', version=1)]
            )

            sc.name = 's1modified'
            b1.name = 'b1modified2'

            eq_(
                sess.query(BaseClassHistory).order_by(
                    BaseClassHistory.id, BaseClassHistory.version).all(),
                [
                    BaseClassHistory(id=1, name='b1', type='base', version=1),
                    BaseClassHistory(
                        id=1, name='b1modified', type='base', version=2),
                    SubClassHistory(id=2, name='s1', type='sub', version=1)
                ]
            )

            # test the unique constraint on the subclass
            # column
            sc.name = "modifyagain"
            sess.flush()

        def test_unique(self):
            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50), unique=True)
                data = Column(String(50))

            self.create_tables()
            sess = self.session
            sc = SomeClass(name='sc1', data='sc1')
            sess.add(sc)
            sess.commit()

            sc.data = 'sc1modified'
            sess.commit()

            assert sc.version == 2

            sc.data = 'sc1modified2'
            sess.commit()

            assert sc.version == 3

        def test_relationship(self):

            class SomeRelated(self.Base, ComparableEntity):
                __tablename__ = 'somerelated'

                id = Column(Integer, primary_key=True)

            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                related_id = Column(Integer, ForeignKey('somerelated.id'))
                related = relationship("SomeRelated", backref='classes')

            SomeClassHistory = SomeClass.__history_mapper__.class_

            self.create_tables()
            sess = self.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

        def test_backref_relationship(self):

            class SomeRelated(self.Base, ComparableEntity):
                __tablename__ = 'somerelated'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))
                related_id = Column(Integer, ForeignKey('sometable.id'))
                related = relationship("SomeClass", backref='related')

            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)

            self.create_tables()
            sess = self.session
            sc = SomeClass()
            sess.add(sc)
            sess.commit()

            assert sc.version == 1

            sr = SomeRelated(name='sr', related=sc)
            sess.add(sr)
            sess.commit()

            assert sc.version == 1

            sr.name = 'sr2'
            sess.commit()

            assert sc.version == 1

            sess.delete(sr)
            sess.commit()

            assert sc.version == 1

        def test_create_double_flush(self):

            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(30))
                other = Column(String(30))

            self.create_tables()

            sc = SomeClass()
            self.session.add(sc)
            self.session.flush()
            sc.name = 'Foo'
            self.session.flush()

            assert sc.version == 2

        def test_mutate_plain_column(self):
            class Document(self.Base, Versioned):
                __tablename__ = 'document'
                id = Column(Integer, primary_key=True, autoincrement=True)
                name = Column(String, nullable=True)
                description_ = Column('description', String, nullable=True)

            self.create_tables()

            document = Document()
            self.session.add(document)
            document.name = 'Foo'
            self.session.commit()
            document.name = 'Bar'
            self.session.commit()

            DocumentHistory = Document.__history_mapper__.class_
            v2 = self.session.query(Document).one()
            v1 = self.session.query(DocumentHistory).one()
            self.assertEqual(v1.id, v2.id)
            self.assertEqual(v2.name, 'Bar')
            self.assertEqual(v1.name, 'Foo')

        def test_mutate_named_column(self):
            class Document(self.Base, Versioned):
                __tablename__ = 'document'
                id = Column(Integer, primary_key=True, autoincrement=True)
                name = Column(String, nullable=True)
                description_ = Column('description', String, nullable=True)

            self.create_tables()

            document = Document()
            self.session.add(document)
            document.description_ = 'Foo'
            self.session.commit()
            document.description_ = 'Bar'
            self.session.commit()

            DocumentHistory = Document.__history_mapper__.class_
            v2 = self.session.query(Document).one()
            v1 = self.session.query(DocumentHistory).one()
            self.assertEqual(v1.id, v2.id)
            self.assertEqual(v2.description_, 'Bar')
            self.assertEqual(v1.description_, 'Foo')

        def test_unique_identifiers_across_deletes(self):
            """Ensure unique integer values are used for the primary table.

            Checks whether the database assigns the same identifier twice
            within the span of a table.  SQLite will do this if
            sqlite_autoincrement is not set (e.g. SQLite's AUTOINCREMENT flag).

            """

            class SomeClass(Versioned, self.Base, ComparableEntity):
                __tablename__ = 'sometable'

                id = Column(Integer, primary_key=True)
                name = Column(String(50))

            self.create_tables()
            sess = self.session
            sc = SomeClass(name='sc1')
            sess.add(sc)
            sess.commit()

            sess.delete(sc)
            sess.commit()

            sc2 = SomeClass(name='sc2')
            sess.add(sc2)
            sess.commit()

            SomeClassHistory = SomeClass.__history_mapper__.class_

            # only one entry should exist in the history table; one()
            # ensures that
            scdeleted = sess.query(SomeClassHistory).one()

            # If sc2 has the same id that deleted sc1 had,
            # it will fail when modified or deleted
            # because of the violation of the uniqueness of the primary key on
            # sometable_history
            ne_(sc2.id, scdeleted.id)

            # If previous assertion fails, this will also fail:
            sc2.name = 'sc2 modified'
            sess.commit()