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:
- Call
make_versioned()before your models are defined. - 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
- http://en.wikipedia.org/wiki/Slowly_changing_dimension
- http://en.wikipedia.org/wiki/Change_data_capture
- http://en.wikipedia.org/wiki/Anchor_Modeling
- http://en.wikipedia.org/wiki/Shadow_table
- https://wiki.postgresql.org/wiki/Audit_trigger
- https://wiki.postgresql.org/wiki/Audit_trigger_91plus
- http://kosalads.blogspot.fi/2014/06/implement-audit-functionality-in.html
- https://github.com/2ndQuadrant/pgaudit
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:
- sqlalchemy-continuum
- Does not support oracle, mssql
- Feature filled making it difficult to maintain all plugins/extensions
- flask-continuum
- Thin wrapper on sqlalchemy-continuum specifically for flask
- postgresql-audit
- Supports only postgres
- versionalchemy
- Not updated in a while
- No reverting capability, Relationship queries on history not available
- django-simple-history
- Uses django ORM, does not support sqlalchemy
- sqlalchemy example versioning-objects
- Simple example to demonstrate implementation - but very minimal
Metadata
Release files for SQLAlchemy-History 2.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sqlalchemy_history-2.1.6.tar.gz | 231.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sqlalchemy_history-2.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 282.1 kB
Release files / sqlalchemy_history-2.1.6.tar.gz
| Download URL | sqlalchemy_history-2.1.6.tar.gz |
|---|---|
| Size | 231.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8015f58d22e9d3dd55897e4217c685eb320d3774cfcc7f596a91b44a02ce8261
|
|
BLAKE2b-256 checksum How to use checksums |
14c5924c6bd7d5db44ba3bc509f71094b440a39bffe9e95e14fa1527dfb72260
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / sqlalchemy_history-2.1.6-py3-none-any.whl
| Download URL | sqlalchemy_history-2.1.6-py3-none-any.whl |
|---|---|
| Size | 51.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e5fad386fb9770ec24d0db7df5498fc519e51fa73a395a716aa34543700874e4
|
|
BLAKE2b-256 checksum How to use checksums |
f8a46d710506d7ec45f3e0be76e68daf2aee4b6f74e761e35c5f532ad73af5ab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|