pgwui.autoconfigure declaration; autodetect pgwui components
authorKarl O. Pinc <kop@karlpinc.com>
Sat, 28 Dec 2019 21:32:05 +0000 (15:32 -0600)
committerKarl O. Pinc <kop@karlpinc.com>
Sat, 28 Dec 2019 21:32:05 +0000 (15:32 -0600)
examples/etc/pgwui.ini
examples/misc/development.ini
setup.py
src/pgwui_server/__init__.py
tests/test___init__.py

index 0aeb2e815c79579ca6cbc68f8589a300e596e128..bf0034157007cc345d778d96a5f5f73f64935400 100644 (file)
@@ -28,10 +28,15 @@ pgwui.pg_port = 5432
 # There are occasions when PGWUI uses a default database.  (optional)
 pgwui.default_db = template1
 
-# What PGWUI modules to use.
-pyramid.includes =
-    pgwui_logout
-    pgwui_upload
+# Whether to auto-discover the pgwui component modules. (optional)
+# When False pgwui component names must be listed in pyramid.includes=...
+# pgwui.autoconfigure = True
+
+# What PGWUI components and other pyramid modules to use.
+# (Required when pgwui.autoconfigure is False.)
+#pyramid.includes =
+#    pgwui_logout
+#    pgwui_upload
 
 # Whether or not to change the db content.  (required)
 pgwui.dry_run = False
index b3d8dd2b69155fc02f76773b1189fad3afe498ef..2ac1cd7d1ceb0ba164ed0254ea1e88dfa13d3c33 100644 (file)
@@ -28,10 +28,14 @@ pgwui.pg_port = 5432
 # There are occasions when PGWUI uses a default database.  (optional)
 pgwui.default_db = template1
 
-# What PGWUI modules to use.
+
+# Whether to auto-discover the pgwui component modules. (optional)
+# When False pgwui component names must be listed in pyramid.includes=...
+# pgwui.autoconfigure = True
+
+# What PGWUI components and other pyramid modules to use.
+# (Required when pgwui.autoconfigure is False.)
 pyramid.includes =
-    pgwui_logout
-    pgwui_upload
     pyramid_debugtoolbar
 
 # Whether or not to change the db content.  (required)
index 0373f3cfc4476dc18767b6d6fda14efa31a0d116..f207711d585f7278bd5a7581439815173b212552 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -149,6 +149,7 @@ setup(
     # Run-time dependencies.
     install_requires=[
         'pgwui_common==' + version,
+        'setuptools',                # for pkg_resources module
         'pyramid',
     ],
 
@@ -173,6 +174,9 @@ setup(
     # To provide executable scripts, use entry points in preference to the
     # "scripts" keyword. Entry points provide cross-platform support and allow
     # pip to create the appropriate form of executable for the target platform.
+    #
+    # Do not register PGWUI_Server in entry_points as a PGWUI component.
+    # Its configuration is manually coded.
     entry_points="""\
         [paste.app_factory]
         main = pgwui_server:main
index 73ca789e130188bdc2d97e7e848308f9d3f6caa6..a85140d06e3798b75caee1a909eb8f6fd50a9931 100644 (file)
@@ -25,6 +25,7 @@
 from ast import literal_eval
 from pyramid.config import Configurator
 import logging
+import pkg_resources
 import sys
 
 # Constants
@@ -38,6 +39,7 @@ SETTINGS = set(
      'route_prefix',
      'routes',
      'validate_hmac',
+     'autoconfigure',
      'literal_column_headings',
      ])
 
@@ -56,6 +58,12 @@ class Error(Exception):
     pass
 
 
+class AutoconfigureConflict(Error):
+    def __init__(self):
+        super().__init__(
+            'Autoconfigure is True and there is a pyramid.include setting')
+
+
 class BadSettingsAbort(Error):
     def __init__(self):
         super().__init__('Aborting due to bad setting(s)')
@@ -237,14 +245,38 @@ def add_routes(config, settings):
             config.add_route(name, route)
 
 
+def find_pgwui_components():
+    '''Return list of all pgwui component names as strings
+    '''
+    return [entry_point.resolve().__name__ for entry_point in
+            pkg_resources.iter_entry_points('pgwui.components')]
+
+
+def autoconfig_components(settings):
+    '''Automatic pgwui component discovery
+    '''
+    autoconfig = settings.get('pgwui.autoconfigure')
+    if not autoconfig:
+        return []
+
+    if 'pyramid.include' in settings:
+        log.info(AutoconfigureConflict())
+
+    return find_pgwui_components()
+
+
 def pgwui_server_config(settings):
     '''Configure pyramid
     '''
     exit_on_invalid_settings(settings)
 
+    components = autoconfig_components(settings)
+
     rp = settings.get('pgwui.route_prefix')
     with Configurator(settings=settings, route_prefix=rp) as config:
         config.include('pgwui_common')
+        for component in components:
+            config.include(component)
         add_routes(config, settings)
     return config
 
index 17fbfa07ab420e204b775caca3685485d105ccb7..9ceda1d0b012cb93a13e9b3d4e80bba1b1e17fca 100644 (file)
@@ -431,6 +431,82 @@ def test_parse_assignments_dict():
                                ])
 
 
+# find_pgwui_components()
+
+def test_find_pgwui_components(monkeypatch):
+    '''Returns list of entry points via iter_entry_points()
+    '''
+    entry_points = ['a', 'b', 'c']
+
+    class MockEntryPoint():
+        def __init__(self, val):
+            self.__name__ = val
+
+        def resolve(self):
+            return self
+
+    class MockPkgResources():
+        def iter_entry_points(*args):
+            return [MockEntryPoint(name) for name in entry_points]
+
+    monkeypatch.setattr(
+        pgwui_server_init, 'pkg_resources', MockPkgResources())
+
+    result = pgwui_server_init.find_pgwui_components()
+
+    assert result == entry_points
+
+
+# autoconfig_components()
+
+def test_autoconfig_components_no_autoconfig(monkeypatch):
+    '''When the settings have no pgwui.autoconfigure return an empty list
+    '''
+    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
+                        lambda *args: [])
+
+    result = pgwui_server_init.autoconfig_components({})
+
+    assert result == []
+
+
+def test_autoconfig_components_log_info(monkeypatch, caplog):
+    '''When pyramid.include is in the settings an INFO message is logged
+    '''
+    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
+                        lambda *args: [])
+
+    caplog.set_level(logging.INFO)
+
+    pgwui_server_init.autoconfig_components({'pgwui.autoconfigure': True,
+                                             'pyramid.include': None})
+
+    logs = caplog.record_tuples
+
+    assert len(logs) == 1
+
+    level = logs[0][1]
+    assert level == logging.INFO
+
+
+def test_autoconfig_components_find_pgwui_components_called(monkeypatch):
+    '''When pyramid.include is in the settings an INFO message is logged
+    '''
+    find_pgwui_components_called = False
+
+    def mock_find_pgwui_components(*args):
+        nonlocal find_pgwui_components_called
+        find_pgwui_components_called = True
+
+    monkeypatch.setattr(pgwui_server_init, 'find_pgwui_components',
+                        mock_find_pgwui_components)
+
+    pgwui_server_init.autoconfig_components({'pgwui.autoconfigure': True,
+                                             'mock_pgwui_component': 'foo'})
+
+    assert find_pgwui_components_called
+
+
 # add_routes()
 
 def test_add_routes_empty():
@@ -455,8 +531,10 @@ def test_add_routes_notempty(monkeypatch):
 
 def test_pgwui_server_config(monkeypatch):
     '''Returns a configuration'''
-    monkeypatch.setattr(pgwui_server_init, 'validate_settings',
+    monkeypatch.setattr(pgwui_server_init, 'exit_on_invalid_settings',
                         lambda *args: True)
+    monkeypatch.setattr(pgwui_server_init, 'autoconfig_components',
+                        lambda *args: ['pgwui_mock_component_name'])
     monkeypatch.setattr(pgwui_server_init, 'Configurator',
                         MockConfigurator)
     monkeypatch.setattr(pgwui_server_init, 'add_routes',