diff --git a/CHANGELOG.md b/CHANGELOG.md index ab6d44e..0333db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +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/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## v0.2.1 (2025-10-29) - -### Fix - -- add util module, w/ `model_transaction_query()` -- refactor some more for tests + pylint -- format all code with black - ## v0.2.0 (2024-12-07) ### Feat diff --git a/docs/api/wutta_continuum.testing.rst b/docs/api/wutta_continuum.testing.rst deleted file mode 100644 index 4afca16..0000000 --- a/docs/api/wutta_continuum.testing.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wutta_continuum.testing`` -=========================== - -.. automodule:: wutta_continuum.testing - :members: diff --git a/docs/api/wutta_continuum.util.rst b/docs/api/wutta_continuum.util.rst deleted file mode 100644 index c337ddb..0000000 --- a/docs/api/wutta_continuum.util.rst +++ /dev/null @@ -1,6 +0,0 @@ - -``wutta_continuum.util`` -======================== - -.. automodule:: wutta_continuum.util - :members: diff --git a/docs/conf.py b/docs/conf.py index 330e71f..cf8790a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,7 +27,6 @@ templates_path = ["_templates"] exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] intersphinx_mapping = { - "sqlalchemy": ("http://docs.sqlalchemy.org/en/latest/", None), "sqlalchemy-continuum": ( "https://sqlalchemy-continuum.readthedocs.io/en/latest/", None, diff --git a/docs/index.rst b/docs/index.rst index a8f656f..b4add5a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -29,5 +29,3 @@ This package adds data versioning/history for `WuttJamaican`_, using api/wutta_continuum api/wutta_continuum.app api/wutta_continuum.conf - api/wutta_continuum.testing - api/wutta_continuum.util diff --git a/pyproject.toml b/pyproject.toml index 6f1fe7f..6c7f2a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "Wutta-Continuum" -version = "0.2.1" +version = "0.2.0" description = "SQLAlchemy-Continuum versioning for Wutta Framework" readme = "README.md" authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] diff --git a/src/wutta_continuum/conf.py b/src/wutta_continuum/conf.py index 1cb3bd7..dc3f476 100644 --- a/src/wutta_continuum/conf.py +++ b/src/wutta_continuum/conf.py @@ -45,28 +45,7 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension): key = "wutta_continuum" def startup(self, config): # pylint: disable=empty-docstring - """ - Perform final configuration setup for app startup. - - This will do nothing at all, unless config enables the - versioning feature. This must be done in config file and not - in DB settings table: - - .. code-block:: ini - - [wutta_continuum] - enable_versioning = true - - Once enabled, this method will configure the integration, via - these steps: - - 1. call :func:`sqlalchemy-continuum:sqlalchemy_continuum.make_versioned()` - 2. call :meth:`wuttjamaican:wuttjamaican.app.AppHandler.get_model()` - 3. call :func:`sqlalchemy:sqlalchemy.orm.configure_mappers()` - - For more about SQLAlchemy-Continuum see - :doc:`sqlalchemy-continuum:intro`. - """ + """ """ # only do this if config enables it if not config.get_bool( "wutta_continuum.enable_versioning", usedb=False, default=False @@ -81,17 +60,14 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension): ) plugin = load_object(spec) - app = config.get_app() - if "model" in app.__dict__: - raise RuntimeError("something not right, app already has model") - - # let sqlalchemy-continuum do its thing + # tell sqlalchemy-continuum to do its thing make_versioned(plugins=[plugin()]) - # must load model *between* prev and next calls - app.get_model() + # nb. must load the model before configuring mappers + app = config.get_app() + model = app.model # pylint: disable=unused-variable - # let sqlalchemy do its thing + # tell sqlalchemy to do its thing configure_mappers() diff --git a/src/wutta_continuum/testing.py b/src/wutta_continuum/testing.py deleted file mode 100644 index c9229d3..0000000 --- a/src/wutta_continuum/testing.py +++ /dev/null @@ -1,92 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework -# Copyright © 2024-2025 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -Testing utilities -""" - -import sys - -import sqlalchemy_continuum as continuum - -from wuttjamaican.testing import DataTestCase - -from wutta_continuum.conf import WuttaContinuumConfigExtension - - -class VersionTestCase(DataTestCase): - """ - Base class for test suites requiring the SQLAlchemy-Continuum - versioning feature. - - This inherits from - :class:`~wuttjamaican:wuttjamaican.testing.DataTestCase`. - """ - - def setUp(self): - self.setup_versioning() - - def setup_versioning(self): - """ - Do setup tasks relating to this class, as well as its parent(s): - - * call :meth:`wuttjamaican:wuttjamaican.testing.DataTestCase.setup_db()` - - * this will in turn call :meth:`make_config()` - """ - self.setup_db() - - def tearDown(self): - self.teardown_versioning() - - def teardown_versioning(self): - """ - Do teardown tasks relating to this class, as well as its parent(s): - - * call :func:`sqlalchemy-continuum:sqlalchemy_continuum.remove_versioning()` - * call :meth:`wuttjamaican:wuttjamaican.testing.DataTestCase.teardown_db()` - """ - continuum.remove_versioning() - continuum.versioning_manager.transaction_cls = continuum.TransactionFactory() - self.teardown_db() - - def make_config(self, **kwargs): - """ - Make and customize the config object. - - We override this to explicitly enable the versioning feature. - """ - config = super().make_config(**kwargs) - config.setdefault("wutta_continuum.enable_versioning", "true") - - # nb. must purge model classes from sys.modules, so they will - # be reloaded and sqlalchemy-continuum can reconfigure - if "wuttjamaican.db.model" in sys.modules: - del sys.modules["wuttjamaican.db.model.batch"] - del sys.modules["wuttjamaican.db.model.upgrades"] - del sys.modules["wuttjamaican.db.model.auth"] - del sys.modules["wuttjamaican.db.model.base"] - del sys.modules["wuttjamaican.db.model"] - - ext = WuttaContinuumConfigExtension() - ext.startup(config) - return config diff --git a/src/wutta_continuum/util.py b/src/wutta_continuum/util.py deleted file mode 100644 index 4ca64ec..0000000 --- a/src/wutta_continuum/util.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework -# Copyright © 2024-2025 Lance Edgar -# -# This file is part of Wutta Framework. -# -# Wutta Framework is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by the Free -# Software Foundation, either version 3 of the License, or (at your option) any -# later version. -# -# Wutta Framework is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -# more details. -# -# You should have received a copy of the GNU General Public License along with -# Wutta Framework. If not, see . -# -################################################################################ -""" -SQLAlchemy-Continuum utilities -""" - -import sqlalchemy as sa -from sqlalchemy import orm -import sqlalchemy_continuum as continuum - - -OPERATION_TYPES = { - continuum.Operation.INSERT: "INSERT", - continuum.Operation.UPDATE: "UPDATE", - continuum.Operation.DELETE: "DELETE", -} - - -def render_operation_type(operation_type): - """ - Render a SQLAlchemy-Continuum ``operation_type`` from a version - record, for display to user. - - :param operation_type: Value of same name from a version record. - Must be one of: - - * :attr:`sqlalchemy_continuum:sqlalchemy_continuum.operation.Operation.INSERT` - * :attr:`sqlalchemy_continuum:sqlalchemy_continuum.operation.Operation.UPDATE` - * :attr:`sqlalchemy_continuum:sqlalchemy_continuum.operation.Operation.DELETE` - - :returns: Display name for the operation type, as string. - """ - return OPERATION_TYPES[operation_type] - - -def model_transaction_query(instance, session=None, model_class=None): - """ - Make a query capable of finding all SQLAlchemy-Continuum - ``transaction`` records associated with the given model instance. - - :param instance: Instance of a versioned :term:`data model`. - - :param session: Optional :term:`db session` to use for the query. - If not specified, will be obtained from the ``instance``. - - :param model_class: Optional :term:`data model` class to query. - If not specified, will be obtained from the ``instance``. - - :returns: SQLAlchemy query object. Note that it will *not* have an - ``ORDER BY`` clause yet. - """ - if not session: - session = orm.object_session(instance) - if not model_class: - model_class = type(instance) - - txncls = continuum.transaction_class(model_class) - vercls = continuum.version_class(model_class) - - query = session.query(txncls).join( - vercls, - sa.and_(vercls.uuid == instance.uuid, vercls.transaction_id == txncls.id), - ) - - return query diff --git a/tests/test_conf.py b/tests/test_conf.py index 1ea5d44..22c2873 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -4,45 +4,33 @@ import socket from unittest.mock import patch -from wuttjamaican.testing import ConfigTestCase, DataTestCase +from wuttjamaican.testing import DataTestCase from wutta_continuum import conf as mod -class TestWuttaContinuumConfigExtension(ConfigTestCase): +class TestWuttaContinuumConfigExtension(DataTestCase): def make_extension(self): return mod.WuttaContinuumConfigExtension() - def test_startup_without_versioning(self): + def test_startup(self): ext = self.make_extension() + with patch.object(mod, "make_versioned") as make_versioned: with patch.object(mod, "configure_mappers") as configure_mappers: + + # nothing happens by default ext.startup(self.config) make_versioned.assert_not_called() configure_mappers.assert_not_called() - def test_startup_with_versioning(self): - ext = self.make_extension() - with patch.object(mod, "make_versioned") as make_versioned: - with patch.object(mod, "configure_mappers") as configure_mappers: + # but will if we enable it in config self.config.setdefault("wutta_continuum.enable_versioning", "true") ext.startup(self.config) make_versioned.assert_called_once() configure_mappers.assert_called_once_with() - def test_startup_with_error(self): - ext = self.make_extension() - with patch.object(mod, "make_versioned") as make_versioned: - with patch.object(mod, "configure_mappers") as configure_mappers: - self.config.setdefault("wutta_continuum.enable_versioning", "true") - # nb. it is an error for the model to be loaded prior to - # calling make_versioned() for sqlalchemy-continuum - self.app.get_model() - self.assertRaises(RuntimeError, ext.startup, self.config) - make_versioned.assert_not_called() - configure_mappers.assert_not_called() - class TestWuttaContinuumPlugin(DataTestCase): diff --git a/tests/test_util.py b/tests/test_util.py deleted file mode 100644 index 944c861..0000000 --- a/tests/test_util.py +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8; -*- - -from unittest import TestCase - -import sqlalchemy_continuum as continuum - -from wutta_continuum import util as mod -from wutta_continuum.testing import VersionTestCase - - -class TestRenderOperationType(TestCase): - - def test_basic(self): - self.assertEqual( - mod.render_operation_type(continuum.Operation.INSERT), "INSERT" - ) - self.assertEqual( - mod.render_operation_type(continuum.Operation.UPDATE), "UPDATE" - ) - self.assertEqual( - mod.render_operation_type(continuum.Operation.DELETE), "DELETE" - ) - - -class TestModelTransactionQuery(VersionTestCase): - - def test_basic(self): - model = self.app.model - - user = model.User(username="fred") - self.session.add(user) - self.session.commit() - - query = mod.model_transaction_query(user) - self.assertEqual(query.count(), 1) - txn = query.one() - - UserVersion = continuum.version_class(model.User) - version = self.session.query(UserVersion).one() - self.assertIs(version.transaction, txn)