1
0
Fork 0

fix: add basic support for wutta-continuum data versioning/history

not much "support" per se in here, mostly some stubs to allow for
smooth operation if/when it is installed
This commit is contained in:
Lance Edgar 2024-08-27 20:26:22 -05:00
parent 7002986cb7
commit 2fa82bee8c
8 changed files with 72 additions and 23 deletions

View file

@ -35,6 +35,7 @@ intersphinx_mapping = {
'rattail': ('https://rattailproject.org/docs/rattail/', None), 'rattail': ('https://rattailproject.org/docs/rattail/', None),
'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None), 'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None),
'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None),
'wutta-continuum': ('https://rattailproject.org/docs/wutta-continuum/', None),
} }

View file

@ -2,18 +2,12 @@
WuttJamaican WuttJamaican
============ ============
This package provides a "base layer" for custom apps, regardless of This package aims to provide a "base layer" for apps regardless of
environment/platform: platform or environment (console, web, GUI).
* console It comes from patterns developed within the `Rattail Project`_, and
* web roughly corresponds with the "base and data layers" as described in
* GUI :doc:`rattail-manual:index`.
It mostly is a distillation of certain patterns developed within the
`Rattail Project`_, which are deemed generally useful. (At least,
according to the author.) It roughly corresponds to the "base layer"
as described in the Rattail Manual (see
:doc:`rattail-manual:base/index`).
.. _Rattail Project: https://rattailproject.org/ .. _Rattail Project: https://rattailproject.org/
@ -22,9 +16,6 @@ project.
.. _test coverage: https://buildbot.rattailproject.org/coverage/wuttjamaican/ .. _test coverage: https://buildbot.rattailproject.org/coverage/wuttjamaican/
Rattail is still the main use case so far, and will be refactored
along the way to incorporate what this package has to offer.
Features Features
-------- --------
@ -35,6 +26,11 @@ Features
.. _SQLAlchemy: https://www.sqlalchemy.org .. _SQLAlchemy: https://www.sqlalchemy.org
See also these projects which build on WuttJamaican:
* :doc:`wutta-continuum:index`
* `WuttaWeb <https://rattailproject.org/docs/wuttaweb/>`_
Contents Contents
-------- --------

View file

@ -590,6 +590,21 @@ class AppHandler:
if setting: if setting:
session.delete(setting) session.delete(setting)
def continuum_is_enabled(self):
"""
Returns boolean indicating if Wutta-Continuum is installed and
enabled.
Default will be ``False`` as enabling it requires additional
installation and setup. For instructions see
:doc:`wutta-continuum:narr/install`.
"""
for provider in self.providers.values():
if hasattr(provider, 'continuum_is_enabled'):
return provider.continuum_is_enabled()
return False
############################## ##############################
# getters for other handlers # getters for other handlers
############################## ##############################

View file

@ -625,7 +625,7 @@ class WuttaConfig:
class WuttaConfigExtension: class WuttaConfigExtension:
""" """
Base class for all config extensions. Base class for all :term:`config extensions <config extension>`.
""" """
key = None key = None
@ -638,6 +638,17 @@ class WuttaConfigExtension:
object in any way necessary. object in any way necessary.
""" """
def startup(self, config):
"""
This method is called after the config object is fully created
and all extensions have been applied, i.e. after
:meth:`configure()` has been called for each extension.
At this point the config *settings* for the running app should
be settled, and each extension is then allowed to act on those
initial settings if needed.
"""
def generic_default_files(appname): def generic_default_files(appname):
""" """
@ -962,8 +973,13 @@ def make_config(
# apply all registered extensions # apply all registered extensions
# TODO: maybe let config disable some extensions? # TODO: maybe let config disable some extensions?
extensions = load_entry_points(extension_entry_points) extensions = load_entry_points(extension_entry_points)
for extension in extensions.values(): extensions = [ext() for ext in extensions.values()]
for extension in extensions:
log.debug("applying config extension: %s", extension.key) log.debug("applying config extension: %s", extension.key)
extension().configure(config) extension.configure(config)
# let extensions run startup hooks if needed
for extension in extensions:
extension.startup(config)
return config return config

View file

@ -65,6 +65,7 @@ class Role(Base):
See also :attr:`user_refs`. See also :attr:`user_refs`.
""" """
__tablename__ = 'role' __tablename__ = 'role'
__versioned__ = {}
uuid = uuid_column() uuid = uuid_column()
@ -122,6 +123,7 @@ class Permission(Base):
Represents a permission granted to a role. Represents a permission granted to a role.
""" """
__tablename__ = 'permission' __tablename__ = 'permission'
__versioned__ = {}
role_uuid = uuid_fk_column('role.uuid', primary_key=True, nullable=False) role_uuid = uuid_fk_column('role.uuid', primary_key=True, nullable=False)
role = orm.relationship( role = orm.relationship(
@ -155,6 +157,7 @@ class User(Base):
See also :attr:`role_refs`. See also :attr:`role_refs`.
""" """
__tablename__ = 'user' __tablename__ = 'user'
__versioned__ = {}
uuid = uuid_column() uuid = uuid_column()
@ -217,6 +220,7 @@ class UserRole(Base):
user "belongs" or "is assigned" to the role. user "belongs" or "is assigned" to the role.
""" """
__tablename__ = 'user_x_role' __tablename__ = 'user_x_role'
__versioned__ = {}
uuid = uuid_column() uuid = uuid_column()

View file

@ -118,6 +118,7 @@ class Person(Base):
Employee relationship etc. Employee relationship etc.
""" """
__tablename__ = 'person' __tablename__ = 'person'
__versioned__ = {}
uuid = uuid_column() uuid = uuid_column()

View file

@ -200,6 +200,19 @@ class TestAppHandler(FileTestCase):
value = self.app.get_setting(session, 'foo') value = self.app.get_setting(session, 'foo')
self.assertIsNone(value) self.assertIsNone(value)
def test_continuum_is_enabled(self):
# false by default
with patch.object(self.app, 'providers', new={}):
self.assertFalse(self.app.continuum_is_enabled())
# but "any" provider technically could enable it...
class MockProvider:
def continuum_is_enabled(self):
return True
with patch.object(self.app, 'providers', new={'mock': MockProvider()}):
self.assertTrue(self.app.continuum_is_enabled())
def test_model(self): def test_model(self):
try: try:
from wuttjamaican.db import model from wuttjamaican.db import model

View file

@ -7,6 +7,8 @@ from unittest.mock import patch, MagicMock
import pytest import pytest
from wuttjamaican import conf as mod
# TODO: get rid of this eventually
from wuttjamaican import conf from wuttjamaican import conf
from wuttjamaican.exc import ConfigurationError from wuttjamaican.exc import ConfigurationError
from wuttjamaican.app import AppHandler from wuttjamaican.app import AppHandler
@ -673,11 +675,11 @@ class TestMakeConfig(FileTestCase):
generic = self.write_file('generic.conf', '') generic = self.write_file('generic.conf', '')
myfile = self.write_file('my.conf', '') myfile = self.write_file('my.conf', '')
with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: with patch.object(mod, 'WuttaConfig') as WuttaConfig:
with patch('wuttjamaican.conf.load_entry_points') as load_entry_points: with patch.object(mod, 'load_entry_points') as load_entry_points:
# no entry points loaded if extend=False # no entry points loaded if extend=False
config = conf.make_config(appname='wuttatest', extend=False) config = mod.make_config(appname='wuttatest', extend=False)
WuttaConfig.assert_called_once_with([], appname='wuttatest', WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None) usedb=None, preferdb=None)
load_entry_points.assert_not_called() load_entry_points.assert_not_called()
@ -685,7 +687,7 @@ class TestMakeConfig(FileTestCase):
# confirm entry points for default appname # confirm entry points for default appname
load_entry_points.reset_mock() load_entry_points.reset_mock()
WuttaConfig.reset_mock() WuttaConfig.reset_mock()
config = conf.make_config([], appname='wutta') config = mod.make_config([], appname='wutta')
WuttaConfig.assert_called_once_with([], appname='wutta', WuttaConfig.assert_called_once_with([], appname='wutta',
usedb=None, preferdb=None) usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wutta.config.extensions') load_entry_points.assert_called_once_with('wutta.config.extensions')
@ -693,7 +695,7 @@ class TestMakeConfig(FileTestCase):
# confirm entry points for custom appname # confirm entry points for custom appname
load_entry_points.reset_mock() load_entry_points.reset_mock()
WuttaConfig.reset_mock() WuttaConfig.reset_mock()
config = conf.make_config(appname='wuttatest') config = mod.make_config(appname='wuttatest')
WuttaConfig.assert_called_once_with([], appname='wuttatest', WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None) usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wuttatest.config.extensions') load_entry_points.assert_called_once_with('wuttatest.config.extensions')
@ -706,9 +708,10 @@ class TestMakeConfig(FileTestCase):
WuttaConfig.reset_mock() WuttaConfig.reset_mock()
testconfig = MagicMock() testconfig = MagicMock()
WuttaConfig.return_value = testconfig WuttaConfig.return_value = testconfig
config = conf.make_config(appname='wuttatest') config = mod.make_config(appname='wuttatest')
WuttaConfig.assert_called_once_with([], appname='wuttatest', WuttaConfig.assert_called_once_with([], appname='wuttatest',
usedb=None, preferdb=None) usedb=None, preferdb=None)
load_entry_points.assert_called_once_with('wuttatest.config.extensions') load_entry_points.assert_called_once_with('wuttatest.config.extensions')
foo_cls.assert_called_once_with() foo_cls.assert_called_once_with()
foo_obj.configure.assert_called_once_with(testconfig) foo_obj.configure.assert_called_once_with(testconfig)
foo_obj.startup.assert_called_once_with(testconfig)