diff options
| author | Chris McDonough <chrism@agendaless.com> | 2009-05-27 15:47:11 +0000 |
|---|---|---|
| committer | Chris McDonough <chrism@agendaless.com> | 2009-05-27 15:47:11 +0000 |
| commit | 156375861f191f51f4e97ce25cd4d39f8025f90b (patch) | |
| tree | ead4a91f8781cc26b6754e496d5e52399568edbe | |
| parent | 711b60c05b9573f688994233ec1baac3f89bc45a (diff) | |
| download | pyramid-156375861f191f51f4e97ce25cd4d39f8025f90b.tar.gz pyramid-156375861f191f51f4e97ce25cd4d39f8025f90b.tar.bz2 pyramid-156375861f191f51f4e97ce25cd4d39f8025f90b.zip | |
- A paster command has been added named "bfgshell". This command can
be used to get an interactive prompt with your BFG root object in
the global namespace. E.g.::
bin/paster bfgshell /path/to/myapp.ini myapp
See the ``Project`` chapter in the BFG documentation for more
information.
| -rw-r--r-- | CHANGES.txt | 19 | ||||
| -rw-r--r-- | docs/narr/project.rst | 27 | ||||
| -rw-r--r-- | repoze/bfg/paster.py | 68 | ||||
| -rw-r--r-- | repoze/bfg/registry.py | 10 | ||||
| -rw-r--r-- | repoze/bfg/scripting.py | 18 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_paster.py | 71 | ||||
| -rw-r--r-- | repoze/bfg/tests/test_scripting.py | 31 | ||||
| -rw-r--r-- | setup.py | 2 |
8 files changed, 180 insertions, 66 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index cb2b7d7ae..bb9ada6db 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -4,17 +4,14 @@ Features -------- -- A new API function has been added for scripts which need to obtain - the traversal root: ``repoze.bfg.scripting.get_root``. Given a - ``repoze.bfg`` Router application instance as its ``router`` - argument, this callable returns the traversal root of graph as - defined by the application's root factory. It also has the effect - of pushing a new registry and request on to the internal thread - local stack managed by BFG so that registry lookups work properly. - - .. warning:: This function should never be called from *within* a - BFG model or view, only from top-level scripts which wish to - get the root of a graph to do offline processing. +- A paster command has been added named "bfgshell". This command can + be used to get an interactive prompt with your BFG root object in + the global namespace. E.g.:: + + bin/paster bfgshell /path/to/myapp.ini myapp + + See the ``Project`` chapter in the BFG documentation for more + information. Deprecations ------------ diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 2788887e5..8e17a618f 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -648,3 +648,30 @@ build your application. You are not required to write tests to use :mod:`repoze.bfg`, this file is simply provided as convenience and example. +The Interactive Shell +--------------------- + +You can use an interactive shell to examine your BFG application from +a Python prompt. To do so, use the ``bfgshell`` paster command: + +.. code-block:: python + :linenos: + + [chrism@vitaminf bfgshellenv]$ bin/paster bfgshell foo/foo.ini main + + Python 2.4.5 (#1, Aug 29 2008, 12:27:37) + [GCC 4.0.1 (Apple Inc. build 5465)] on darwin + Type "help" for more information. "root" is the BFG app root object. + >>> root + <foo.models.MyModel object at 0x445270> + +The first argument is the path to your application's ``.ini`` file. +The second is the section name inside the ``.ini`` file which points +to your application. + +.. note:: You should use a ``section name`` argument that refers to + the actual ``app`` section within the config file that + points at your BFG app *without any middleware wrapping*, or + this command will almost certainly fail. The section name + ``main`` is often inappropriate if the configuration does + any middleware wrapping. diff --git a/repoze/bfg/paster.py b/repoze/bfg/paster.py index df8bfecb4..bc339fd4c 100644 --- a/repoze/bfg/paster.py +++ b/repoze/bfg/paster.py @@ -1,4 +1,14 @@ +import os +import sys + +from code import interact + +from paste.deploy import loadapp + from paste.script.templates import Template +from paste.script.command import Command +from paste.script.command import BadCommand + from paste.util.template import paste_script_template_renderer class StarterProjectTemplate(Template): @@ -20,3 +30,61 @@ class AlchemyProjectTemplate(Template): _template_dir = 'paster_templates/alchemy' summary = 'repoze.bfg SQLAlchemy project using traversal' template_renderer = staticmethod(paste_script_template_renderer) + +class BFGShellCommand(Command): + """Open an interactive shell with a repoze.bfg app loaded. + + This command accepts two positional arguments: + + ``config_file`` -- specifies the PasteDeploy config file to use + for the interactive shell. + + ``section_name`` -- specifies the section name in the PasteDeploy + config file that represents the application. + + Example:: + + $ paster bfgshell myapp.ini main + + .. note:: You should use a ``section_name`` that refers to the + actual ``app`` section in the config file that points at + your BFG app without any middleware wrapping, or this + command will almost certainly fail. + + """ + summary = "Open an interactive shell with a repoze.bfg app loaded" + usage = '\n' + __doc__ + + min_args = 2 + max_args = 2 + 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} + try: + app.threadlocal_manager.push(threadlocals) + root = app.root_factory(self.environ) + self.interact[0](banner, local={'root':root}) + finally: + app.threadlocal_manager.pop() + diff --git a/repoze/bfg/registry.py b/repoze/bfg/registry.py index b9397e7da..46db57c9e 100644 --- a/repoze/bfg/registry.py +++ b/repoze/bfg/registry.py @@ -112,9 +112,7 @@ registry_manager = FakeRegistryManager() deprecated('registry_manager', 'As of repoze.bfg 0.9, any import of registry_manager from' '``repoze.bfg.registry`` is ' - 'deprecated. Instead, if you are trying to push a BFG ' - 'application registry into a registry_manager within a "debug" ' - 'script, call ``app.get_root(environ)``, which has the side ' - 'effect of pushing the current registry into a thread local ' - 'stack. ``registry_manager`` will disappear in a later ' - 'release of repoze.bfg') + 'deprecated. If you are trying to use the registry manager ' + 'within a "debug" script of your own making, use the ``bfgshell`` ' + 'paster command instead ``registry_manager`` will disappear in ' + 'a later release of repoze.bfg') diff --git a/repoze/bfg/scripting.py b/repoze/bfg/scripting.py deleted file mode 100644 index 9e83e2fe8..000000000 --- a/repoze/bfg/scripting.py +++ /dev/null @@ -1,18 +0,0 @@ -_GET_ROOT_ENVIRON = {} - -def get_root(router): - """ Given a :mod:`repoze.bfg` Router application instance as its - ``router`` argument, this callable returns the traversal root of - graph as defined by the application's root factory. It also has - the effect of pushing a new registry and request on to the - internal thread local stack managed by BFG so that registry - lookups work properly. - - .. warning:: This function should never be called from *within* a - BFG model or view, only from top-level scripts which wish to - get the root of a graph to do offline processing.""" - registry = router.registry - threadlocals = {'registry':registry, 'request':None} - router.threadlocal_manager.push(threadlocals) - return router.root_factory(_GET_ROOT_ENVIRON) - diff --git a/repoze/bfg/tests/test_paster.py b/repoze/bfg/tests/test_paster.py new file mode 100644 index 000000000..3339fe498 --- /dev/null +++ b/repoze/bfg/tests/test_paster.py @@ -0,0 +1,71 @@ +import unittest + +class TestBFGShellCommand(unittest.TestCase): + def _getTargetClass(self): + from repoze.bfg.paster import BFGShellCommand + return BFGShellCommand + + def _makeOne(self): + return self._getTargetClass()('bfgshell') + + def test_command(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.args = ('/foo/bar/myapp.ini', 'myapp') + command.command() + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.failUnless(loadapp.relative_to) + 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(interact.local, {'root':dummy_root}) + self.failUnless(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + +class Dummy: + pass + +dummy_root = Dummy() + +dummy_registry = Dummy() + +class DummyInteractor: + def __call__(self, banner, local): + self.banner = banner + self.local = local + +class DummyLoadApp: + def __init__(self, app): + self.app = app + + def __call__(self, config_name, name=None, relative_to=None): + self.config_name = config_name + self.section_name = name + self.relative_to = relative_to + return self.app + +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) + diff --git a/repoze/bfg/tests/test_scripting.py b/repoze/bfg/tests/test_scripting.py deleted file mode 100644 index a54b4b7d9..000000000 --- a/repoze/bfg/tests/test_scripting.py +++ /dev/null @@ -1,31 +0,0 @@ -import unittest - -class TestGetRoot(unittest.TestCase): - def _callFUT(self, router): - from repoze.bfg.scripting import get_root - return get_root(router) - - def test_it(self): - router = DummyRouter() - result = self._callFUT(router) - self.assertEqual(result, router) - self.assertEqual(len(router.threadlocal_manager.pushed), 1) - self.assertEqual(router.threadlocal_manager.pushed[0], - {'registry':None, 'request':None}) - - -class DummyThreadLocalManager: - def __init__(self): - self.pushed = [] - - def push(self, val): - self.pushed.append(val) - -class DummyRouter: - def __init__(self): - self.registry = None - self.threadlocal_manager = DummyThreadLocalManager() - - def root_factory(self, environ): - return self - @@ -71,6 +71,8 @@ setup(name='repoze.bfg', bfg_starter=repoze.bfg.paster:StarterProjectTemplate bfg_zodb=repoze.bfg.paster:ZODBProjectTemplate bfg_routesalchemy=repoze.bfg.paster:RoutesAlchemyProjectTemplate + [paste.paster_command] + bfgshell=repoze.bfg.paster:BFGShellCommand """ ) |
