From 6b01adc58935e95908c42bc982803b20ceaf60e2 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 5 Jul 2011 23:52:39 -0500 Subject: Updated paster commands to use the ini#section uri spec. --- pyramid/paster.py | 79 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index b1f0a6c8b..f670ae618 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -15,12 +15,22 @@ zope.deprecation.deprecated( 'pyramid.scaffolds.PyramidTemplate in Pyramid 1.1'), ) -def get_app(config_file, name, loadapp=loadapp): +def get_app(config_file, name=None, loadapp=loadapp): """ Return the WSGI application named ``name`` in the PasteDeploy - config file ``config_file``""" - config_name = 'config:%s' % config_file + config file ``config_file``. + + If the ``name`` is None, this will attempt to parse the name from + the ``config_file`` string expecting the format ``ini_path#name``. + If no name is found, the name will default to "main".""" + if '#' in config_file: + path, section = config_file.split('#', 1) + else: + path, section = config_file, 'main' + if name: + section = name + config_name = 'config:%s' % path here_dir = os.getcwd() - app = loadapp(config_name, name=name, relative_to=here_dir) + app = loadapp(config_name, name=section, relative_to=here_dir) return app _marker = object() @@ -42,17 +52,15 @@ class PCommand(Command): class PShellCommand(PCommand): """Open an interactive shell with a :app:`Pyramid` app loaded. - This command accepts two positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts one positional argument: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. Example:: - $ paster pshell myapp.ini main + $ paster pshell myapp.ini#main .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -62,8 +70,8 @@ class PShellCommand(PCommand): """ summary = "Open an interactive shell with a Pyramid application loaded" - min_args = 2 - max_args = 2 + min_args = 1 + max_args = 1 parser = Command.standard_parser(simulate=True) parser.add_option('-d', '--disable-ipython', @@ -81,9 +89,10 @@ class PShellCommand(PCommand): cprt =('Type "help" for more information. "root" is the Pyramid app ' 'root object, "registry" is the Pyramid registry object.') banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - config_file, section_name = self.args + app_spec = self.args[0] + config_file = app_spec.split('#', 1)[0] self.logging_file_config(config_file) - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app = self.get_app(app_spec, loadapp=self.loadapp[0]) root, closer = self.get_root(app) shell_globals = {'root':root, 'registry':app.registry} @@ -108,17 +117,15 @@ class PRoutesCommand(PCommand): route, the pattern of the route, and the view callable which will be invoked when the route is matched. - This command accepts two positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts one positional argument: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. Example:: - $ paster proutes myapp.ini main + $ paster proutes myapp.ini#main .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -126,8 +133,8 @@ class PRoutesCommand(PCommand): command will almost certainly fail. """ summary = "Print all URL dispatch routes related to a Pyramid application" - min_args = 2 - max_args = 2 + min_args = 1 + max_args = 1 stdout = sys.stdout parser = Command.standard_parser(simulate=True) @@ -146,8 +153,8 @@ class PRoutesCommand(PCommand): from pyramid.interfaces import IViewClassifier from pyramid.interfaces import IView from zope.interface import Interface - config_file, section_name = self.args - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app_spec = self.args[0] + app = self.get_app(app_spec, loadapp=self.loadapp[0]) registry = app.registry mapper = self._get_mapper(app) if mapper is not None: @@ -179,19 +186,17 @@ class PViewsCommand(PCommand): each route+predicate set, print each view that might match and its predicates. - This command accepts three positional arguments: - - ``config_file`` -- specifies the PasteDeploy config file to use - for the interactive shell. + This command accepts two positional arguments: - ``section_name`` -- specifies the section name in the PasteDeploy - config file that represents the application. + ``config_file#section_name`` -- specifies the PasteDeploy config file + to use for the interactive shell. If the section_name is left off, + ``main`` will be assumed. ``url`` -- specifies the URL that will be used to find matching views. Example:: - $ paster proutes myapp.ini main url + $ paster proutes myapp.ini#main url .. note:: You should use a ``section_name`` that refers to the actual ``app`` section in the config file that points at @@ -199,8 +204,8 @@ class PViewsCommand(PCommand): command will almost certainly fail. """ summary = "Print all views in an application that might match a URL" - min_args = 3 - max_args = 3 + min_args = 2 + max_args = 2 stdout = sys.stdout parser = Command.standard_parser(simulate=True) @@ -395,10 +400,10 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - config_file, section_name, url = self.args + app_spec, url = self.args[0] if not url.startswith('/'): url = '/%s' % url - app = self.get_app(config_file, section_name, loadapp=self.loadapp[0]) + app = self.get_app(app_spec, loadapp=self.loadapp[0]) registry = app.registry view = self._find_view(url, registry) self.out('') -- cgit v1.2.3 From 828384c14b07aa4f50a70f44f789ce4b1b1e34f6 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 01:32:52 -0500 Subject: Added [pshell] support to INI files and the ability to load generic wsgi apps. Generic WSGI apps can now be loaded without exposing the 'registry' and 'root' objects. When an app is loaded, extra variables will be exposed to the environment from a special [pshell] section of the INI file. These variables are of the format "name = asset.path.to.object". --- pyramid/paster.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index f670ae618..9683da043 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,13 +1,16 @@ import os +import re import sys from code import interact import zope.deprecation from paste.deploy import loadapp +from paste.script.command import BadCommand from paste.script.command import Command from pyramid.scripting import get_root +from pyramid.util import DottedNameResolver from pyramid.scaffolds import PyramidTemplate # bw compat zope.deprecation.deprecated( @@ -79,6 +82,45 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") + _pshell_section_re = re.compile(r'^\s*\[\s*pshell\s*\]\s*$') + _section_re = re.compile(r'^\s*\[') + + def pshell_file_config(self, filename): + vars = { + 'here': os.path.dirname(filename), + '__file__': filename, + } + f = open(filename) + lines = f.readlines() + f.close() + lineno = 1 + # find the pshell section + while lines: + if self._pshell_section_re.search(lines[0]): + lines.pop(0) + break + lines.pop(0) + lineno += 1 + # parse pshell section for key/value pairs + resolver = DottedNameResolver(None) + self.loaded_objects = {} + self.object_help = {} + for line in lines: + lineno += 1 + line = line.strip() + if not line or line.startswith('#'): + continue + if self._section_re.search(line): + break + if '=' not in line: + raise BadCommand('Missing = in %s at %s: %r' + % (filename, lineno, line)) + name, value = line.split('=', 1) + name = name.strip() + value = value.strip() % vars + self.loaded_objects[name] = resolver.maybe_resolve(value) + self.object_help[name] = value + def command(self, IPShell=_marker): # IPShell passed to command method is for testing purposes if IPShell is _marker: # pragma: no cover @@ -86,15 +128,58 @@ class PShellCommand(PCommand): from IPython.Shell import IPShell except ImportError: IPShell = None - cprt =('Type "help" for more information. "root" is the Pyramid app ' - 'root object, "registry" is the Pyramid registry object.') + cprt =('Type "help" for more information.') banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) app_spec = self.args[0] config_file = app_spec.split('#', 1)[0] self.logging_file_config(config_file) app = self.get_app(app_spec, loadapp=self.loadapp[0]) - root, closer = self.get_root(app) - shell_globals = {'root':root, 'registry':app.registry} + + # load default globals + shell_globals = { + 'app': app, + } + default_variables = {'app': 'The WSGI Application'} + if hasattr(app, 'registry'): + root, closer = self.get_root(app) + shell_globals.update({'root':root, 'registry':app.registry}) + default_variables.update({ + 'root': 'The root of the default resource tree.', + 'registry': 'The Pyramid registry object.', + 'settings': 'The Pyramid settings object.', + }) + warning = '' + else: + # warn the user that this isn't actually the Pyramid app + warning = """\n +WARNING: You have loaded a generic WSGI application, therefore the +"root" and "registry" are not available. To correct this, run "pshell" +again and specify the INI section containing your Pyramid application.""" + closer = lambda: None + + # load the pshell section of the ini file + self.pshell_file_config(config_file) + shell_globals.update(self.loaded_objects) + + # eliminate duplicates from default_variables + for k in self.loaded_objects: + if k in default_variables: + del default_variables[k] + + # append the loaded variables + if default_variables: + banner += '\n\nDefault Variables:' + for var, txt in default_variables.iteritems(): + banner += '\n %-12s %s' % (var, txt) + + if self.object_help: + banner += '\n\nCustom Variables:' + for var in sorted(self.object_help.keys()): + banner += '\n %-12s %s' % (var, self.object_help[var]) + + # append the warning + banner += warning + banner += '\n' if (IPShell is None) or self.options.disable_ipython: try: -- cgit v1.2.3 From 7b1a206b06610dc482219d12bcd00e1d47fe4517 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:27:21 -0500 Subject: Updated paster tests to use the ini_file#section syntax. --- pyramid/tests/test_paster.py | 66 +++++++++++++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index cf0b38a80..2bae6aba3 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -15,7 +15,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -39,7 +39,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = True @@ -62,7 +62,7 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -92,7 +92,7 @@ class TestPShellCommand(unittest.TestCase): interact = DummyInteractor() app = DummyApp() command.interact = (interact,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -121,7 +121,7 @@ class TestPShellCommand(unittest.TestCase): apps.append(app) return root, lambda *arg: None command.get_root =get_root - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -152,7 +152,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -165,7 +165,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -180,7 +180,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -205,7 +205,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -235,7 +235,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -268,7 +268,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp') + command.args = ('/foo/bar/myapp.ini#myapp') result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -511,7 +511,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -528,7 +528,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', 'a') + command.args = ('/foo/bar/myapp.ini#myapp', 'a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -546,7 +546,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -567,7 +567,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -588,7 +588,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -612,7 +612,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -635,7 +635,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -665,7 +665,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -690,7 +690,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -719,7 +719,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -743,7 +743,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -770,7 +770,7 @@ class TestPViewsCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini', 'myapp', '/a') + command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.command() self.assertEqual(result, None) self.assertEqual(L[1], 'URL = /a') @@ -798,6 +798,26 @@ class TestGetApp(unittest.TestCase): self.assertEqual(loadapp.section_name, 'myapp') self.assertEqual(loadapp.relative_to, os.getcwd()) self.assertEqual(result, app) + + def test_it_with_hash(self): + import os + app = DummyApp() + loadapp = DummyLoadApp(app) + result = self._callFUT('/foo/bar/myapp.ini#myapp', None, 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) + + def test_it_with_hash_and_name_override(self): + import os + app = DummyApp() + loadapp = DummyLoadApp(app) + result = self._callFUT('/foo/bar/myapp.ini#myapp', 'yourapp', loadapp) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'yourapp') + self.assertEqual(loadapp.relative_to, os.getcwd()) + self.assertEqual(result, app) -- cgit v1.2.3 From 26d135bae9aa1b24774a76053b22e9202e0421bb Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:28:09 -0500 Subject: Fixed a small bug in pviews initialization. --- pyramid/paster.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 9683da043..2e732bce4 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -485,7 +485,7 @@ class PViewsCommand(PCommand): self.out("%sview predicates (%s)" % (indent, predicate_text)) def command(self): - app_spec, url = self.args[0] + app_spec, url = self.args if not url.startswith('/'): url = '/%s' % url app = self.get_app(app_spec, loadapp=self.loadapp[0]) -- cgit v1.2.3 From 648a9ee498dc1a02ab79bc4e29dcb1d2b1697e45 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 02:28:24 -0500 Subject: Replaced the manual parsing with python's ConfigParser. --- pyramid/paster.py | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 2e732bce4..0360a57bb 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -2,11 +2,11 @@ import os import re import sys from code import interact +from ConfigParser import ConfigParser import zope.deprecation from paste.deploy import loadapp -from paste.script.command import BadCommand from paste.script.command import Command from pyramid.scripting import get_root @@ -82,44 +82,17 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") - _pshell_section_re = re.compile(r'^\s*\[\s*pshell\s*\]\s*$') - _section_re = re.compile(r'^\s*\[') + ConfigParser = ConfigParser # testing def pshell_file_config(self, filename): - vars = { - 'here': os.path.dirname(filename), - '__file__': filename, - } - f = open(filename) - lines = f.readlines() - f.close() - lineno = 1 - # find the pshell section - while lines: - if self._pshell_section_re.search(lines[0]): - lines.pop(0) - break - lines.pop(0) - lineno += 1 - # parse pshell section for key/value pairs resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} - for line in lines: - lineno += 1 - line = line.strip() - if not line or line.startswith('#'): - continue - if self._section_re.search(line): - break - if '=' not in line: - raise BadCommand('Missing = in %s at %s: %r' - % (filename, lineno, line)) - name, value = line.split('=', 1) - name = name.strip() - value = value.strip() % vars - self.loaded_objects[name] = resolver.maybe_resolve(value) - self.object_help[name] = value + config = ConfigParser() + config.read(filename) + for k, v in config.items('pshell'): + self.loaded_objects[k] = resolver.maybe_resolve(v) + self.object_help[k] = v def command(self, IPShell=_marker): # IPShell passed to command method is for testing purposes -- cgit v1.2.3 From e6d8e5394aeb1111d955bc21712c93bcb26ffcc0 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Wed, 6 Jul 2011 03:25:52 -0500 Subject: Added tests for the new pshell. --- pyramid/paster.py | 17 +++-- pyramid/tests/test_paster.py | 165 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 155 insertions(+), 27 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 0360a57bb..eabd12a1b 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -1,8 +1,8 @@ +import ConfigParser import os import re import sys from code import interact -from ConfigParser import ConfigParser import zope.deprecation @@ -23,7 +23,7 @@ def get_app(config_file, name=None, loadapp=loadapp): config file ``config_file``. If the ``name`` is None, this will attempt to parse the name from - the ``config_file`` string expecting the format ``ini_path#name``. + the ``config_file`` string expecting the format ``ini_file#name``. If no name is found, the name will default to "main".""" if '#' in config_file: path, section = config_file.split('#', 1) @@ -82,15 +82,19 @@ class PShellCommand(PCommand): dest='disable_ipython', help="Don't use IPython even if it is available") - ConfigParser = ConfigParser # testing + ConfigParser = ConfigParser.ConfigParser # testing def pshell_file_config(self, filename): resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} - config = ConfigParser() + config = self.ConfigParser() config.read(filename) - for k, v in config.items('pshell'): + try: + items = config.items('pshell') + except ConfigParser.NoSectionError: + return + for k, v in items: self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v @@ -115,7 +119,8 @@ class PShellCommand(PCommand): default_variables = {'app': 'The WSGI Application'} if hasattr(app, 'registry'): root, closer = self.get_root(app) - shell_globals.update({'root':root, 'registry':app.registry}) + shell_globals.update({'root':root, 'registry':app.registry, + 'settings': app.registry.settings}) default_variables.update({ 'root': 'The root of the default resource tree.', 'registry': 'The Pyramid registry object.', diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 2bae6aba3..e7a3b7507 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -15,7 +15,8 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -27,8 +28,10 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -39,7 +42,8 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = True @@ -51,8 +55,10 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -61,8 +67,9 @@ class TestPShellCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser({}) dummy_shell_factory = DummyIPShellFactory() - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython = False @@ -75,7 +82,9 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) self.assertEqual(dummy_shell_factory.shell.local_ns, - {'root':dummy_root, 'registry':dummy_registry}) + {'app':app, 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertEqual(dummy_shell_factory.shell.global_ns, {}) self.assertTrue('\n\n' in dummy_shell_factory.shell.IP.BANNER) self.assertEqual(len(app.threadlocal_manager.popped), 1) @@ -92,7 +101,8 @@ class TestPShellCommand(unittest.TestCase): interact = DummyInteractor() app = DummyApp() command.interact = (interact,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.ConfigParser = makeDummyConfigParser({}) + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -101,11 +111,13 @@ class TestPShellCommand(unittest.TestCase): pushed = app.threadlocal_manager.pushed[0] self.assertEqual(pushed['registry'], dummy_registry) self.assertEqual(pushed['request'].registry, dummy_registry) - self.assertEqual(interact.local, {'root':dummy_root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app': app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(len(app.threadlocal_manager.popped), 1) - self.assertEqual(apped, [(('/foo/bar/myapp.ini', 'myapp'), + self.assertEqual(apped, [(('/foo/bar/myapp.ini#myapp',), {'loadapp': loadapp})]) def test_command_get_root_hookable(self): @@ -115,13 +127,14 @@ class TestPShellCommand(unittest.TestCase): loadapp = DummyLoadApp(app) command.interact = (interact,) command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser({}) root = Dummy() apps = [] def get_root(app): apps.append(app) return root, lambda *arg: None command.get_root =get_root - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) class Options(object): pass command.options = Options() command.options.disable_ipython =True @@ -130,11 +143,108 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(loadapp.section_name, 'myapp') self.assertTrue(loadapp.relative_to) self.assertEqual(len(app.threadlocal_manager.pushed), 0) - self.assertEqual(interact.local, {'root':root, - 'registry':dummy_registry}) + self.assertEqual(interact.local, {'app':app, + 'root':root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) self.assertTrue(interact.banner) self.assertEqual(apps, [app]) + def test_command_loads_custom_items(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + model = Dummy() + command.ConfigParser = makeDummyConfigParser([('m', model)]) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(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'].registry, dummy_registry) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings, + 'm': model}) + self.assertTrue(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_command_no_custom_section(self): + command = self._makeOne() + interact = DummyInteractor() + app = DummyApp() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser(None) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(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'].registry, dummy_registry) + self.assertEqual(interact.local, {'app':app, + 'root':dummy_root, + 'registry':dummy_registry, + 'settings':dummy_registry.settings}) + self.assertTrue(interact.banner) + self.assertEqual(len(app.threadlocal_manager.popped), 1) + + def test_command_custom_section_override(self): + command = self._makeOne() + interact = DummyInteractor() + app = Dummy() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + model = Dummy() + command.ConfigParser = makeDummyConfigParser([('app', model)]) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(interact.local, {'app':model}) + self.assertTrue(interact.banner) + + def test_command_generic_wsgi_app(self): + command = self._makeOne() + interact = DummyInteractor() + app = Dummy() + loadapp = DummyLoadApp(app) + command.interact = (interact,) + command.loadapp = (loadapp,) + command.ConfigParser = makeDummyConfigParser(None) + command.args = ('/foo/bar/myapp.ini#myapp',) + class Options(object): pass + command.options = Options() + command.options.disable_ipython = False + command.command(IPShell=None) + self.assertEqual(loadapp.config_name, 'config:/foo/bar/myapp.ini') + self.assertEqual(loadapp.section_name, 'myapp') + self.assertTrue(loadapp.relative_to) + self.assertEqual(interact.local, {'app':app}) + self.assertTrue(interact.banner) + class TestPRoutesCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.paster import PRoutesCommand @@ -152,7 +262,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -165,7 +275,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(L, []) @@ -180,7 +290,7 @@ class TestPRoutesCommand(unittest.TestCase): app = DummyApp() loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -205,7 +315,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -235,7 +345,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -268,7 +378,7 @@ class TestPRoutesCommand(unittest.TestCase): app.registry = registry loadapp = DummyLoadApp(app) command.loadapp = (loadapp,) - command.args = ('/foo/bar/myapp.ini#myapp') + command.args = ('/foo/bar/myapp.ini#myapp',) result = command.command() self.assertEqual(result, None) self.assertEqual(len(L), 3) @@ -844,6 +954,7 @@ class DummyIPShell(object): dummy_root = Dummy() class DummyRegistry(object): + settings = {} def queryUtility(self, iface, default=None, name=''): return default @@ -925,3 +1036,15 @@ class DummyMultiView(object): self.views = [(None, view, None) for view in views] self.__request_attrs__ = attrs +def makeDummyConfigParser(items): + class DummyConfigParser(object): + def read(self, filename): + self.filename = filename + + def items(self, section): + self.section = section + if items is None: + from ConfigParser import NoSectionError + raise NoSectionError, section + return items + return DummyConfigParser -- cgit v1.2.3 From e8561f919548e2fe17f82d98e2a13e1e4e85bf40 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 7 Jul 2011 01:57:18 -0500 Subject: Added/updated documentation for the new interactive shell. --- docs/api/paster.rst | 7 ++-- docs/narr/project.rst | 96 +++++++++++++++++++++++++++++++++------------------ 2 files changed, 68 insertions(+), 35 deletions(-) diff --git a/docs/api/paster.rst b/docs/api/paster.rst index 9ecfa3d9c..6668f3c77 100644 --- a/docs/api/paster.rst +++ b/docs/api/paster.rst @@ -5,9 +5,12 @@ .. module:: pyramid.paster -.. function:: get_app(config_file, name) +.. function:: get_app(config_file, name=None) Return the WSGI application named ``name`` in the PasteDeploy config file ``config_file``. - + If the ``name`` is None, this will attempt to parse the name from + the ``config_file`` string expecting the format ``ini_file#name``. + If no name is found, the name will default to "main". + diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 631412f42..be673c370 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -258,8 +258,9 @@ develop``, you can use an interactive Python shell to examine your :app:`Pyramid` project's :term:`resource` and :term:`view` objects from a Python prompt. To do so, use your virtualenv's ``paster pshell`` command. -The first argument to ``pshell`` is the path to your application's ``.ini`` -file. The second is the ``app`` section name inside the ``.ini`` file which +The argument to ``pshell`` follows the format ``config_file#section_name`` +where ``config_file`` is the path to your application's ``.ini`` file and +``section_name`` is the ``app`` section name inside the ``.ini`` file which points to *your application* as opposed to any other section within the ``.ini`` file. For example, if your application ``.ini`` file might have a ``[app:MyProject]`` section that looks like so: @@ -280,16 +281,21 @@ name ``MyProject`` as a section name: .. code-block:: text - [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini MyProject + [chrism@vitaminf shellenv]$ ../bin/paster pshell development.ini#MyProject 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 Pyramid app root object, - "registry" is the Pyramid registry object. + + Default Variables: + app The WSGI Application + root The root of the default resource tree. + registry The Pyramid registry object. + settings The Pyramid settings object. + >>> root >>> registry - >>> registry.settings['debug_notfound'] + >>> settings['debug_notfound'] False >>> from myproject.views import my_view >>> from pyramid.request import Request @@ -297,31 +303,16 @@ name ``MyProject`` as a section name: >>> my_view(r) {'project': 'myproject'} -Two names are made available to the pshell user as globals: ``root`` and -``registry``. ``root`` is the the object returned by the default :term:`root -factory` in your application. ``registry`` is the :term:`application -registry` object associated with your project's application (often accessed -within view code as ``request.registry``). - -If you have `IPython `_ installed in -the interpreter you use to invoke the ``paster`` command, the ``pshell`` -command will use an IPython interactive shell instead of a standard Python -interpreter shell. If you don't want this to happen, even if you have -IPython installed, you can pass the ``--disable-ipython`` flag to the -``pshell`` command to use a standard Python interpreter shell -unconditionally. - -.. code-block:: text - - [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ - development.ini MyProject +The WSGI application that is loaded will be available in the shell as the +``app`` global. Also, if the application that is loaded is the +:app:`Pyramid` app with no surrounding middleware, the ``root`` object +returned by the default :term:`root factory`, ``registry``, and ``settings`` +will be available. -You should always use a section name argument that refers to the actual -``app`` section within the Paste configuration file that points at your -:app:`Pyramid` application *without any middleware wrapping*. In particular, -a section name is inappropriate as the second argument to ``pshell`` if the -configuration section it names is a ``pipeline`` rather than an ``app``. For -example, if you have the following ``.ini`` file content: +The interactive shell will not be able to load some of the globals like +``root``, ``registry`` and ``settings`` if the section name specified when +loading ``pshell`` is not referencing your :app:`Pyramid` application directly. +For example, if you have the following ``.ini`` file content: .. code-block:: ini :linenos: @@ -341,12 +332,51 @@ example, if you have the following ``.ini`` file content: Use ``MyProject`` instead of ``main`` as the section name argument to ``pshell`` against the above ``.ini`` file (e.g. ``paster pshell -development.ini MyProject``). If you use ``main`` instead, an error will -occur. Use the most specific reference to your application within the -``.ini`` file possible as the section name argument. +development.ini#MyProject``). Press ``Ctrl-D`` to exit the interactive shell (or ``Ctrl-Z`` on Windows). +Extending the Shell +~~~~~~~~~~~~~~~~~~~ + +It is sometimes convenient when using the interactive shell often to have +some variables significant to your application already loaded as globals +when you start the ``pshell``. To facilitate this, ``pshell`` will look +for a special ``[pshell]`` section in your INI file and expose the subsequent +key/value pairs to the shell. + +For example, you want to expose your model to the shell, along with the +database session so that you can mutate the model on an actual database. +Here, we'll assume your model is stored in the ``myapp.models`` package. + +.. code-block:: ini + :linenos: + + [pshell] + m = myapp.models + session = myapp.models.DBSession + t = transaction + +When this INI file is loaded, the extra variables ``m``, ``session`` and +``t`` will be available for use immediately. This happens regardless of +whether the ``registry`` and other special variables are loaded. + +IPython +~~~~~~~ + +If you have `IPython `_ installed in +the interpreter you use to invoke the ``paster`` command, the ``pshell`` +command will use an IPython interactive shell instead of a standard Python +interpreter shell. If you don't want this to happen, even if you have +IPython installed, you can pass the ``--disable-ipython`` flag to the +``pshell`` command to use a standard Python interpreter shell +unconditionally. + +.. code-block:: text + + [chrism@vitaminf shellenv]$ ../bin/paster pshell --disable-ipython \ + development.ini#MyProject + .. index:: single: running an application single: paster serve -- cgit v1.2.3 From 9e7162eb7f584e8afdbc2f04846d0d7e1fcf676c Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Thu, 7 Jul 2011 02:02:48 -0500 Subject: Updated proutes and pviews docs. --- docs/narr/urldispatch.rst | 9 +++++---- docs/narr/viewconfig.rst | 11 ++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/narr/urldispatch.rst b/docs/narr/urldispatch.rst index f94ed3ba8..51a840b8d 100644 --- a/docs/narr/urldispatch.rst +++ b/docs/narr/urldispatch.rst @@ -1084,16 +1084,17 @@ Displaying All Application Routes You can use the ``paster proutes`` command in a terminal window to print a summary of routes related to your application. Much like the ``paster pshell`` command (see :ref:`interactive_shell`), the ``paster proutes`` -command accepts two arguments. The first argument to ``proutes`` is the path -to your application's ``.ini`` file. The second is the ``app`` section name -inside the ``.ini`` file which points to your application. +command accepts one argument with the format ``config_file#section_name``. +The ``config_file`` is the path to your application's ``.ini`` file, +and ``section_name`` is the ``app`` section name inside the ``.ini`` file +which points to your application. For example: .. code-block:: text :linenos: - [chrism@thinko MyProject]$ ../bin/paster proutes development.ini MyProject + [chrism@thinko MyProject]$ ../bin/paster proutes development.ini#MyProject Name Pattern View ---- ------- ---- home / diff --git a/docs/narr/viewconfig.rst b/docs/narr/viewconfig.rst index ec42446ff..67ac39259 100644 --- a/docs/narr/viewconfig.rst +++ b/docs/narr/viewconfig.rst @@ -795,10 +795,11 @@ For a big application with several views, it can be hard to keep the view configuration details in your head, even if you defined all the views yourself. You can use the ``paster pviews`` command in a terminal window to print a summary of matching routes and views for a given URL in your -application. The ``paster pviews`` command accepts three arguments. The -first argument to ``pviews`` is the path to your application's ``.ini`` file. -The second is the ``app`` section name inside the ``.ini`` file which points -to your application. The third is the URL to test for matching views. +application. The ``paster pviews`` command accepts two arguments. The +first argument to ``pviews`` is the path to your application's ``.ini`` file +and section name inside the ``.ini`` file which points to your application. +This should be of the format ``config_file#section_name``. The second argument +is the URL to test for matching views. Here is an example for a simple view configuration using :term:`traversal`: @@ -829,7 +830,7 @@ A more complex configuration might generate something like this: .. code-block:: text :linenos: - $ ../bin/paster pviews development.ini shootout /about + $ ../bin/paster pviews development.ini#shootout /about URL = /about -- cgit v1.2.3