diff --git a/src/wutta_continuum/conf.py b/src/wutta_continuum/conf.py index 1cb3bd7..361131a 100644 --- a/src/wutta_continuum/conf.py +++ b/src/wutta_continuum/conf.py @@ -28,7 +28,7 @@ import socket from sqlalchemy.orm import configure_mappers from sqlalchemy_continuum import make_versioned -from sqlalchemy_continuum.plugins import Plugin +from sqlalchemy_continuum.plugins import Plugin, TransactionMetaPlugin from wuttjamaican.conf import WuttaConfigExtension from wuttjamaican.util import load_object @@ -66,6 +66,20 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension): For more about SQLAlchemy-Continuum see :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 if not config.get_bool( @@ -86,7 +100,7 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension): raise RuntimeError("something not right, app already has model") # let sqlalchemy-continuum do its thing - make_versioned(plugins=[plugin()]) + make_versioned(plugins=[TransactionMetaPlugin(), plugin()]) # must load model *between* prev and next calls app.get_model() @@ -148,3 +162,16 @@ class WuttaContinuumPlugin(Plugin): kwargs["user_id"] = user_id 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 diff --git a/src/wutta_continuum/db/alembic/versions/46fb4711411d_add_transaction_meta.py b/src/wutta_continuum/db/alembic/versions/46fb4711411d_add_transaction_meta.py new file mode 100644 index 0000000..ebaf636 --- /dev/null +++ b/src/wutta_continuum/db/alembic/versions/46fb4711411d_add_transaction_meta.py @@ -0,0 +1,40 @@ +"""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") diff --git a/tests/test_conf.py b/tests/test_conf.py index 1ea5d44..697592a 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -2,7 +2,7 @@ import socket -from unittest.mock import patch +from unittest.mock import patch, Mock from wuttjamaican.testing import ConfigTestCase, DataTestCase @@ -71,3 +71,20 @@ class TestWuttaContinuumPlugin(DataTestCase): plugin.transaction_args(None, self.session), {"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")