Utilities

changeset(obj)

Return a humanized changeset for given SQLAlchemy declarative object. With this function you can easily check the changeset of given object in current transaction.

Parameters:
  • obj
Returns:
  • this function you can easily check the changeset of given object in current transaction.

sqlalchemy_history/utils.py
def changeset(obj):
    """
    Return a humanized changeset for given SQLAlchemy declarative object. With
    this function you can easily check the changeset of given object in current
    transaction.

    :param obj:
    :returns: this function you can easily check the changeset of given object in current
    transaction.

    """
    data = {}
    session = sa.orm.object_session(obj)
    if session and obj in session.deleted:
        columns = [c for c in sa.inspect(obj.__class__).columns.values() if is_table_column(c)]

        for column in columns:
            if not column.primary_key:
                value = getattr(obj, column.key)
                if value is not None:
                    data[column.key] = [None, getattr(obj, column.key)]
    else:
        for prop in obj.__mapper__.iterate_properties:
            history = get_history(obj, prop.key)
            if history.has_changes():
                old_value = history.deleted[0] if history.deleted else None
                new_value = history.added[0] if history.added else None

                if new_value:
                    data[prop.key] = [new_value, old_value]
    return data

count_versions(obj)

Return the number of versions given object has. This function works even when obj has create_models and create_tables versioned settings

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • when obj has create_models and create_tables versioned settings disabled.

sqlalchemy_history/utils.py
def count_versions(obj):
    """
    Return the number of versions given object has. This function works even
    when obj has `create_models` and `create_tables` versioned settings


    :param obj: SQLAlchemy declarative model object
    :returns: when obj has `create_models` and `create_tables` versioned settings
    disabled.


    """
    session = sa.orm.object_session(obj)
    if session is None:
        # If object is transient, we assume it has no version history.
        return 0
    manager = get_versioning_manager(obj)
    table_name = manager.option(obj, "table_name") % obj.__table__.name
    criteria = ["%s = %r" % (pk, getattr(obj, pk)) for pk in get_primary_keys(obj)]
    query = sa.text("SELECT COUNT(1) FROM %s WHERE %s" % (table_name, " AND ".join(criteria)))
    return session.execute(query).scalar()

get_association_proxies(klass)

Get Association proxy mappings for ORM Models

sqlalchemy_history/utils.py
def get_association_proxies(klass):
    """Get Association proxy mappings for ORM Models"""
    # NOTE: Ideally this method we should try to move it to sqlalchemy-utils
    # if they are ok with it as they provide a similar method to detect and
    # provide hypbrid properties.
    # ref: https://github.com/kvesteri/sqlalchemy-utils/issues/679
    association_proxy_mapping = {}
    for key, prop in sa.inspect(klass).all_orm_descriptors.items():
        if isinstance(prop, sa.ext.associationproxy.AssociationProxy):
            association_proxy_mapping[key] = prop
    return association_proxy_mapping

get_versioning_manager(item)

Return the associated SQLAlchemy-History VersioningManager for given SQLAlchemy declarative model class or object or table.

Parameters:
  • item

    An item from SQLAlchemy

  • A

    declarative ORM object

  • An

    instance of a SQL table

Returns:
  • SQLAlchemy declarative model class or object or table.

sqlalchemy_history/utils.py
def get_versioning_manager(item):
    """
    Return the associated SQLAlchemy-History VersioningManager for given
    SQLAlchemy declarative model class or object or table.

    :param item: An item from SQLAlchemy
    :param A: declarative ORM object
    :param A: declarative ORM class
    :param An: instance of a SQL table
    :returns: SQLAlchemy declarative model class or object or table.

    """
    # The ORM class or SQL table on which versioning was enabled
    versioned_item = None
    if isclass(item):
        versioned_item = item
    else:
        if isinstance(item, AliasedClass):
            versioned_item = sa.inspect(item).mapper.class_
        elif isinstance(item, sa.Table):
            versioned_item = item
        else:
            versioned_item = item.__class__

    try:
        return versioned_item.__versioning_manager__
    except AttributeError:
        if isinstance(versioned_item, sa.Table):
            raise TableNotVersioned('Table "%s"' % versioned_item.name)
        else:
            raise ClassNotVersioned(versioned_item.__name__)

is_internal_column(model, column_name)

Return whether or not given column of given SQLAlchemy declarative classs is considered an internal column (a column whose purpose is mainly for SQLA-History's internal use).

Parameters:
  • version_obj

    SQLAlchemy declarative class

  • column_name

    Name of the column

  • model
Returns:
  • Bool

sqlalchemy_history/utils.py
def is_internal_column(model, column_name):
    """
    Return whether or not given column of given SQLAlchemy declarative classs
    is considered an internal column (a column whose purpose is mainly
    for SQLA-History's internal use).

    :param version_obj: SQLAlchemy declarative class
    :param column_name: Name of the column
    :param model:
    :returns: Bool

    """
    return column_name in (
        option(model, "transaction_column_name"),
        option(model, "end_transaction_column_name"),
        option(model, "operation_type_column_name"),
    )

is_modified(obj)

Return whether or not the versioned properties of given object have been modified.

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • modified.

sqlalchemy_history/utils.py
def is_modified(obj):
    """
    Return whether or not the versioned properties of given object have been
    modified.

    :param obj: SQLAlchemy declarative model object
    :returns: modified.

    """
    column_names = sa.inspect(obj.__class__).columns.keys()
    versioned_column_keys = [prop.key for prop in versioned_column_properties(obj)]
    versioned_relationship_keys = [prop.key for prop in versioned_relationships(obj, versioned_column_keys)]
    for key, attr in sa.inspect(obj).attrs.items():
        if key in column_names:
            if key not in versioned_column_keys:
                continue
            if attr.history.has_changes():
                return True
        if key in versioned_relationship_keys:
            if attr.history.has_changes():
                return True
    return False

is_modified_or_deleted(obj)

Return whether or not some of the versioned properties of given SQLAlchemy declarative object have been modified or if the object has been deleted.

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • Bool

sqlalchemy_history/utils.py
def is_modified_or_deleted(obj):
    """
    Return whether or not some of the versioned properties of given SQLAlchemy
    declarative object have been modified or if the object has been deleted.

    :param obj: SQLAlchemy declarative model object
    :returns: Bool

    """
    session = sa.orm.object_session(obj)
    return is_versioned(obj) and (is_modified(obj) or obj in chain(session.deleted, session.new))

is_session_modified(session)

Return whether or not any of the versioned objects in given session have been either modified or deleted.

Parameters:
  • session

    SQLAlchemy session object

Returns:
  • Bool

sqlalchemy_history/utils.py
def is_session_modified(session):
    """Return whether or not any of the versioned objects in given session have
    been either modified or deleted.

    :param session: SQLAlchemy session object
    :returns: Bool

    """
    return any(is_modified_or_deleted(obj) for obj in versioned_objects(session))

is_table_column(column)

Return wheter of not give field is a column over the database table.

Parameters:
  • column

    SQLAclhemy model field

Returns:
  • Bool

sqlalchemy_history/utils.py
def is_table_column(column):
    """
    Return wheter of not give field is a column over the database table.

    :param column: SQLAclhemy model field
    :returns: Bool

    """
    return isinstance(column, sa.Column)

is_versioned(obj_or_class)

Return whether or not given object is versioned.

Parameters:
  • obj_or_class
  • SQLAlchemy

    declarative model object or SQLAlchemy declarative model

  • class
  • seealso

    func

Returns:
sqlalchemy_history/utils.py
def is_versioned(obj_or_class):
    """
    Return whether or not given object is versioned.

    :param obj_or_class:
    :param SQLAlchemy: declarative model object or SQLAlchemy declarative model
    :param class:
    :param seealso: func
    :returns:
    """
    try:
        return hasattr(obj_or_class, "__versioned__") and get_versioning_manager(obj_or_class).option(
            obj_or_class, "versioning"
        )
    except ClassNotVersioned:
        return False

option(obj_or_class, option_name)

Return the option value of given option for given versioned object or class.

Parameters:
  • obj_or_class

    SQLAlchemy declarative model object or class

  • option_name

    The name of an option to return

sqlalchemy_history/utils.py
def option(obj_or_class, option_name):
    """Return the option value of given option for given versioned object or class.

    :param obj_or_class: SQLAlchemy declarative model object or class
    :param option_name: The name of an option to return

    """
    if isinstance(obj_or_class, AliasedClass):
        obj_or_class = sa.inspect(obj_or_class).mapper.class_
    cls = obj_or_class if isclass(obj_or_class) else obj_or_class.__class__
    if not hasattr(cls, "__versioned__"):
        cls = parent_class(cls)
    return get_versioning_manager(cls).option(cls, option_name)

parent_class(version_cls)

Return the parent class for given version model class.

Parameters:
  • model

    SQLAlchemy declarative version model class

  • version_cls
Returns:
sqlalchemy_history/utils.py
def parent_class(version_cls):
    """
    Return the parent class for given version model class.

    :param model: SQLAlchemy declarative version model class
    :param version_cls:
    :returns:
    """
    manager = get_versioning_manager(version_cls)
    try:
        return next((k for k, v in manager.version_class_map.items() if v == version_cls))
    except StopIteration:
        # Should raise Key Error if we can't find the parent_object of a orphaned versioned_model
        raise KeyError(version_cls)

parent_table(version_table)

Return corresponding parent table for any given parent table.

Parameters:
  • version_table

    A versioned table table which could be either association_table or model_table.

sqlalchemy_history/utils.py
def parent_table(version_table):
    """
    Return corresponding parent table for any given parent table.

    :param version_table: A versioned table table which could be either association_table or model_table.
    """
    manager = get_versioning_manager(version_table)
    try:
        return next((k for k, v in manager.version_table_map.items() if v == version_table))
    except StopIteration:
        # Raise Key error as we couldn't find parent_object of versioned_object
        raise KeyError(version_table)

transaction_class(cls)

Return the associated transaction class for given versioned SQLAlchemy declarative class or version class.

Parameters:
  • cls

    SQLAlchemy versioned declarative class or version model class

Returns:
  • declarative class or version class.

sqlalchemy_history/utils.py
def transaction_class(cls):
    """
    Return the associated transaction class for given versioned SQLAlchemy
    declarative class or version class.

    :param cls: SQLAlchemy versioned declarative class or version model class
    :returns: declarative class or version class.
    """
    return get_versioning_manager(cls).transaction_cls

vacuum(session, model, yield_per=1000)

When making structural changes to version tables (for example dropping columns) there are sometimes situations where some old version records become futile.

Vacuum deletes all futile version rows which had no changes compared to previous version.

Parameters:
  • session

    SQLAlchemy session object

  • model

    SQLAlchemy declarative model class

  • yield_per

    how many rows to process at a time (Default value = 1000)

sqlalchemy_history/utils.py
def vacuum(session, model, yield_per=1000):
    """When making structural changes to version tables (for example dropping
    columns) there are sometimes situations where some old version records
    become futile.

    Vacuum deletes all futile version rows which had no changes compared to
    previous version.


    :param session: SQLAlchemy session object
    :param model: SQLAlchemy declarative model class
    :param yield_per: how many rows to process at a time (Default value = 1000)
    """
    version_cls = version_class(model)
    versions = defaultdict(list)

    query = (session.query(version_cls).order_by(option(version_cls, "transaction_column_name"))).yield_per(
        yield_per
    )

    primary_key_col = sa.inspection.inspect(model).primary_key[0].name

    for version in query:
        version_id = getattr(version, primary_key_col)
        if versions[version_id]:
            prev_version = versions[version_id][-1]
            if naturally_equivalent(prev_version, version):
                session.delete(version)
        else:
            versions[version_id].append(version)

version_class(model)

Return the version class for given SQLAlchemy declarative model class.

Parameters:
  • model

    SQLAlchemy declarative model class

Returns:
sqlalchemy_history/utils.py
def version_class(model):
    """
    Return the version class for given SQLAlchemy declarative model class.

    :param model: SQLAlchemy declarative model class
    :returns:
    """
    manager = get_versioning_manager(model)
    return manager.version_class_map.get(model, None)

version_table(table)

Return associated version table for given SQLAlchemy Table object.

Parameters:
  • table

    SQLAlchemy Table object

sqlalchemy_history/utils.py
def version_table(table):
    """
    Return associated version table for given SQLAlchemy Table object.

    :param table: SQLAlchemy Table object

    """
    manager = get_versioning_manager(table)
    return manager.version_table_map.get(table, None)

versioned_column_properties(obj_or_class)

Parameters:
  • obj

    SQLAlchemy declarative model object

  • obj_or_class
Returns:
  • declarative model object.

sqlalchemy_history/utils.py
def versioned_column_properties(obj_or_class):
    """

    :param obj: SQLAlchemy declarative model object
    :param obj_or_class:
    :returns: declarative model object.

    """
    manager = get_versioning_manager(obj_or_class)

    cls = obj_or_class if isclass(obj_or_class) else obj_or_class.__class__

    mapper = sa.inspect(cls)
    for key, column in mapper.columns.items():
        # Ignores non table columns
        if not is_table_column(column):
            continue

        if not manager.is_excluded_property(obj_or_class, key):
            yield getattr(mapper.attrs, key)

versioned_objects(session)

Return all versioned objects in given session.

Parameters:
  • session

    SQLAlchemy session object

sqlalchemy_history/utils.py
def versioned_objects(session):
    """
    Return all versioned objects in given session.

    :param session: SQLAlchemy session object

    """
    for obj in session:
        if is_versioned(obj):
            yield obj

versioned_relationships(obj, versioned_column_keys)

Parameters:
  • obj

    SQLAlchemy declarative model object

  • versioned_column_keys
Returns:
  • declarative model object.

sqlalchemy_history/utils.py
def versioned_relationships(obj, versioned_column_keys):
    """

    :param obj: SQLAlchemy declarative model object
    :param versioned_column_keys:
    :returns: declarative model object.

    """
    for prop in sa.inspect(obj.__class__).relationships:
        if any(c.key in versioned_column_keys for c in prop.local_columns):
            yield prop

changeset

Return a humanized changeset for given SQLAlchemy declarative object. With this function you can easily check the changeset of given object in current transaction.

Parameters:
  • obj
Returns:
  • this function you can easily check the changeset of given object in current transaction.

sqlalchemy_history/utils.py
def changeset(obj):
    """
    Return a humanized changeset for given SQLAlchemy declarative object. With
    this function you can easily check the changeset of given object in current
    transaction.

    :param obj:
    :returns: this function you can easily check the changeset of given object in current
    transaction.

    """
    data = {}
    session = sa.orm.object_session(obj)
    if session and obj in session.deleted:
        columns = [c for c in sa.inspect(obj.__class__).columns.values() if is_table_column(c)]

        for column in columns:
            if not column.primary_key:
                value = getattr(obj, column.key)
                if value is not None:
                    data[column.key] = [None, getattr(obj, column.key)]
    else:
        for prop in obj.__mapper__.iterate_properties:
            history = get_history(obj, prop.key)
            if history.has_changes():
                old_value = history.deleted[0] if history.deleted else None
                new_value = history.added[0] if history.added else None

                if new_value:
                    data[prop.key] = [new_value, old_value]
    return data

count_versions

Return the number of versions given object has. This function works even when obj has create_models and create_tables versioned settings

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • when obj has create_models and create_tables versioned settings disabled.

sqlalchemy_history/utils.py
def count_versions(obj):
    """
    Return the number of versions given object has. This function works even
    when obj has `create_models` and `create_tables` versioned settings


    :param obj: SQLAlchemy declarative model object
    :returns: when obj has `create_models` and `create_tables` versioned settings
    disabled.


    """
    session = sa.orm.object_session(obj)
    if session is None:
        # If object is transient, we assume it has no version history.
        return 0
    manager = get_versioning_manager(obj)
    table_name = manager.option(obj, "table_name") % obj.__table__.name
    criteria = ["%s = %r" % (pk, getattr(obj, pk)) for pk in get_primary_keys(obj)]
    query = sa.text("SELECT COUNT(1) FROM %s WHERE %s" % (table_name, " AND ".join(criteria)))
    return session.execute(query).scalar()

get_versioning_manager

Return the associated SQLAlchemy-History VersioningManager for given SQLAlchemy declarative model class or object or table.

Parameters:
  • item

    An item from SQLAlchemy

  • A

    declarative ORM object

  • An

    instance of a SQL table

Returns:
  • SQLAlchemy declarative model class or object or table.

sqlalchemy_history/utils.py
def get_versioning_manager(item):
    """
    Return the associated SQLAlchemy-History VersioningManager for given
    SQLAlchemy declarative model class or object or table.

    :param item: An item from SQLAlchemy
    :param A: declarative ORM object
    :param A: declarative ORM class
    :param An: instance of a SQL table
    :returns: SQLAlchemy declarative model class or object or table.

    """
    # The ORM class or SQL table on which versioning was enabled
    versioned_item = None
    if isclass(item):
        versioned_item = item
    else:
        if isinstance(item, AliasedClass):
            versioned_item = sa.inspect(item).mapper.class_
        elif isinstance(item, sa.Table):
            versioned_item = item
        else:
            versioned_item = item.__class__

    try:
        return versioned_item.__versioning_manager__
    except AttributeError:
        if isinstance(versioned_item, sa.Table):
            raise TableNotVersioned('Table "%s"' % versioned_item.name)
        else:
            raise ClassNotVersioned(versioned_item.__name__)

is_modified

Return whether or not the versioned properties of given object have been modified.

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • modified.

sqlalchemy_history/utils.py
def is_modified(obj):
    """
    Return whether or not the versioned properties of given object have been
    modified.

    :param obj: SQLAlchemy declarative model object
    :returns: modified.

    """
    column_names = sa.inspect(obj.__class__).columns.keys()
    versioned_column_keys = [prop.key for prop in versioned_column_properties(obj)]
    versioned_relationship_keys = [prop.key for prop in versioned_relationships(obj, versioned_column_keys)]
    for key, attr in sa.inspect(obj).attrs.items():
        if key in column_names:
            if key not in versioned_column_keys:
                continue
            if attr.history.has_changes():
                return True
        if key in versioned_relationship_keys:
            if attr.history.has_changes():
                return True
    return False

is_modified_or_deleted

Return whether or not some of the versioned properties of given SQLAlchemy declarative object have been modified or if the object has been deleted.

Parameters:
  • obj

    SQLAlchemy declarative model object

Returns:
  • Bool

sqlalchemy_history/utils.py
def is_modified_or_deleted(obj):
    """
    Return whether or not some of the versioned properties of given SQLAlchemy
    declarative object have been modified or if the object has been deleted.

    :param obj: SQLAlchemy declarative model object
    :returns: Bool

    """
    session = sa.orm.object_session(obj)
    return is_versioned(obj) and (is_modified(obj) or obj in chain(session.deleted, session.new))

is_session_modified

Return whether or not any of the versioned objects in given session have been either modified or deleted.

Parameters:
  • session

    SQLAlchemy session object

Returns:
  • Bool

sqlalchemy_history/utils.py
def is_session_modified(session):
    """Return whether or not any of the versioned objects in given session have
    been either modified or deleted.

    :param session: SQLAlchemy session object
    :returns: Bool

    """
    return any(is_modified_or_deleted(obj) for obj in versioned_objects(session))

is_versioned

Return whether or not given object is versioned.

Parameters:
  • obj_or_class
  • SQLAlchemy

    declarative model object or SQLAlchemy declarative model

  • class
  • seealso

    func

Returns:
sqlalchemy_history/utils.py
def is_versioned(obj_or_class):
    """
    Return whether or not given object is versioned.

    :param obj_or_class:
    :param SQLAlchemy: declarative model object or SQLAlchemy declarative model
    :param class:
    :param seealso: func
    :returns:
    """
    try:
        return hasattr(obj_or_class, "__versioned__") and get_versioning_manager(obj_or_class).option(
            obj_or_class, "versioning"
        )
    except ClassNotVersioned:
        return False

parent_class

Return the parent class for given version model class.

Parameters:
  • model

    SQLAlchemy declarative version model class

  • version_cls
Returns:
sqlalchemy_history/utils.py
def parent_class(version_cls):
    """
    Return the parent class for given version model class.

    :param model: SQLAlchemy declarative version model class
    :param version_cls:
    :returns:
    """
    manager = get_versioning_manager(version_cls)
    try:
        return next((k for k, v in manager.version_class_map.items() if v == version_cls))
    except StopIteration:
        # Should raise Key Error if we can't find the parent_object of a orphaned versioned_model
        raise KeyError(version_cls)

transaction_class

Return the associated transaction class for given versioned SQLAlchemy declarative class or version class.

Parameters:
  • cls

    SQLAlchemy versioned declarative class or version model class

Returns:
  • declarative class or version class.

sqlalchemy_history/utils.py
def transaction_class(cls):
    """
    Return the associated transaction class for given versioned SQLAlchemy
    declarative class or version class.

    :param cls: SQLAlchemy versioned declarative class or version model class
    :returns: declarative class or version class.
    """
    return get_versioning_manager(cls).transaction_cls

version_class

Return the version class for given SQLAlchemy declarative model class.

Parameters:
  • model

    SQLAlchemy declarative model class

Returns:
sqlalchemy_history/utils.py
def version_class(model):
    """
    Return the version class for given SQLAlchemy declarative model class.

    :param model: SQLAlchemy declarative model class
    :returns:
    """
    manager = get_versioning_manager(model)
    return manager.version_class_map.get(model, None)

versioned_objects

Return all versioned objects in given session.

Parameters:
  • session

    SQLAlchemy session object

sqlalchemy_history/utils.py
def versioned_objects(session):
    """
    Return all versioned objects in given session.

    :param session: SQLAlchemy session object

    """
    for obj in session:
        if is_versioned(obj):
            yield obj

version_table

Return associated version table for given SQLAlchemy Table object.

Parameters:
  • table

    SQLAlchemy Table object

sqlalchemy_history/utils.py
def version_table(table):
    """
    Return associated version table for given SQLAlchemy Table object.

    :param table: SQLAlchemy Table object

    """
    manager = get_versioning_manager(table)
    return manager.version_table_map.get(table, None)