summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt16
-rw-r--r--docs/api/paster.rst8
-rw-r--r--docs/api/scripting.rst9
-rw-r--r--docs/index.rst1
-rw-r--r--repoze/bfg/paster.py28
-rw-r--r--repoze/bfg/scripting.py20
-rw-r--r--repoze/bfg/tests/test_paster.py15
-rw-r--r--repoze/bfg/tests/test_scripting.py56
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)
+