Skip to main content

SQLAlchemy-History

SQLAlchemy-History is a fork of SQLAlchemy-Continuum. It is an auditing extension that tracks the history of SQLAlchemy models.

Features

  • Supports SQLAlchemy 2+ and Python 3.9+
  • Tracks history for inserts, deletes, and updates
  • Does not store updates which don't change anything
  • Supports Alembic migrations
  • Can revert objects data as well as all object relations at given transaction even if the object was deleted
  • Transactions can be queried afterwards using SQLAlchemy select syntax
  • Query for changed records at given transaction
  • Temporal relationship reflection. Get the relationships of an object in that point in time.
  • Supports async SQLAlchemy

Quickstart

uv pip install sqlalchemy-history

In order to make your models versioned you need two things:

  1. Call make_versioned() before your models are defined.
  2. Add __versioned__ to all models you wish to add versioning to
>>> from sqlalchemy_history import make_versioned
>>> make_versioned(user_cls=None)
>>> class Article(Base):
...    __versioned__ = {}
...    __tablename__ = 'article'
...    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
...    name = sa.Column(sa.Unicode(255))
...    content = sa.Column(sa.UnicodeText)
>>> article = Article(name='Some article', content='Some content')
>>> session.add(article)
>>> session.commit()
'article has now one version stored in database'
>>> article.versions[0].name
'Some article'
>>> article.name = 'Updated name'
>>> session.commit()
>>> article.versions[1].name
'Updated name'
>>> article.versions[0].revert()
'lets revert back to first version'
>>> article.name
'Some article'

For completeness, below is a working example.

from sqlalchemy_history import make_versioned
from sqlalchemy import Column, Integer, Unicode, UnicodeText, create_engine
from sqlalchemy.orm import DeclarativeBase, create_session, configure_mappers

make_versioned(user_cls=None)


class Base(DeclarativeBase):
    pass


class Article(Base):
    __versioned__ = {}
    __tablename__ = "article"
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(Unicode(255))
    content = Column(UnicodeText)


configure_mappers()
engine = create_engine("sqlite://")
Base.metadata.create_all(engine)
session = create_session(bind=engine, autocommit=False)
article = Article(name="Some article", content="Some content")
session.add(article)
session.commit()
print(article.versions[0].name)  # 'Some article'
article.name = "Updated name"
session.commit()
print(article.versions[1].name)  # 'Updated name'
article.versions[0].revert()
print(article.name)  # 'Some article'
Async working example
import asyncio

import sqlalchemy as sa
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, configure_mappers

from sqlalchemy_history import make_versioned

make_versioned(user_cls=None, options={"support_async": True})


class Base(DeclarativeBase):
    pass


class Article(Base):
    __versioned__ = {}
    __tablename__ = "article"
    id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
    name = sa.Column(sa.Unicode(255))
    content = sa.Column(sa.UnicodeText)


async def main():
    configure_mappers()

    engine = create_async_engine("sqlite+aiosqlite://")

    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    Session = async_sessionmaker(engine, expire_on_commit=False)

    async with Session() as session:
        article = Article(name="Some article", content="Some content")
        session.add(article)
        await session.commit()

        versions = (await session.scalars(article.versions.select())).all()
        print(versions[0].name)  # 'Some article'

        article.name = "Updated name"
        await session.commit()

        versions = (await session.scalars(article.versions.select())).all()
        print(versions[1].name)  # 'Updated name'

        versions[0].revert()
        await session.commit()
        print(article.name)  # 'Some article'

    await engine.dispose()


asyncio.run(main())

For more async querying and revert examples, see Async support.

Resources

More information

Comparison

Primary reasons to create another library:

  • Be future looking and support sqlalchemy 2.x
  • Support multiple databases (sqlite, mysql, postgres, mssql, oracle)
  • Focus on the history tracking and be as efficient as possible when doing it

We found multiple libraries which has an implementation of history tracking:

  1. sqlalchemy-continuum
    • Does not support oracle, mssql
    • Feature filled making it difficult to maintain all plugins/extensions
  2. flask-continuum
    • Thin wrapper on sqlalchemy-continuum specifically for flask
  3. postgresql-audit
    • Supports only postgres
  4. versionalchemy
    • Not updated in a while
    • No reverting capability, Relationship queries on history not available
  5. django-simple-history
    • Uses django ORM, does not support sqlalchemy
  6. sqlalchemy example versioning-objects
    • Simple example to demonstrate implementation - but very minimal

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sqlalchemy_history-2.1.6.tar.gz (231.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sqlalchemy_history-2.1.6-py3-none-any.whl (51.1 kB view details)

Uploaded Python 3

File details

Details for the file sqlalchemy_history-2.1.6.tar.gz.

File metadata

  • Download URL: sqlalchemy_history-2.1.6.tar.gz
  • Upload date:
  • Size: 231.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sqlalchemy_history-2.1.6.tar.gz
Algorithm Hash digest
SHA256 8015f58d22e9d3dd55897e4217c685eb320d3774cfcc7f596a91b44a02ce8261
MD5 dcae0f7d677108f5379812e6483a1c4e
BLAKE2b-256 14c5924c6bd7d5db44ba3bc509f71094b440a39bffe9e95e14fa1527dfb72260

See more details on using hashes here.

File details

Details for the file sqlalchemy_history-2.1.6-py3-none-any.whl.

File metadata

  • Download URL: sqlalchemy_history-2.1.6-py3-none-any.whl
  • Upload date:
  • Size: 51.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.6 {"installer":{"name":"uv","version":"0.12.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for sqlalchemy_history-2.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 e5fad386fb9770ec24d0db7df5498fc519e51fa73a395a716aa34543700874e4
MD5 7b7785102d5c3d9c9963dfb850565553
BLAKE2b-256 f8a46d710506d7ec45f3e0be76e68daf2aee4b6f74e761e35c5f532ad73af5ab

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

2.1.6 This release

2 files

2.1.5

2 files

2.1.4

2 files

2.1.3

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page