Version objects

Operation types

When changing entities and committing results into database Continuum saves the used operations (INSERT, UPDATE or DELETE) into version entities. The operation types are stored by default to a small integer field named ‘operation_type’. Class called ‘Operation’ holds convenient constants for these values as shown below:

from sqlalchemy_continuum import Operation

article = Article(name=u'Some article')
session.add(article)
session.commit()

article.versions[0].operation_type == Operation.INSERT

article.name = u'Some updated article'
session.commit()
article.versions[1].operation_type == Operation.UPDATE

session.delete(article)
session.commit()
article.versions[2].operation_type == Operation.DELETE

Version traversal

first_version = article.versions[0]
first_version.index
# 0


second_version = first_version.next
assert second_version == article.versions[1]

second_version.previous == first_version
# True

second_version.index
# 1

Changeset

Continuum provides easy way for getting the changeset of given version object. Each version contains a changeset property which holds a dict of changed fields in that version.

article = Article(name=u'New article', content=u'Some content')
session.add(article)
session.commit(article)

version = article.versions[0]
version.changeset
# {
#   'id': [None, 1],
#   'name': [None, u'New article'],
#   'content': [None, u'Some content']
# }
article.name = u'Updated article'
session.commit()

version = article.versions[1]
version.changeset
# {
#   'name': [u'New article', u'Updated article'],
# }

session.delete(article)
version = article.versions[1]
version.changeset
# {
#   'id': [1, None]
#   'name': [u'Updated article', None],
#   'content': [u'Some content', None]
# }

SQLAlchemy-Continuum also provides a utility function called changeset. With this function you can easily check the changeset of given object in current transaction.

from sqlalchemy_continuum import changeset


article = Article(name=u'Some article')
changeset(article)
# {'name': [u'Some article', None]}

Version relationships

Each version object reflects all parent object relationships. You can think version object relations as ‘relations of parent object in given point in time’.

Lets say you have two models: Article and Category. Each Article has one Category. In the following example we first add article and category objects into database.

Continuum saves new ArticleVersion and CategoryVersion records in the background. After that we update the created article entity to use another category. Continuum creates new version objects accordingly.

Lastly we check the category relations of different article versions.

category = Category(name=u'Some category')
article = Article(
    name=u'Some article',
    category=category
)
session.add(article)
session.commit()

article.category = Category(name=u'Some other category')
session.commit()


article.versions[0].category.name  # u'Some category'
article.versions[1].category.name  # u'Some other category'

The logic how SQLAlchemy-Continuum builds these relationships is within the RelationshipBuilder class.

Relationships to non-versioned classes

Let’s take previous example of Articles and Categories. Now consider that only Article model is versioned:

class Article(Base):
    __tablename__ = 'article'
    __versioned__ = {}

    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    name = sa.Column(sa.Unicode(255), nullable=False)


class Category(Base):
    __tablename__ = 'tag'

    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    name = sa.Column(sa.Unicode(255))
    article_id = sa.Column(sa.Integer, sa.ForeignKey(Article.id))
    article = sa.orm.relationship(
        Article,
        backref=sa.orm.backref('categories')
    )

Here Article versions will still reflect the relationships of Article model but they will simply return Category objects instead of CategoryVersion objects:

category = Category(name=u'Some category')
article = Article(
    name=u'Some article',
    category=category
)
session.add(article)
session.commit()

article.category = Category(name=u'Some other category')
session.commit()

version = article.versions[0]
version.category.name                   # u'Some other category'
isinstance(version.category, Category)  # True

Dynamic relationships

If the parent class has a dynamic relationship it will be reflected as a property which returns a query in the associated version class.

class Article(Base):
    __tablename__ = 'article'
    __versioned__ = {}

    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    name = sa.Column(sa.Unicode(255), nullable=False)


class Tag(Base):
    __tablename__ = 'tag'
    __versioned__ = {}

    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)
    name = sa.Column(sa.Unicode(255))
    article_id = sa.Column(sa.Integer, sa.ForeignKey(Article.id))
    article = sa.orm.relationship(
        Article,
        backref=sa.orm.backref(
            'tags',
            lazy='dynamic'
        )
    )

article = Article()
article.name = u'Some article'
article.content = u'Some content'
session.add(article)
session.commit()

tag_query = article.versions[0].tags
tag_query.all()  # return all tags for given version

tag_query.count()  # return the tag count for given version