diff --git a/docs/conf.py b/docs/conf.py index 23fc2cf..cbee050 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -35,6 +35,7 @@ intersphinx_mapping = { 'rattail': ('https://rattailproject.org/docs/rattail/', None), 'rattail-manual': ('https://rattailproject.org/docs/rattail-manual/', None), 'sqlalchemy': ('http://docs.sqlalchemy.org/en/latest/', None), + 'wutta-continuum': ('https://rattailproject.org/docs/wutta-continuum/', None), } diff --git a/docs/index.rst b/docs/index.rst index 3da6f92..d2a6bd4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,18 +2,12 @@ WuttJamaican ============ -This package provides a "base layer" for custom apps, regardless of -environment/platform: +This package aims to provide a "base layer" for apps regardless of +platform or environment (console, web, GUI). -* console -* web -* GUI - -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`). +It comes from patterns developed within the `Rattail Project`_, and +roughly corresponds with the "base and data layers" as described in +:doc:`rattail-manual:index`. .. _Rattail Project: https://rattailproject.org/ @@ -22,9 +16,6 @@ project. .. _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 -------- @@ -35,6 +26,11 @@ Features .. _SQLAlchemy: https://www.sqlalchemy.org +See also these projects which build on WuttJamaican: + +* :doc:`wutta-continuum:index` +* `WuttaWeb `_ + Contents -------- diff --git a/src/wuttjamaican/app.py b/src/wuttjamaican/app.py index 5d67df2..98aa8ab 100644 --- a/src/wuttjamaican/app.py +++ b/src/wuttjamaican/app.py @@ -590,6 +590,21 @@ class AppHandler: if 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 ############################## diff --git a/src/wuttjamaican/conf.py b/src/wuttjamaican/conf.py index c04b603..a54c38d 100644 --- a/src/wuttjamaican/conf.py +++ b/src/wuttjamaican/conf.py @@ -625,7 +625,7 @@ class WuttaConfig: class WuttaConfigExtension: """ - Base class for all config extensions. + Base class for all :term:`config extensions `. """ key = None @@ -638,6 +638,17 @@ class WuttaConfigExtension: 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): """ @@ -962,8 +973,13 @@ def make_config( # apply all registered extensions # TODO: maybe let config disable some extensions? 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) - extension().configure(config) + extension.configure(config) + + # let extensions run startup hooks if needed + for extension in extensions: + extension.startup(config) return config diff --git a/src/wuttjamaican/db/model/auth.py b/src/wuttjamaican/db/model/auth.py index 5aee1df..18ca366 100644 --- a/src/wuttjamaican/db/model/auth.py +++ b/src/wuttjamaican/db/model/auth.py @@ -65,6 +65,7 @@ class Role(Base): See also :attr:`user_refs`. """ __tablename__ = 'role' + __versioned__ = {} uuid = uuid_column() @@ -122,6 +123,7 @@ class Permission(Base): Represents a permission granted to a role. """ __tablename__ = 'permission' + __versioned__ = {} role_uuid = uuid_fk_column('role.uuid', primary_key=True, nullable=False) role = orm.relationship( @@ -155,6 +157,7 @@ class User(Base): See also :attr:`role_refs`. """ __tablename__ = 'user' + __versioned__ = {} uuid = uuid_column() @@ -217,6 +220,7 @@ class UserRole(Base): user "belongs" or "is assigned" to the role. """ __tablename__ = 'user_x_role' + __versioned__ = {} uuid = uuid_column() diff --git a/src/wuttjamaican/db/model/base.py b/src/wuttjamaican/db/model/base.py index 81c41ab..6da0cb3 100644 --- a/src/wuttjamaican/db/model/base.py +++ b/src/wuttjamaican/db/model/base.py @@ -118,6 +118,7 @@ class Person(Base): Employee relationship etc. """ __tablename__ = 'person' + __versioned__ = {} uuid = uuid_column() diff --git a/tests/test_app.py b/tests/test_app.py index 54ab540..720f59e 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -200,6 +200,19 @@ class TestAppHandler(FileTestCase): value = self.app.get_setting(session, 'foo') 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): try: from wuttjamaican.db import model diff --git a/tests/test_conf.py b/tests/test_conf.py index 89af3be..690cee1 100644 --- a/tests/test_conf.py +++ b/tests/test_conf.py @@ -7,6 +7,8 @@ from unittest.mock import patch, MagicMock import pytest +from wuttjamaican import conf as mod +# TODO: get rid of this eventually from wuttjamaican import conf from wuttjamaican.exc import ConfigurationError from wuttjamaican.app import AppHandler @@ -673,11 +675,11 @@ class TestMakeConfig(FileTestCase): generic = self.write_file('generic.conf', '') myfile = self.write_file('my.conf', '') - with patch('wuttjamaican.conf.WuttaConfig') as WuttaConfig: - with patch('wuttjamaican.conf.load_entry_points') as load_entry_points: + with patch.object(mod, 'WuttaConfig') as WuttaConfig: + with patch.object(mod, 'load_entry_points') as load_entry_points: # 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', usedb=None, preferdb=None) load_entry_points.assert_not_called() @@ -685,7 +687,7 @@ class TestMakeConfig(FileTestCase): # confirm entry points for default appname load_entry_points.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config([], appname='wutta') + config = mod.make_config([], appname='wutta') WuttaConfig.assert_called_once_with([], appname='wutta', usedb=None, preferdb=None) load_entry_points.assert_called_once_with('wutta.config.extensions') @@ -693,7 +695,7 @@ class TestMakeConfig(FileTestCase): # confirm entry points for custom appname load_entry_points.reset_mock() WuttaConfig.reset_mock() - config = conf.make_config(appname='wuttatest') + config = mod.make_config(appname='wuttatest') WuttaConfig.assert_called_once_with([], appname='wuttatest', usedb=None, preferdb=None) load_entry_points.assert_called_once_with('wuttatest.config.extensions') @@ -706,9 +708,10 @@ class TestMakeConfig(FileTestCase): WuttaConfig.reset_mock() testconfig = MagicMock() WuttaConfig.return_value = testconfig - config = conf.make_config(appname='wuttatest') + config = mod.make_config(appname='wuttatest') WuttaConfig.assert_called_once_with([], appname='wuttatest', usedb=None, preferdb=None) load_entry_points.assert_called_once_with('wuttatest.config.extensions') foo_cls.assert_called_once_with() foo_obj.configure.assert_called_once_with(testconfig) + foo_obj.startup.assert_called_once_with(testconfig)