Compare commits
16 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b858ba1793 | |||
| 2c57d8ba50 | |||
| 53b35f1f55 | |||
| 4db3fa5962 | |||
| 0e25cca0ba | |||
| b89684cfc0 | |||
| 1b01df79e2 | |||
| e20ef31ff7 | |||
| 39dc66df40 | |||
| 6baca8c6b1 | |||
| bec16f4de2 | |||
| 77d1b58160 | |||
|
|
94a554ebc9 | ||
|
|
2a6dbfacd3 | ||
|
|
7dd4eb587b | ||
|
|
8e78cd3253 |
22 changed files with 747 additions and 167 deletions
4
.pylintrc
Normal file
4
.pylintrc
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# -*- mode: conf; -*-
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
disable=fixme
|
||||
30
CHANGELOG.md
30
CHANGELOG.md
|
|
@ -5,6 +5,36 @@ 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.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
|
||||
|
||||
- fix nullable flags for initial version tables
|
||||
|
||||
## v0.1.0 (2024-08-27)
|
||||
|
||||
### Feat
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
# Wutta-Continuum
|
||||
|
||||
SQLAlchemy-Continuum versioning for WuttJamaican
|
||||
SQLAlchemy-Continuum versioning for Wutta Framework
|
||||
|
||||
See docs at https://rattailproject.org/docs/wutta-continuum/
|
||||
|
|
|
|||
6
docs/api/wutta_continuum.testing.rst
Normal file
6
docs/api/wutta_continuum.testing.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wutta_continuum.testing``
|
||||
===========================
|
||||
|
||||
.. automodule:: wutta_continuum.testing
|
||||
:members:
|
||||
6
docs/api/wutta_continuum.util.rst
Normal file
6
docs/api/wutta_continuum.util.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
``wutta_continuum.util``
|
||||
========================
|
||||
|
||||
.. automodule:: wutta_continuum.util
|
||||
:members:
|
||||
32
docs/conf.py
32
docs/conf.py
|
|
@ -8,32 +8,36 @@
|
|||
|
||||
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-continuum': ('https://sqlalchemy-continuum.readthedocs.io/en/latest/', None),
|
||||
'wuttjamaican': ('https://rattailproject.org/docs/wuttjamaican/', None),
|
||||
"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),
|
||||
}
|
||||
|
||||
|
||||
# -- 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"]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@ This package adds data versioning/history for `WuttJamaican`_, using
|
|||
|
||||
.. _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
|
||||
|
|
@ -23,3 +29,5 @@ 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
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "Wutta-Continuum"
|
||||
version = "0.1.0"
|
||||
description = "SQLAlchemy-Continuum versioning for WuttJamaican"
|
||||
version = "0.2.2"
|
||||
description = "SQLAlchemy-Continuum versioning for Wutta Framework"
|
||||
readme = "README.md"
|
||||
authors = [{name = "Lance Edgar", email = "lance@edbob.org"}]
|
||||
authors = [{name = "Lance Edgar", email = "lance@wuttaproject.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]",
|
||||
"WuttJamaican[db]>=0.24.1",
|
||||
]
|
||||
|
||||
|
||||
[project.optional-dependencies]
|
||||
docs = ["Sphinx", "furo"]
|
||||
tests = ["pytest-cov", "tox"]
|
||||
tests = ["pylint", "pytest", "pytest-cov", "tox"]
|
||||
|
||||
|
||||
[project.entry-points."wutta.app.providers"]
|
||||
|
|
@ -47,6 +47,7 @@ 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"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
#
|
||||
################################################################################
|
||||
"""
|
||||
Wutta-Continuum -- SQLAlchemy-Continuum versioning for WuttJamaican
|
||||
Wutta-Continuum -- SQLAlchemy-Continuum versioning for Wutta Framework
|
||||
"""
|
||||
|
||||
from ._version import __version__
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Package Version
|
||||
"""
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
__version__ = version('Wutta-Continuum')
|
||||
__version__ = version("Wutta-Continuum")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework
|
||||
# Copyright © 2024 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
|
|
@ -40,5 +40,6 @@ 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Copyright © 2024 Lance Edgar
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for Wutta Framework
|
||||
# Copyright © 2024-2025 Lance Edgar
|
||||
#
|
||||
# This file is part of Wutta Framework.
|
||||
#
|
||||
|
|
@ -24,7 +24,6 @@
|
|||
App Configuration
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import socket
|
||||
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
|
|
@ -42,29 +41,57 @@ 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'
|
||||
|
||||
def startup(self, config):
|
||||
""" """
|
||||
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):
|
||||
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')
|
||||
WuttaPlugin = load_object(spec)
|
||||
spec = config.get(
|
||||
"wutta_continuum.wutta_plugin_spec",
|
||||
usedb=False,
|
||||
default="wutta_continuum.conf:WuttaContinuumPlugin",
|
||||
)
|
||||
plugin = 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()
|
||||
model = app.model
|
||||
if "model" in app.__dict__:
|
||||
raise RuntimeError("something not right, app already has model")
|
||||
|
||||
# tell sqlalchemy to do its thing
|
||||
# let sqlalchemy-continuum do its thing
|
||||
make_versioned(plugins=[plugin()])
|
||||
|
||||
# must load model *between* prev and next calls
|
||||
app.get_model()
|
||||
|
||||
# let sqlalchemy do its thing
|
||||
configure_mappers()
|
||||
|
||||
|
||||
|
|
@ -95,24 +122,29 @@ class WuttaContinuumPlugin(Plugin):
|
|||
:doc:`sqlalchemy-continuum:plugins`.
|
||||
"""
|
||||
|
||||
def get_remote_addr(self, uow, session):
|
||||
def get_remote_addr( # pylint: disable=empty-docstring,unused-argument
|
||||
self, uow, session
|
||||
):
|
||||
""" """
|
||||
host = socket.gethostname()
|
||||
return socket.gethostbyname(host)
|
||||
|
||||
def get_user_id(self, uow, session):
|
||||
def get_user_id( # pylint: disable=empty-docstring,unused-argument
|
||||
self, uow, session
|
||||
):
|
||||
""" """
|
||||
return None
|
||||
|
||||
def transaction_args(self, uow, session):
|
||||
def transaction_args(self, uow, session): # pylint: disable=empty-docstring
|
||||
""" """
|
||||
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)
|
||||
user_id = self.get_user_id(uow, session) # pylint: disable=assignment-from-none
|
||||
if user_id:
|
||||
kwargs['user_id'] = user_id
|
||||
kwargs["user_id"] = user_id
|
||||
|
||||
return kwargs
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
"""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")
|
||||
|
|
@ -1,142 +1,296 @@
|
|||
"""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', 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)
|
||||
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
|
||||
)
|
||||
|
||||
# person
|
||||
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=False),
|
||||
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", 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,
|
||||
)
|
||||
|
||||
# user
|
||||
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=False),
|
||||
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=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('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", 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,
|
||||
)
|
||||
|
||||
# role
|
||||
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=False),
|
||||
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", 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,
|
||||
)
|
||||
|
||||
# user_x_role
|
||||
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=False),
|
||||
sa.Column('role_uuid', sa.String(length=32), 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('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", 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,
|
||||
)
|
||||
|
||||
# permission
|
||||
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)
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
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")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
"""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
|
||||
),
|
||||
)
|
||||
92
src/wutta_continuum/testing.py
Normal file
92
src/wutta_continuum/testing.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
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
|
||||
85
src/wutta_continuum/util.py
Normal file
85
src/wutta_continuum/util.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
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
|
||||
24
tasks.py
Normal file
24
tasks.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
"""
|
||||
Tasks for Wutta-Continuum
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from invoke import task
|
||||
|
||||
|
||||
@task
|
||||
def release(c, skip_tests=False):
|
||||
"""
|
||||
Release a new version of Wutta-Continuum
|
||||
"""
|
||||
if not skip_tests:
|
||||
c.run("pytest")
|
||||
|
||||
if os.path.exists("dist"):
|
||||
shutil.rmtree("dist")
|
||||
|
||||
c.run("python -m build --sdist")
|
||||
c.run("twine upload dist/*")
|
||||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -4,33 +4,45 @@ import socket
|
|||
|
||||
from unittest.mock import patch
|
||||
|
||||
from wuttjamaican.testing import DataTestCase
|
||||
from wuttjamaican.testing import ConfigTestCase, DataTestCase
|
||||
|
||||
from wutta_continuum import conf as mod
|
||||
|
||||
|
||||
class TestWuttaContinuumConfigExtension(DataTestCase):
|
||||
class TestWuttaContinuumConfigExtension(ConfigTestCase):
|
||||
|
||||
def make_extension(self):
|
||||
return mod.WuttaContinuumConfigExtension()
|
||||
|
||||
def test_startup(self):
|
||||
def test_startup_without_versioning(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
|
||||
with patch.object(mod, "make_versioned") as make_versioned:
|
||||
with patch.object(mod, "configure_mappers") as configure_mappers:
|
||||
ext.startup(self.config)
|
||||
make_versioned.assert_not_called()
|
||||
configure_mappers.assert_not_called()
|
||||
|
||||
# but will if we enable it in config
|
||||
self.config.setdefault('wutta_continuum.enable_versioning', 'true')
|
||||
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")
|
||||
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):
|
||||
|
||||
|
|
@ -39,8 +51,8 @@ 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):
|
||||
plugin = self.make_plugin()
|
||||
|
|
@ -48,11 +60,14 @@ class TestWuttaContinuumPlugin(DataTestCase):
|
|||
|
||||
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'})
|
||||
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"},
|
||||
)
|
||||
|
|
|
|||
40
tests/test_util.py
Normal file
40
tests/test_util.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# -*- 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)
|
||||
4
tox.ini
4
tox.ini
|
|
@ -6,6 +6,10 @@ 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue