feat: initial release
This commit is contained in:
commit
9dfe2a8d6b
24 changed files with 1370 additions and 0 deletions
27
src/wutta_continuum/__init__.py
Normal file
27
src/wutta_continuum/__init__.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Copyright © 2024 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
Wutta-Continuum -- SQLAlchemy-Continuum versioning for WuttJamaican
|
||||
"""
|
||||
|
||||
from ._version import __version__
|
6
src/wutta_continuum/_version.py
Normal file
6
src/wutta_continuum/_version.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
|
||||
from importlib.metadata import version
|
||||
|
||||
|
||||
__version__ = version('Wutta-Continuum')
|
44
src/wutta_continuum/app.py
Normal file
44
src/wutta_continuum/app.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Copyright © 2024 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
App Provider
|
||||
"""
|
||||
|
||||
from wuttjamaican.app import AppProvider
|
||||
|
||||
|
||||
class WuttaContinuumAppProvider(AppProvider):
|
||||
"""
|
||||
The :term:`app provider` for WuttaWeb. This adds some methods to
|
||||
the :term:`app handler`, which are specific to Wutta-Continuum.
|
||||
"""
|
||||
|
||||
def continuum_is_enabled(self):
|
||||
"""
|
||||
Returns boolean indicating if Wutta-Continuum is enabled.
|
||||
|
||||
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)
|
118
src/wutta_continuum/conf.py
Normal file
118
src/wutta_continuum/conf.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: utf-8; -*-
|
||||
################################################################################
|
||||
#
|
||||
# Wutta-Continuum -- SQLAlchemy Versioning for WuttJamaican
|
||||
# Copyright © 2024 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/>.
|
||||
#
|
||||
################################################################################
|
||||
"""
|
||||
App Configuration
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import socket
|
||||
|
||||
from sqlalchemy.orm import configure_mappers
|
||||
from sqlalchemy_continuum import make_versioned
|
||||
from sqlalchemy_continuum.plugins import Plugin
|
||||
|
||||
from wuttjamaican.conf import WuttaConfigExtension
|
||||
from wuttjamaican.util import load_object
|
||||
|
||||
|
||||
class WuttaContinuumConfigExtension(WuttaConfigExtension):
|
||||
"""
|
||||
App :term:`config extension` for Wutta-Continuum.
|
||||
|
||||
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):
|
||||
""" """
|
||||
# only do this if config enables it
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
# tell sqlalchemy to do its thing
|
||||
configure_mappers()
|
||||
|
||||
|
||||
class WuttaContinuumPlugin(Plugin):
|
||||
"""
|
||||
SQLAlchemy-Continuum manager plugin for Wutta-Continuum.
|
||||
|
||||
This tries to assign the current user and IP address to the
|
||||
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):
|
||||
""" """
|
||||
host = socket.gethostname()
|
||||
return socket.gethostbyname(host)
|
||||
|
||||
def get_user_id(self, uow, session):
|
||||
""" """
|
||||
|
||||
def transaction_args(self, uow, session):
|
||||
""" """
|
||||
kwargs = {}
|
||||
|
||||
remote_addr = self.get_remote_addr(uow, session)
|
||||
if remote_addr:
|
||||
kwargs['remote_addr'] = remote_addr
|
||||
|
||||
user_id = self.get_user_id(uow, session)
|
||||
if user_id:
|
||||
kwargs['user_id'] = user_id
|
||||
|
||||
return kwargs
|
0
src/wutta_continuum/db/__init__.py
Normal file
0
src/wutta_continuum/db/__init__.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
"""first versioning tables
|
||||
|
||||
Revision ID: 71406251b8e7
|
||||
Revises:
|
||||
Create Date: 2024-08-27 18:28:31.488291
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '71406251b8e7'
|
||||
down_revision: Union[str, None] = None
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
# 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')
|
||||
|
||||
# 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')
|
||||
|
||||
# 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')
|
||||
|
||||
# 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')
|
||||
|
||||
# transaction
|
||||
op.drop_index(op.f('ix_transaction_user_id'), table_name='transaction')
|
||||
op.drop_table('transaction')
|
Loading…
Add table
Add a link
Reference in a new issue