Compare commits

..

No commits in common. "1fc280eff1f84b2d18d9437a0b981c9a4d4a7fda" and "b858ba17936f922227972cb2a5c7ccc4b306ec3b" have entirely different histories.

9 changed files with 7 additions and 166 deletions

View file

@ -5,12 +5,6 @@ All notable changes to Wutta-Continuum will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## v0.3.0 (2025-12-20)
### Feat
- add TransactionMetaPlugin to save comments when applicable
## v0.2.2 (2025-10-29) ## v0.2.2 (2025-10-29)
### Fix ### Fix

View file

@ -5,7 +5,7 @@ Wutta-Continuum
This package adds data versioning/history for `WuttJamaican`_, using This package adds data versioning/history for `WuttJamaican`_, using
`SQLAlchemy-Continuum`_. `SQLAlchemy-Continuum`_.
.. _WuttJamaican: https://docs.wuttaproject.org/wuttjamaican/ .. _WuttJamaican: https://rattailproject.org/docs/wuttjamaican/
.. _SQLAlchemy-Continuum: https://sqlalchemy-continuum.readthedocs.io/en/latest/ .. _SQLAlchemy-Continuum: https://sqlalchemy-continuum.readthedocs.io/en/latest/
@ -20,9 +20,7 @@ This package adds data versioning/history for `WuttJamaican`_, using
:maxdepth: 2 :maxdepth: 2
:caption: Documentation :caption: Documentation
narr/features
narr/install narr/install
narr/usage
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1

View file

@ -1,26 +0,0 @@
Features
========
The general idea is to provide an audit/versioning trail for important
data tables.
Each table defined in the :term:`app model` can either be versioned,
or not. Nothing changes for a non-versioned table.
For a "versioned" table, a secondary "versions" table is created,
schema for which is a superset of the original "versioned" table.
When records change in the original table, new "version" records are
added to the versions table.
Therefore you can see how a record has changed over time, by
inspecting its corresponding versions.
When any record changes (for any versioned table), a new "transaction"
record is also created. This identifies the user responsible, and
timestamp etc. Any new version records will tie back to this
transaction record.
All this is made possible by SQLAlchemy-Continuum; the Wutta-Continuum
package mostly just adds config glue. See also
:doc:`sqlalchemy-continuum:index`.

View file

@ -31,7 +31,7 @@ this package for database migrations. You should already have an
[alembic] [alembic]
script_location = wuttjamaican.db:alembic script_location = wuttjamaican.db:alembic
version_locations = wutta_continuum.db:alembic/versions poser.db:alembic/versions wuttjamaican.db:alembic/versions version_locations = wuttjamaican.db:alembic/versions wutta_continuum.db:alembic/versions
Then (as you would have done previously in Then (as you would have done previously in
:ref:`wuttjamaican:db-setup`) you can migrate your database to add the :ref:`wuttjamaican:db-setup`) you can migrate your database to add the

View file

@ -1,41 +0,0 @@
Usage
=====
You can check the feature status with
:meth:`~wutta_continuum.app.WuttaContinuumAppProvider.continuum_is_enabled()`::
app = config.get_app()
if not app.continuum_is_enabled():
print("Oh no! Continuum is not enabled.")
The rest of this will assume the feature is enabled.
Built-In Models
---------------
The following built-in models are versioned. So, when records are
added / modified / removed via the ORM, new version records are
automatically created for each of these:
* :class:`~wuttjamaican:wuttjamaican.db.model.auth.Permission`
* :class:`~wuttjamaican:wuttjamaican.db.model.base.Person`
* :class:`~wuttjamaican:wuttjamaican.db.model.auth.Role`
* :class:`~wuttjamaican:wuttjamaican.db.model.auth.User`
* :class:`~wuttjamaican:wuttjamaican.db.model.auth.UserRole`
Object Versions
---------------
A versioned model works normally but also has a ``versions``
attribute, which reflects the list of version records::
user = session.query(model.User).first()
for version in user.versions:
print(version)
See also :doc:`sqlalchemy-continuum:version_objects`.

View file

@ -6,7 +6,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "Wutta-Continuum" name = "Wutta-Continuum"
version = "0.3.0" version = "0.2.2"
description = "SQLAlchemy-Continuum versioning for Wutta Framework" description = "SQLAlchemy-Continuum versioning for Wutta Framework"
readme = "README.md" readme = "README.md"
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}]
@ -27,7 +27,7 @@ classifiers = [
requires-python = ">= 3.8" requires-python = ">= 3.8"
dependencies = [ dependencies = [
"SQLAlchemy-Continuum", "SQLAlchemy-Continuum",
"WuttJamaican[db]>=0.27.0", "WuttJamaican[db]>=0.24.1",
] ]

View file

@ -28,7 +28,7 @@ import socket
from sqlalchemy.orm import configure_mappers from sqlalchemy.orm import configure_mappers
from sqlalchemy_continuum import make_versioned from sqlalchemy_continuum import make_versioned
from sqlalchemy_continuum.plugins import Plugin, TransactionMetaPlugin from sqlalchemy_continuum.plugins import Plugin
from wuttjamaican.conf import WuttaConfigExtension from wuttjamaican.conf import WuttaConfigExtension
from wuttjamaican.util import load_object from wuttjamaican.util import load_object
@ -66,20 +66,6 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension):
For more about SQLAlchemy-Continuum see For more about SQLAlchemy-Continuum see
:doc:`sqlalchemy-continuum:intro`. :doc:`sqlalchemy-continuum:intro`.
Two plugins are provided to ``make_versioned()``:
The first is ``TransactionMetaPlugin`` for sake of adding
comments (see
:mod:`~sqlalchemy-continuum:sqlalchemy_continuum.plugins.transaction_meta`).
The second by default is :class:`WuttaContinuumPlugin` but you
can override with config:
.. code-block:: ini
[wutta_continuum]
wutta_plugin_spec = poser.db.continuum:PoserContinuumPlugin
""" """
# only do this if config enables it # only do this if config enables it
if not config.get_bool( if not config.get_bool(
@ -100,7 +86,7 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension):
raise RuntimeError("something not right, app already has model") raise RuntimeError("something not right, app already has model")
# let sqlalchemy-continuum do its thing # let sqlalchemy-continuum do its thing
make_versioned(plugins=[TransactionMetaPlugin(), plugin()]) make_versioned(plugins=[plugin()])
# must load model *between* prev and next calls # must load model *between* prev and next calls
app.get_model() app.get_model()
@ -162,16 +148,3 @@ class WuttaContinuumPlugin(Plugin):
kwargs["user_id"] = user_id kwargs["user_id"] = user_id
return kwargs return kwargs
def before_flush(self, uow, session):
"""
We use this hook to inject the "comment" for current
transaction, if applicable.
This checks the session for the comment; so any session can
specify one like so::
session.info["continuum_comment"] = "hello world"
"""
if comment := session.info.get("continuum_comment"):
uow.current_transaction.meta["comment"] = comment

View file

@ -1,40 +0,0 @@
"""add transaction_meta
Revision ID: 46fb4711411d
Revises: 989392cc191d
Create Date: 2025-12-18 21:22:33.382628
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
import wuttjamaican.db.util
# revision identifiers, used by Alembic.
revision: str = "46fb4711411d"
down_revision: Union[str, None] = "989392cc191d"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# transaction_meta
op.create_table(
"transaction_meta",
sa.Column("transaction_id", sa.BigInteger(), nullable=False),
sa.Column("key", sa.Unicode(length=255), nullable=False),
sa.Column("value", sa.UnicodeText(), nullable=True),
sa.PrimaryKeyConstraint(
"transaction_id", "key", name=op.f("pk_transaction_meta")
),
)
def downgrade() -> None:
# transaction_meta
op.drop_table("transaction_meta")

View file

@ -2,7 +2,7 @@
import socket import socket
from unittest.mock import patch, Mock from unittest.mock import patch
from wuttjamaican.testing import ConfigTestCase, DataTestCase from wuttjamaican.testing import ConfigTestCase, DataTestCase
@ -71,20 +71,3 @@ class TestWuttaContinuumPlugin(DataTestCase):
plugin.transaction_args(None, self.session), plugin.transaction_args(None, self.session),
{"remote_addr": "127.0.0.1", "user_id": "some-random-uuid"}, {"remote_addr": "127.0.0.1", "user_id": "some-random-uuid"},
) )
def test_before_flush(self):
plugin = self.make_plugin()
meta = {}
txn = Mock(meta=meta)
uow = Mock(current_transaction=txn)
# no comment in session or transaction
plugin.before_flush(uow, self.session)
self.assertNotIn("comment", meta)
# transaction comment matches session
self.session.info["continuum_comment"] = "whaddyaknow"
plugin.before_flush(uow, self.session)
self.assertIn("comment", meta)
self.assertEqual(meta["comment"], "whaddyaknow")