Compare commits
No commits in common. "1fc280eff1f84b2d18d9437a0b981c9a4d4a7fda" and "b858ba17936f922227972cb2a5c7ccc4b306ec3b" have entirely different histories.
1fc280eff1
...
b858ba1793
9 changed files with 7 additions and 166 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
|
||||||
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
@ -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")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue