Skip to content

Commit 9f05837

Browse files
committed
Improve plug-in loader
Always load plug-ins in the specified order. Simplify and test the new documented bootstrap mechanism.
1 parent 1ad89a6 commit 9f05837

File tree

8 files changed

+89
-41
lines changed

8 files changed

+89
-41
lines changed

docs/appdev.rst

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -366,16 +366,12 @@ Some IDEs like PyCharm can also debug remote processes. This could be useful to
366366
Bootstrap Webware from Command line
367367
-----------------------------------
368368

369-
You may be in a situation where you want to execute some part of your Webware app from the command line, for example to implement a cron job or
370-
maintenance script. In these situations you probably don't want to instantiate a full-fledged `Application` -- some of the downsides are that doing so
371-
would cause standard output and standard error to be redirected to the log file, and that it sets up the session sweeper, task manager, etc.
372-
But you may still need access to plugins such as MiscUtils, MiddleKit, which you may not be able to import directly.
369+
You may be in a situation where you want to execute some part of your Webware applicaton from the command line, for example to implement a cron job or maintenance script. In these situations you probably don't want to instantiate a full-fledged `Application` -- some of the downsides are that doing so would cause standard output and standard error to be redirected to the log file, and that it sets up the session sweeper, task manager, etc. But you may still need access to plugins such as MiscUtils, MiddleKit, which you may not be able to import directly.
373370

374371
Here is a lightweight approach which allows you to bootstrap Webware and plugins::
375372

376373
import webware
377-
webware.add_to_python_path()
378-
webware.load_plugins('/your/app/directory')
374+
app = webware.mockAppWithPlugins()
379375

380376
# now plugins are available...
381377
import MiscUtils

webware/Application.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
OutputEncoding='utf-8',
100100
PlugIns=['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'],
101101
PrintConfigAtStartUp=True,
102+
PrintPlugIns=True,
102103
ReloadServletClasses=False,
103104
ReportRPCExceptionsInWebware=True,
104105
ResponseBufferSize=8 * 1024, # 8 kBytes

webware/MockApplication.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ConfigurableForServerSidePath import ConfigurableForServerSidePath
44

55

6-
class MockImportManager(object):
6+
class MockImportManager:
77

88
def recordFile(self, filename, isfile=None):
99
pass
@@ -12,6 +12,7 @@ def recordFile(self, filename, isfile=None):
1212
defaultConfig = dict(
1313
CacheDir='Cache',
1414
PlugIns=['MiscUtils', 'WebUtils', 'TaskKit', 'UserKit', 'PSP'],
15+
PrintPlugIns=False
1516
)
1617

1718

@@ -30,11 +31,11 @@ def __init__(self, path=None, settings=None, development=None):
3031
if development is None:
3132
development = bool(os.environ.get('WEBWARE_DEVELOPMENT'))
3233
self._development = development
33-
34-
appConfig = self.config() # get and cache the configuration
34+
appConfig = self.config()
3535
if settings:
3636
appConfig.update(settings)
37-
self._cacheDir = self.serverSidePath(self.setting('CacheDir') or 'Cache')
37+
self._cacheDir = self.serverSidePath(
38+
self.setting('CacheDir') or 'Cache')
3839
from MiscUtils.PropertiesObject import PropertiesObject
3940
props = PropertiesObject(os.path.join(
4041
self._webwarePath, 'Properties.py'))
@@ -64,7 +65,7 @@ def serverSidePath(self, path=None):
6465
os.path.join(self._serverSidePath, path))
6566
return self._serverSidePath
6667

67-
def hasContext(self, context):
68+
def hasContext(self, _name):
6869
return False
6970

7071
def addServletFactory(self, factory):

webware/PlugInLoader.py

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
from PlugIn import PlugIn
44

55

6-
class PlugInLoader(object):
6+
class PlugInLoader:
77

88
def __init__(self, app):
99
self.app = app
10-
self._plugIns = {}
1110

1211
def loadPlugIn(self, name, module, verbose=True):
1312
"""Load and return the given plug-in.
@@ -29,32 +28,43 @@ def loadPlugIn(self, name, module, verbose=True):
2928
raise
3029
return plugIn
3130

32-
def loadPlugIns(self, plugInNames, verbose=True):
31+
def loadPlugIns(self, plugInNames=None, verbose=None):
3332
"""Load all plug-ins.
3433
3534
A plug-in allows you to extend the functionality of Webware without
3635
necessarily having to modify its source. Plug-ins are loaded by
3736
Application at startup time, just before listening for requests.
3837
See the docs in `PlugIn` for more info.
3938
"""
40-
plugInNames = set(plugInNames)
41-
plugInNames.add('Webware')
42-
plugIns = [
43-
(entry_point.name, entry_point.load())
44-
for entry_point
45-
in pkg_resources.iter_entry_points('webware.plugins')
46-
if entry_point.name in plugInNames
47-
]
39+
if plugInNames is None:
40+
plugInNames = self.app.setting('PlugIns')
41+
if verbose is None:
42+
verbose = self.app.setting('PrintPlugIns')
4843

4944
if verbose:
50-
print('Plug-ins list:', ', '.join(
51-
name for name, _module in plugIns if name != 'Webware'))
45+
print('Plug-ins list:', ', '.join(plugInNames))
5246

53-
# Now that we have our plug-in list, load them...
54-
for name, module in plugIns:
47+
entryPoints = {
48+
entry_point.name: entry_point for entry_point
49+
in pkg_resources.iter_entry_points('webware.plugins')}
50+
51+
plugIns = {}
52+
for name in plugInNames:
53+
if name in plugIns:
54+
if verbose:
55+
print(f'Plug-in {name} has already been loaded.')
56+
continue
57+
entry_point = entryPoints.get(name)
58+
if not entry_point:
59+
if verbose:
60+
print(f'Plug-in {name} has not entry point.')
61+
continue
62+
module = entry_point.load()
5563
plugIn = self.loadPlugIn(name, module, verbose=verbose)
5664
if plugIn:
57-
self._plugIns[name] = plugIn
65+
plugIns[name] = plugIn
66+
5867
if verbose:
5968
print()
60-
return self._plugIns
69+
70+
return plugIns

webware/Scripts/WSGIScript.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@
2121
os.chdir(workDir)
2222

2323
import webware
24-
webwarePath = webware.__path__[0]
25-
if webwarePath not in sys.path:
26-
sys.path.insert(0, webwarePath)
24+
webware.addToSearchPath()
2725

2826
from Application import Application
2927

webware/Tests/TestEndToEnd/TestServer.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@
3333
Loading context: Examples at .+Examples
3434
Loading context: Admin at .+Admin
3535
Loading context: Testing at .+Testing
36-
Plug-ins list: MiscUtils, PSP, TaskKit, UserKit, WebUtils
36+
Plug-ins list: MiscUtils, WebUtils, TaskKit, UserKit, PSP
3737
Loading plug-in: MiscUtils at .+MiscUtils
38-
Loading plug-in: PSP at .+PSP
39-
Loading context: PSP/Examples at .+Examples
38+
Loading plug-in: WebUtils at .+WebUtils
4039
Loading plug-in: TaskKit at .+TaskKit
4140
Loading plug-in: UserKit at .+UserKit
42-
Loading plug-in: WebUtils at .+WebUtils
41+
Loading plug-in: PSP at .+PSP
42+
Loading context: PSP/Examples at .+Examples
4343
4444
Waitress serving Webware application...
4545
Serving on http://.+:8080

webware/Tests/TestMocking.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Test Webware Mock Environment"""
2+
3+
import io
4+
import sys
5+
import unittest
6+
7+
import webware
8+
9+
10+
class TestMocking(unittest.TestCase):
11+
12+
def setUp(self):
13+
self.sysPath = sys.path
14+
self.webwarePath = webware.__path__[0]
15+
sys.path = [path for path in sys.path if path != self.webwarePath]
16+
self.sysStdout = sys.stdout
17+
sys.stdout = io.StringIO()
18+
self.getOutput = sys.stdout.getvalue
19+
20+
def tearDown(self):
21+
sys.stdout = self.sysStdout
22+
sys.path = self.sysPath
23+
24+
def testAddToSearchPath(self):
25+
assert self.webwarePath not in sys.path
26+
webware.addToSearchPath()
27+
assert self.webwarePath in sys.path
28+
29+
def testLoadPlugins(self):
30+
webware.addToSearchPath()
31+
app = webware.mockAppWithPlugins(
32+
self.webwarePath, settings={'PrintPlugIns': True})
33+
self.assertEqual(app.__class__.__name__, 'MockApplication')
34+
output = self.getOutput().splitlines()
35+
output = [line.split(' at ', 1)[0] for line in output]
36+
self.assertEqual(output, [
37+
'Plug-ins list: MiscUtils, WebUtils, TaskKit, UserKit, PSP',
38+
'Loading plug-in: MiscUtils', 'Loading plug-in: WebUtils',
39+
'Loading plug-in: TaskKit', 'Loading plug-in: UserKit',
40+
'Loading plug-in: PSP', ''])

webware/__init__.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
"""Webware for Python"""
22

3-
import sys
43

5-
6-
def add_to_python_path():
4+
def addToSearchPath():
5+
"""Add the Webware package to the search path for Python modules."""
6+
import sys
77
webwarePath = __path__[0]
88
if webwarePath not in sys.path:
99
sys.path.insert(0, webwarePath)
1010

1111

12-
def load_plugins(path, settings=None, development=None):
12+
def mockAppWithPlugins(path=None, settings=None, development=None):
13+
"""Return a mock application with all plugins loaded."""
14+
addToSearchPath()
1315
from MockApplication import MockApplication
1416
from PlugInLoader import PlugInLoader
1517
app = MockApplication(path, settings, development)
16-
loader = PlugInLoader(app)
17-
loader.loadPlugIns(app.setting('PlugIns'), verbose=False)
18+
PlugInLoader(app).loadPlugIns()
19+
return app

0 commit comments

Comments
 (0)