diff --git a/.pylintrc b/.pylintrc deleted file mode 100644 index 7eb5e2c..0000000 --- a/.pylintrc +++ /dev/null @@ -1,4 +0,0 @@ -# -*- mode: conf; -*- - -[MESSAGES CONTROL] -disable=fixme diff --git a/CHANGELOG.md b/CHANGELOG.md index a770df3..86872b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,42 +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.3.1 (2025-12-31) - -### Fix - -- set transaction user based on session info, when applicable - -## v0.3.0 (2025-12-20) - -### Feat - -- add TransactionMetaPlugin to save comments when applicable - -## v0.2.2 (2025-10-29) - -### Fix - -- exclude user password from continuum versioning - -## 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 - -- convert all uuid fields from str to proper UUID - -### Fix - -- add `User.prevent_edit` to schema - ## v0.1.1 (2024-08-27) ### Fix diff --git a/README.md b/README.md index 531c46a..c7bd996 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Wutta-Continuum -SQLAlchemy-Continuum versioning for Wutta Framework +SQLAlchemy-Continuum versioning for WuttJamaican -See docs at https://docs.wuttaproject.org/wutta-continuum/ +See docs at https://rattailproject.org/docs/wutta-continuum/ 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..654920b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,36 +8,32 @@ from importlib.metadata import version as get_version -project = "Wutta-Continuum" -copyright = "2024, Lance Edgar" -author = "Lance Edgar" -release = get_version("Wutta-Continuum") +project = 'Wutta-Continuum' +copyright = '2024, Lance Edgar' +author = 'Lance Edgar' +release = get_version('Wutta-Continuum') # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.intersphinx", - "sphinx.ext.viewcode", - "sphinx.ext.todo", + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.todo', ] -templates_path = ["_templates"] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] +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, - ), - "wuttjamaican": ("https://docs.wuttaproject.org/wuttjamaican/", None), + 'sqlalchemy-continuum': ('https://sqlalchemy-continuum.readthedocs.io/en/latest/', None), + 'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None), } # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "furo" -html_static_path = ["_static"] +html_theme = 'furo' +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst index 6b003b9..e229883 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,24 +5,16 @@ Wutta-Continuum This package adds data versioning/history for `WuttJamaican`_, using `SQLAlchemy-Continuum`_. -.. _WuttJamaican: https://docs.wuttaproject.org/wuttjamaican/ +.. _WuttJamaican: https://rattailproject.org/docs/wuttjamaican/ .. _SQLAlchemy-Continuum: https://sqlalchemy-continuum.readthedocs.io/en/latest/ -.. image:: https://img.shields.io/badge/linting-pylint-yellowgreen - :target: https://github.com/pylint-dev/pylint - -.. image:: https://img.shields.io/badge/code%20style-black-000000.svg - :target: https://github.com/psf/black - .. toctree:: :maxdepth: 2 :caption: Documentation - narr/features narr/install - narr/usage .. toctree:: :maxdepth: 1 @@ -31,5 +23,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/docs/narr/features.rst b/docs/narr/features.rst deleted file mode 100644 index 20b08a4..0000000 --- a/docs/narr/features.rst +++ /dev/null @@ -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`. diff --git a/docs/narr/install.rst b/docs/narr/install.rst index 1494d22..4959f96 100644 --- a/docs/narr/install.rst +++ b/docs/narr/install.rst @@ -31,7 +31,7 @@ this package for database migrations. You should already have an [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 :ref:`wuttjamaican:db-setup`) you can migrate your database to add the diff --git a/docs/narr/usage.rst b/docs/narr/usage.rst deleted file mode 100644 index 4d42288..0000000 --- a/docs/narr/usage.rst +++ /dev/null @@ -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`. diff --git a/pyproject.toml b/pyproject.toml index 4498c9b..cdae7af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,10 @@ build-backend = "hatchling.build" [project] name = "Wutta-Continuum" -version = "0.3.1" -description = "SQLAlchemy-Continuum versioning for Wutta Framework" +version = "0.1.1" +description = "SQLAlchemy-Continuum versioning for WuttJamaican" readme = "README.md" -authors = [{name = "Lance Edgar", email = "lance@wuttaproject.org"}] +authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] license = {text = "GNU GPL v3+"} classifiers = [ "Development Status :: 4 - Beta", @@ -27,13 +27,13 @@ classifiers = [ requires-python = ">= 3.8" dependencies = [ "SQLAlchemy-Continuum", - "WuttJamaican[db]>=0.27.0", + "WuttJamaican[db]", ] [project.optional-dependencies] docs = ["Sphinx", "furo"] -tests = ["pylint", "pytest", "pytest-cov", "tox"] +tests = ["pytest-cov", "tox"] [project.entry-points."wutta.app.providers"] @@ -47,7 +47,6 @@ wutta_continuum = "wutta_continuum.conf:WuttaContinuumConfigExtension" [project.urls] Homepage = "https://wuttaproject.org/" Repository = "https://forgejo.wuttaproject.org/wutta/wutta-continuum" -Issues = "https://forgejo.wuttaproject.org/wutta/wutta-continuum/issues" Changelog = "https://forgejo.wuttaproject.org/wutta/wutta-continuum/src/branch/master/CHANGELOG.md" diff --git a/src/wutta_continuum/__init__.py b/src/wutta_continuum/__init__.py index 60e7ad2..63f63ff 100644 --- a/src/wutta_continuum/__init__.py +++ b/src/wutta_continuum/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8; -*- ################################################################################ # -# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework +# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican # Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. @@ -21,7 +21,7 @@ # ################################################################################ """ -Wutta-Continuum -- SQLAlchemy-Continuum versioning for Wutta Framework +Wutta-Continuum -- SQLAlchemy-Continuum versioning for WuttJamaican """ from ._version import __version__ diff --git a/src/wutta_continuum/_version.py b/src/wutta_continuum/_version.py index f243184..23de4cf 100644 --- a/src/wutta_continuum/_version.py +++ b/src/wutta_continuum/_version.py @@ -1,9 +1,6 @@ # -*- coding: utf-8; -*- -""" -Package Version -""" from importlib.metadata import version -__version__ = version("Wutta-Continuum") +__version__ = version('Wutta-Continuum') diff --git a/src/wutta_continuum/app.py b/src/wutta_continuum/app.py index 30ab321..a6d55c7 100644 --- a/src/wutta_continuum/app.py +++ b/src/wutta_continuum/app.py @@ -1,7 +1,7 @@ # -*- coding: utf-8; -*- ################################################################################ # -# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework +# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican # Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. @@ -40,6 +40,5 @@ class WuttaContinuumAppProvider(AppProvider): This checks the config value as described in :doc:`/narr/install`; default will be ``False``. """ - return self.config.get_bool( - "wutta_continuum.enable_versioning", usedb=False, default=False - ) + return self.config.get_bool('wutta_continuum.enable_versioning', + usedb=False, default=False) diff --git a/src/wutta_continuum/conf.py b/src/wutta_continuum/conf.py index 6c9abb4..bdf2d92 100644 --- a/src/wutta_continuum/conf.py +++ b/src/wutta_continuum/conf.py @@ -1,8 +1,8 @@ # -*- coding: utf-8; -*- ################################################################################ # -# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework -# Copyright © 2024-2025 Lance Edgar +# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican +# Copyright © 2024 Lance Edgar # # This file is part of Wutta Framework. # @@ -24,11 +24,12 @@ App Configuration """ +import datetime import socket from sqlalchemy.orm import configure_mappers 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.util import load_object @@ -41,74 +42,29 @@ class WuttaContinuumConfigExtension(WuttaConfigExtension): This adds a startup hook, which can optionally turn on the SQLAlchemy-Continuum versioning features for the main app DB. """ + key = 'wutta_continuum' - 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`. - - 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 - - See also the SQLAlchemy-Continuum docs for - :doc:`sqlalchemy-continuum:plugins`. - """ + def startup(self, config): + """ """ # only do this if config enables it - if not config.get_bool( - "wutta_continuum.enable_versioning", usedb=False, default=False - ): + if not config.get_bool('wutta_continuum.enable_versioning', + usedb=False, default=False): return # create wutta plugin, to assign user and ip address - spec = config.get( - "wutta_continuum.wutta_plugin_spec", - usedb=False, - default="wutta_continuum.conf:WuttaContinuumPlugin", - ) - plugin = load_object(spec) + spec = config.get('wutta_continuum.wutta_plugin_spec', + usedb=False, + default='wutta_continuum.conf:WuttaContinuumPlugin') + WuttaPlugin = load_object(spec) + # tell sqlalchemy-continuum to do its thing + make_versioned(plugins=[WuttaPlugin()]) + + # nb. must load the model before configuring mappers app = config.get_app() - if "model" in app.__dict__: - raise RuntimeError("something not right, app already has model") + model = app.model - # let sqlalchemy-continuum do its thing - make_versioned(plugins=[TransactionMetaPlugin(), plugin()]) - - # must load model *between* prev and next calls - app.get_model() - - # let sqlalchemy do its thing + # tell sqlalchemy to do its thing configure_mappers() @@ -116,85 +72,47 @@ class WuttaContinuumPlugin(Plugin): """ SQLAlchemy-Continuum manager plugin for Wutta-Continuum. - This is the default plugin used within - :meth:`~WuttaContinuumConfigExtension.startup()` unless config - overrides. + This tries to assign the current user and IP address to the + transaction. - This tries to establish the user and IP address responsible, and - comment if applicable, for the current transaction. + It will assume the "current machine" IP address, which may be + suitable for some apps but not all (e.g. web apps, where IP + address should reflect an arbitrary client machine). + + However it does not actually have a way to determine the current + user. WuttaWeb therefore uses a different plugin, based on this + one, to get both the user and IP address from current request. + + You can override this to use a custom plugin for this purpose; if + so you must specify in your config file: + + .. code-block:: ini + + [wutta_continuum] + wutta_plugin_spec = poser.db.continuum:PoserContinuumPlugin See also the SQLAlchemy-Continuum docs for :doc:`sqlalchemy-continuum:plugins`. """ - def get_remote_addr(self, uow, session): # pylint: disable=unused-argument - """ - This should return the effective IP address responsible for - the current change(s). - - Default logic will assume the "current machine" e.g. where a - CLI command or script is running. In practice that often - means this winds up being ``127.0.0.1`` or similar. - - :returns: IP address (v4 or v6) as string - """ + def get_remote_addr(self, uow, session): + """ """ host = socket.gethostname() return socket.gethostbyname(host) - def get_user_id(self, uow, session): # pylint: disable=unused-argument - """ - This should return the effective ``User.uuid`` indicating who - is responsible for the current change(s). - - Default logic does not have a way to determine current user on - its own per se. However it can inspect the session, and use a - value from there if found. - - Any session can therefore declare the resonsible user:: - - myuser = session.query(model.User).first() - session.info["continuum_user_id"] = myuser.uuid - - :returns: :attr:`wuttjamaican.db.model.auth.User.uuid` value, - or ``None`` - """ - if user_id := session.info.get("continuum_user_id"): - return user_id - - return None + def get_user_id(self, uow, session): + """ """ def transaction_args(self, uow, session): - """ - This is a standard hook method for SQLAchemy-Continuum - plugins. We use it to (try to) inject these values, which - then become set on the current (new) transaction: - - * ``remote_addr`` - effective IP address causing the change - * see :meth:`get_remote_addr()` - * ``user_id`` - effective ``User.uuid`` for change authorship - * see :meth:`get_user_id()` - """ + """ """ kwargs = {} remote_addr = self.get_remote_addr(uow, session) if remote_addr: - kwargs["remote_addr"] = remote_addr + kwargs['remote_addr'] = remote_addr - user_id = self.get_user_id(uow, session) # pylint: disable=assignment-from-none + user_id = self.get_user_id(uow, session) if user_id: - kwargs["user_id"] = user_id + 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/0a5f8ac0cd06_add_user_prevent_edit.py b/src/wutta_continuum/db/alembic/versions/0a5f8ac0cd06_add_user_prevent_edit.py deleted file mode 100644 index b2683e5..0000000 --- a/src/wutta_continuum/db/alembic/versions/0a5f8ac0cd06_add_user_prevent_edit.py +++ /dev/null @@ -1,34 +0,0 @@ -"""add user.prevent_edit - -Revision ID: 0a5f8ac0cd06 -Revises: 71406251b8e7 -Create Date: 2024-11-24 17:39:57.415425 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision: str = "0a5f8ac0cd06" -down_revision: Union[str, None] = "71406251b8e7" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - - # user - op.add_column( - "user_version", - sa.Column("prevent_edit", sa.Boolean(), autoincrement=False, nullable=True), - ) - - -def downgrade() -> None: - - # user - op.drop_column("user_version", "prevent_edit") 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 deleted file mode 100644 index ebaf636..0000000 --- a/src/wutta_continuum/db/alembic/versions/46fb4711411d_add_transaction_meta.py +++ /dev/null @@ -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") diff --git a/src/wutta_continuum/db/alembic/versions/71406251b8e7_first_versioning_tables.py b/src/wutta_continuum/db/alembic/versions/71406251b8e7_first_versioning_tables.py index 13909cb..fe54b41 100644 --- a/src/wutta_continuum/db/alembic/versions/71406251b8e7_first_versioning_tables.py +++ b/src/wutta_continuum/db/alembic/versions/71406251b8e7_first_versioning_tables.py @@ -1,296 +1,142 @@ """first versioning tables Revision ID: 71406251b8e7 -Revises: +Revises: Create Date: 2024-08-27 18:28:31.488291 """ - from typing import Sequence, Union from alembic import op import sqlalchemy as sa -import wuttjamaican.db.util # revision identifiers, used by Alembic. -revision: str = "71406251b8e7" +revision: str = '71406251b8e7' down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = ("wutta_continuum",) +branch_labels: Union[str, Sequence[str], None] = ('wutta_continuum',) depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # transaction - op.create_table( - "transaction", - sa.Column("issued_at", sa.DateTime(), nullable=True), - sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False), - sa.Column("remote_addr", sa.String(length=50), nullable=True), - sa.Column("user_id", wuttjamaican.db.util.UUID(), nullable=True), - sa.ForeignKeyConstraint( - ["user_id"], ["user.uuid"], name=op.f("fk_transaction_user_id_user") - ), - sa.PrimaryKeyConstraint("id", name=op.f("pk_transaction")), - ) - op.create_index( - op.f("ix_transaction_user_id"), "transaction", ["user_id"], unique=False - ) + op.create_table('transaction', + sa.Column('issued_at', sa.DateTime(), nullable=True), + sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False), + sa.Column('remote_addr', sa.String(length=50), nullable=True), + sa.Column('user_id', sa.String(length=32), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.uuid'], name=op.f('fk_transaction_user_id_user')), + sa.PrimaryKeyConstraint('id', name=op.f('pk_transaction')) + ) + op.create_index(op.f('ix_transaction_user_id'), 'transaction', ['user_id'], unique=False) # person - op.create_table( - "person_version", - sa.Column( - "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False - ), - sa.Column( - "full_name", sa.String(length=100), autoincrement=False, nullable=True - ), - sa.Column( - "first_name", sa.String(length=50), autoincrement=False, nullable=True - ), - sa.Column( - "middle_name", sa.String(length=50), autoincrement=False, nullable=True - ), - sa.Column( - "last_name", sa.String(length=50), autoincrement=False, nullable=True - ), - sa.Column( - "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False - ), - sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), - sa.Column("operation_type", sa.SmallInteger(), nullable=False), - sa.PrimaryKeyConstraint( - "uuid", "transaction_id", name=op.f("pk_person_version") - ), - ) - op.create_index( - op.f("ix_person_version_end_transaction_id"), - "person_version", - ["end_transaction_id"], - unique=False, - ) - op.create_index( - op.f("ix_person_version_operation_type"), - "person_version", - ["operation_type"], - unique=False, - ) - op.create_index( - op.f("ix_person_version_transaction_id"), - "person_version", - ["transaction_id"], - unique=False, - ) + op.create_table('person_version', + sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('full_name', sa.String(length=100), autoincrement=False, nullable=True), + sa.Column('first_name', sa.String(length=50), autoincrement=False, nullable=True), + sa.Column('middle_name', sa.String(length=50), autoincrement=False, nullable=True), + sa.Column('last_name', sa.String(length=50), autoincrement=False, nullable=True), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('uuid', 'transaction_id', name=op.f('pk_person_version')) + ) + op.create_index(op.f('ix_person_version_end_transaction_id'), 'person_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_person_version_operation_type'), 'person_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_person_version_transaction_id'), 'person_version', ['transaction_id'], unique=False) # user - op.create_table( - "user_version", - sa.Column( - "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False - ), - sa.Column("username", sa.String(length=25), autoincrement=False, nullable=True), - sa.Column("password", sa.String(length=60), autoincrement=False, nullable=True), - sa.Column( - "person_uuid", - wuttjamaican.db.util.UUID(), - autoincrement=False, - nullable=True, - ), - sa.Column("active", sa.Boolean(), autoincrement=False, nullable=True), - sa.Column( - "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False - ), - sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), - sa.Column("operation_type", sa.SmallInteger(), nullable=False), - sa.PrimaryKeyConstraint("uuid", "transaction_id", name=op.f("pk_user_version")), - ) - op.create_index( - op.f("ix_user_version_end_transaction_id"), - "user_version", - ["end_transaction_id"], - unique=False, - ) - op.create_index( - op.f("ix_user_version_operation_type"), - "user_version", - ["operation_type"], - unique=False, - ) - op.create_index( - op.f("ix_user_version_transaction_id"), - "user_version", - ["transaction_id"], - unique=False, - ) + op.create_table('user_version', + sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('username', sa.String(length=25), autoincrement=False, nullable=True), + sa.Column('password', sa.String(length=60), autoincrement=False, nullable=True), + sa.Column('person_uuid', sa.String(length=32), autoincrement=False, nullable=True), + sa.Column('active', sa.Boolean(), autoincrement=False, nullable=True), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('uuid', 'transaction_id', name=op.f('pk_user_version')) + ) + op.create_index(op.f('ix_user_version_end_transaction_id'), 'user_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_user_version_operation_type'), 'user_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_user_version_transaction_id'), 'user_version', ['transaction_id'], unique=False) # role - op.create_table( - "role_version", - sa.Column( - "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False - ), - sa.Column("name", sa.String(length=100), autoincrement=False, nullable=True), - sa.Column("notes", sa.Text(), autoincrement=False, nullable=True), - sa.Column( - "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False - ), - sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), - sa.Column("operation_type", sa.SmallInteger(), nullable=False), - sa.PrimaryKeyConstraint("uuid", "transaction_id", name=op.f("pk_role_version")), - ) - op.create_index( - op.f("ix_role_version_end_transaction_id"), - "role_version", - ["end_transaction_id"], - unique=False, - ) - op.create_index( - op.f("ix_role_version_operation_type"), - "role_version", - ["operation_type"], - unique=False, - ) - op.create_index( - op.f("ix_role_version_transaction_id"), - "role_version", - ["transaction_id"], - unique=False, - ) + op.create_table('role_version', + sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('name', sa.String(length=100), autoincrement=False, nullable=True), + sa.Column('notes', sa.Text(), autoincrement=False, nullable=True), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('uuid', 'transaction_id', name=op.f('pk_role_version')) + ) + op.create_index(op.f('ix_role_version_end_transaction_id'), 'role_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_role_version_operation_type'), 'role_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_role_version_transaction_id'), 'role_version', ['transaction_id'], unique=False) # user_x_role - op.create_table( - "user_x_role_version", - sa.Column( - "uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=False - ), - sa.Column( - "user_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True - ), - sa.Column( - "role_uuid", wuttjamaican.db.util.UUID(), autoincrement=False, nullable=True - ), - sa.Column( - "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False - ), - sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), - sa.Column("operation_type", sa.SmallInteger(), nullable=False), - sa.PrimaryKeyConstraint( - "uuid", "transaction_id", name=op.f("pk_user_x_role_version") - ), - ) - op.create_index( - op.f("ix_user_x_role_version_end_transaction_id"), - "user_x_role_version", - ["end_transaction_id"], - unique=False, - ) - op.create_index( - op.f("ix_user_x_role_version_operation_type"), - "user_x_role_version", - ["operation_type"], - unique=False, - ) - op.create_index( - op.f("ix_user_x_role_version_transaction_id"), - "user_x_role_version", - ["transaction_id"], - unique=False, - ) + op.create_table('user_x_role_version', + sa.Column('uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('user_uuid', sa.String(length=32), autoincrement=False, nullable=True), + sa.Column('role_uuid', sa.String(length=32), autoincrement=False, nullable=True), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('uuid', 'transaction_id', name=op.f('pk_user_x_role_version')) + ) + op.create_index(op.f('ix_user_x_role_version_end_transaction_id'), 'user_x_role_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_user_x_role_version_operation_type'), 'user_x_role_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_user_x_role_version_transaction_id'), 'user_x_role_version', ['transaction_id'], unique=False) # permission - op.create_table( - "permission_version", - sa.Column( - "role_uuid", - wuttjamaican.db.util.UUID(), - autoincrement=False, - nullable=False, - ), - sa.Column( - "permission", sa.String(length=254), autoincrement=False, nullable=False - ), - sa.Column( - "transaction_id", sa.BigInteger(), autoincrement=False, nullable=False - ), - sa.Column("end_transaction_id", sa.BigInteger(), nullable=True), - sa.Column("operation_type", sa.SmallInteger(), nullable=False), - sa.PrimaryKeyConstraint( - "role_uuid", - "permission", - "transaction_id", - name=op.f("pk_permission_version"), - ), - ) - op.create_index( - op.f("ix_permission_version_end_transaction_id"), - "permission_version", - ["end_transaction_id"], - unique=False, - ) - op.create_index( - op.f("ix_permission_version_operation_type"), - "permission_version", - ["operation_type"], - unique=False, - ) - op.create_index( - op.f("ix_permission_version_transaction_id"), - "permission_version", - ["transaction_id"], - unique=False, - ) + op.create_table('permission_version', + sa.Column('role_uuid', sa.String(length=32), autoincrement=False, nullable=False), + sa.Column('permission', sa.String(length=254), autoincrement=False, nullable=False), + sa.Column('transaction_id', sa.BigInteger(), autoincrement=False, nullable=False), + sa.Column('end_transaction_id', sa.BigInteger(), nullable=True), + sa.Column('operation_type', sa.SmallInteger(), nullable=False), + sa.PrimaryKeyConstraint('role_uuid', 'permission', 'transaction_id', name=op.f('pk_permission_version')) + ) + op.create_index(op.f('ix_permission_version_end_transaction_id'), 'permission_version', ['end_transaction_id'], unique=False) + op.create_index(op.f('ix_permission_version_operation_type'), 'permission_version', ['operation_type'], unique=False) + op.create_index(op.f('ix_permission_version_transaction_id'), 'permission_version', ['transaction_id'], unique=False) def downgrade() -> None: # permission - op.drop_index( - op.f("ix_permission_version_transaction_id"), table_name="permission_version" - ) - op.drop_index( - op.f("ix_permission_version_operation_type"), table_name="permission_version" - ) - op.drop_index( - op.f("ix_permission_version_end_transaction_id"), - table_name="permission_version", - ) - op.drop_table("permission_version") + op.drop_index(op.f('ix_permission_version_transaction_id'), table_name='permission_version') + op.drop_index(op.f('ix_permission_version_operation_type'), table_name='permission_version') + op.drop_index(op.f('ix_permission_version_end_transaction_id'), table_name='permission_version') + op.drop_table('permission_version') # user_x_role - op.drop_index( - op.f("ix_user_x_role_version_transaction_id"), table_name="user_x_role_version" - ) - op.drop_index( - op.f("ix_user_x_role_version_operation_type"), table_name="user_x_role_version" - ) - op.drop_index( - op.f("ix_user_x_role_version_end_transaction_id"), - table_name="user_x_role_version", - ) - op.drop_table("user_x_role_version") + op.drop_index(op.f('ix_user_x_role_version_transaction_id'), table_name='user_x_role_version') + op.drop_index(op.f('ix_user_x_role_version_operation_type'), table_name='user_x_role_version') + op.drop_index(op.f('ix_user_x_role_version_end_transaction_id'), table_name='user_x_role_version') + op.drop_table('user_x_role_version') # role - op.drop_index(op.f("ix_role_version_transaction_id"), table_name="role_version") - op.drop_index(op.f("ix_role_version_operation_type"), table_name="role_version") - op.drop_index(op.f("ix_role_version_end_transaction_id"), table_name="role_version") - op.drop_table("role_version") + op.drop_index(op.f('ix_role_version_transaction_id'), table_name='role_version') + op.drop_index(op.f('ix_role_version_operation_type'), table_name='role_version') + op.drop_index(op.f('ix_role_version_end_transaction_id'), table_name='role_version') + op.drop_table('role_version') # user - op.drop_index(op.f("ix_user_version_transaction_id"), table_name="user_version") - op.drop_index(op.f("ix_user_version_operation_type"), table_name="user_version") - op.drop_index(op.f("ix_user_version_end_transaction_id"), table_name="user_version") - op.drop_table("user_version") + op.drop_index(op.f('ix_user_version_transaction_id'), table_name='user_version') + op.drop_index(op.f('ix_user_version_operation_type'), table_name='user_version') + op.drop_index(op.f('ix_user_version_end_transaction_id'), table_name='user_version') + op.drop_table('user_version') # person - op.drop_index(op.f("ix_person_version_transaction_id"), table_name="person_version") - op.drop_index(op.f("ix_person_version_operation_type"), table_name="person_version") - op.drop_index( - op.f("ix_person_version_end_transaction_id"), table_name="person_version" - ) - op.drop_table("person_version") + op.drop_index(op.f('ix_person_version_transaction_id'), table_name='person_version') + op.drop_index(op.f('ix_person_version_operation_type'), table_name='person_version') + op.drop_index(op.f('ix_person_version_end_transaction_id'), table_name='person_version') + op.drop_table('person_version') # transaction - op.drop_index(op.f("ix_transaction_user_id"), table_name="transaction") - op.drop_table("transaction") + op.drop_index(op.f('ix_transaction_user_id'), table_name='transaction') + op.drop_table('transaction') diff --git a/src/wutta_continuum/db/alembic/versions/989392cc191d_remove_password.py b/src/wutta_continuum/db/alembic/versions/989392cc191d_remove_password.py deleted file mode 100644 index 545f75a..0000000 --- a/src/wutta_continuum/db/alembic/versions/989392cc191d_remove_password.py +++ /dev/null @@ -1,37 +0,0 @@ -"""remove password - -Revision ID: 989392cc191d -Revises: 0a5f8ac0cd06 -Create Date: 2025-10-29 19:42:52.985167 - -""" - -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -import wuttjamaican.db.util - - -# revision identifiers, used by Alembic. -revision: str = "989392cc191d" -down_revision: Union[str, None] = "0a5f8ac0cd06" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - - # user - op.drop_column("user_version", "password") - - -def downgrade() -> None: - - # user - op.add_column( - "user_version", - sa.Column( - "password", sa.VARCHAR(length=60), autoincrement=False, nullable=True - ), - ) 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/tasks.py b/tasks.py index c55d669..7e7734b 100644 --- a/tasks.py +++ b/tasks.py @@ -15,10 +15,10 @@ def release(c, skip_tests=False): Release a new version of Wutta-Continuum """ if not skip_tests: - c.run("pytest") + c.run('pytest') - if os.path.exists("dist"): - shutil.rmtree("dist") + if os.path.exists('dist'): + shutil.rmtree('dist') - c.run("python -m build --sdist") - c.run("twine upload dist/*") + c.run('python -m build --sdist') + c.run('twine upload dist/*') diff --git a/tests/test_app.py b/tests/test_app.py index 4efb525..8d41b25 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -17,5 +17,5 @@ class TestWuttaContinuumAppProvider(DataTestCase): self.assertFalse(provider.continuum_is_enabled()) # but can be turned on - self.config.setdefault("wutta_continuum.enable_versioning", "true") + self.config.setdefault('wutta_continuum.enable_versioning', 'true') self.assertTrue(provider.continuum_is_enabled()) diff --git a/tests/test_conf.py b/tests/test_conf.py index 03485c2..1f9236b 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -2,47 +2,35 @@ import socket -from unittest.mock import patch, Mock +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: + + 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: - self.config.setdefault("wutta_continuum.enable_versioning", "true") + # 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): @@ -51,51 +39,20 @@ class TestWuttaContinuumPlugin(DataTestCase): def test_remote_addr(self): plugin = self.make_plugin() - with patch.object(socket, "gethostbyname", return_value="127.0.0.1"): - self.assertEqual(plugin.get_remote_addr(None, self.session), "127.0.0.1") + with patch.object(socket, 'gethostbyname', return_value='127.0.0.1'): + self.assertEqual(plugin.get_remote_addr(None, self.session), '127.0.0.1') def test_user_id(self): - model = self.app.model plugin = self.make_plugin() - - fred = model.User(username="fred") - self.session.add(fred) - self.session.commit() - - # empty by default self.assertIsNone(plugin.get_user_id(None, self.session)) - # but session can declare one - self.session.info["continuum_user_id"] = fred.uuid - self.assertEqual(plugin.get_user_id(None, self.session), fred.uuid) - def test_transaction_args(self): plugin = self.make_plugin() - with patch.object(socket, "gethostbyname", return_value="127.0.0.1"): - self.assertEqual( - plugin.transaction_args(None, self.session), - {"remote_addr": "127.0.0.1"}, - ) + with patch.object(socket, 'gethostbyname', return_value='127.0.0.1'): + self.assertEqual(plugin.transaction_args(None, self.session), + {'remote_addr': '127.0.0.1'}) - with patch.object(plugin, "get_user_id", return_value="some-random-uuid"): - self.assertEqual( - 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") + with patch.object(plugin, 'get_user_id', return_value='some-random-uuid'): + self.assertEqual(plugin.transaction_args(None, self.session), + {'remote_addr': '127.0.0.1', + 'user_id': 'some-random-uuid'}) 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) diff --git a/tox.ini b/tox.ini index 460f86b..3e2d218 100644 --- a/tox.ini +++ b/tox.ini @@ -6,10 +6,6 @@ envlist = py38, py39, py310, py311 extras = tests commands = pytest {posargs} -[testenv:pylint] -basepython = python3.11 -commands = pylint wutta_continuum - [testenv:coverage] basepython = python3.11 commands = pytest --cov=wutta_continuum --cov-report=html --cov-fail-under=100