diff options
| -rw-r--r-- | CHANGES.txt | 16 | ||||
| -rw-r--r-- | docs/api/paster.rst | 8 | ||||
| -rw-r--r-- | docs/api/scripting.rst | 9 | ||||
| -rw-r--r-- | docs/index.rst | 1 | ||||
| -rw-r--r-- | repoze/bfg/paster.py | 28 | ||||
| -rw-r--r-- | repoze/bfg/scripting.py | 20 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_paster.py | 15 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_scripting.py | 56 |
8 files changed, 138 insertions, 15 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 6bb1a96d1..a75cd513c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,19 @@ +Next release +============ + +Features +-------- + +- Add a ``get_app`` API functions to the ``paster`` module. This + obtains a WSGI application from a config file given a config file + name and a section name. See the ``repoze.bfg.paster`` API docs for + more information. + +- Add a new module named ``scripting``. It contains a ``get_root`` + API function, which, provided a Router instance, returns a traversal + root object and a "closer". See the ``repoze.bfg.scripting`` API + docs for more info. + 0.9a4 (2009-05-27) ================== diff --git a/docs/api/paster.rst b/docs/api/paster.rst new file mode 100644 index 000000000..52b3048c1 --- /dev/null +++ b/docs/api/paster.rst @@ -0,0 +1,8 @@ +.. _paster_module: + +:mod:`repoze.bfg.paster` +--------------------------- + +.. automodule:: repoze.bfg.paster + + .. autofunction:: get_app(config_file, name) diff --git a/docs/api/scripting.rst b/docs/api/scripting.rst new file mode 100644 index 000000000..afb86d9bb --- /dev/null +++ b/docs/api/scripting.rst @@ -0,0 +1,9 @@ +.. _scripting_module: + +:mod:`repoze.bfg.scripting` +--------------------------- + +.. automodule:: repoze.bfg.scripting + + .. autofunction:: get_root + diff --git a/docs/index.rst b/docs/index.rst index c1efadb42..56729bac8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -57,6 +57,7 @@ Per-module :mod:`repoze.bfg` API documentation. api/location api/push api/router + api/scripting api/security api/template api/testing diff --git a/repoze/bfg/paster.py b/repoze/bfg/paster.py index 233cc9c57..6ba1391e3 100644 --- a/repoze/bfg/paster.py +++ b/repoze/bfg/paster.py @@ -10,6 +10,8 @@ from paste.script.command import Command from paste.util.template import paste_script_template_renderer +from repoze.bfg.scripting import get_root + class StarterProjectTemplate(Template): _template_dir = 'paster_templates/starter' summary = 'repoze.bfg starter project' @@ -30,6 +32,14 @@ class AlchemyProjectTemplate(Template): summary = 'repoze.bfg SQLAlchemy project using traversal' template_renderer = staticmethod(paste_script_template_renderer) +def get_app(config_file, name, loadapp=loadapp): + """ Return the WSGI application named ``name`` in the PasteDeploy + config file ``config_file``""" + config_name = 'config:%s' % config_file + here_dir = os.getcwd() + app = loadapp(config_name, name=name, relative_to=here_dir) + return app + class BFGShellCommand(Command): """Open an interactive shell with a repoze.bfg app loaded. @@ -59,31 +69,19 @@ class BFGShellCommand(Command): group_name = 'bfg' parser = Command.standard_parser(simulate=True) - environ = {} interact = (interact,) # for testing loadapp = (loadapp,) # for testing verbose = 3 - def __init__(self, name): - Command.__init__(self, name) - def command(self): cprt =('Type "help" for more information. "root" is the BFG app ' 'root object.') banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - config_file, section_name = self.args - config_name = 'config:%s' % config_file - here_dir = os.getcwd() - - app = self.loadapp[0](config_name, - name=section_name, relative_to=here_dir) - registry = app.registry - threadlocals = {'registry':registry, 'request':None} + app = get_app(config_file, section_name, loadapp=self.loadapp[0]) + root, closer = get_root(app) try: - app.threadlocal_manager.push(threadlocals) - root = app.root_factory(self.environ) self.interact[0](banner, local={'root':root}) finally: - app.threadlocal_manager.pop() + closer() diff --git a/repoze/bfg/scripting.py b/repoze/bfg/scripting.py new file mode 100644 index 000000000..5399b7d77 --- /dev/null +++ b/repoze/bfg/scripting.py @@ -0,0 +1,20 @@ +def get_root(app, environ=None): + """ Return a tuple composed of ``(root, closer)`` when provided a + ``repoze.bfg.router.Router`` instance as the ``app`` argument. + The ``root`` returned is the application root object. The + ``closer`` returned is a callable (accepting no arguments) that + should be called when your scripting application is finished using + the root. If ``environ`` is not None, it is used as the + environment passed to the BFG application root factory. An empty + environ is constructed and passed to the root factory if + ``environ`` is None.""" + registry = app.registry + threadlocals = {'registry':registry, 'request':None} + app.threadlocal_manager.push(threadlocals) + if environ is None: + environ = {} + def closer(environ=environ): # keep environ alive via this function default + app.threadlocal_manager.pop() + root = app.root_factory(environ) + return root, closer + diff --git a/repoze/bfg/tests/test_paster.py b/repoze/bfg/tests/test_paster.py index 3339fe498..0119e3313 100644 --- a/repoze/bfg/tests/test_paster.py +++ b/repoze/bfg/tests/test_paster.py @@ -28,6 +28,21 @@ class TestBFGShellCommand(unittest.TestCase): self.failUnless(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) +class TestGetApp(unittest.TestCase): + def _callFUT(self, config_file, section_name, loadapp): + from repoze.bfg.paster import get_app + return get_app(config_file, section_name, loadapp) + + def test_it(self): + import os + app = DummyApp() + loadapp = DummyLoadApp(app) + result = self._callFUT('/foo/bar/myapp.ini', 'myapp', loadapp) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertEqual(loadapp.relative_to, os.getcwd()) + self.assertEqual(result, app) + class Dummy: pass diff --git a/repoze/bfg/tests/test_scripting.py b/repoze/bfg/tests/test_scripting.py new file mode 100644 index 000000000..514b02dca --- /dev/null +++ b/repoze/bfg/tests/test_scripting.py @@ -0,0 +1,56 @@ +import unittest + +class TestGetRoot(unittest.TestCase): + def _callFUT(self, app, environ=None): + from repoze.bfg.paster import get_root + return get_root(app, environ) + + def test_it_noenviron(self): + app = DummyApp() + root, closer = self._callFUT(app) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'], None) + self.assertEqual(len(app.threadlocal_manager.popped), 0) + closer() + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_it_withenviron(self): + app = DummyApp() + environ = {} + root, closer = self._callFUT(app, environ) + self.assertEqual(len(app.threadlocal_manager.pushed), 1) + pushed = app.threadlocal_manager.pushed[0] + self.assertEqual(pushed['registry'], dummy_registry) + self.assertEqual(pushed['request'], None) + self.assertEqual(len(app.threadlocal_manager.popped), 0) + closer() + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + +class Dummy: + pass + +dummy_root = Dummy() +dummy_registry = Dummy() + +class DummyApp: + def __init__(self): + self.registry = dummy_registry + self.threadlocal_manager = DummyThreadLocalManager() + + def root_factory(self, environ): + return dummy_root + +class DummyThreadLocalManager: + def __init__(self): + self.pushed = [] + self.popped = [] + + def push(self, item): + self.pushed.append(item) + + def pop(self): + self.popped.append(True) + |
