From 87b0c53830fed9de60bd97996630b4b20228df09 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 01:54:10 -0500 Subject: Added support for 'import' key in [pshell] ini section. --- pyramid/paster.py | 35 +++++++++++++++++++++++++++----- pyramid/tests/pshellapp/__init__.py | 7 +++++++ pyramid/tests/test_paster.py | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 pyramid/tests/pshellapp/__init__.py diff --git a/pyramid/paster.py b/pyramid/paster.py index f9765a07c..87abdddd4 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -132,19 +132,28 @@ class PShellCommand(PCommand): ConfigParser = ConfigParser.ConfigParser # testing + loaded_objects = {} + object_help = {} + use_script = None + def pshell_file_config(self, filename): - resolver = DottedNameResolver(None) - self.loaded_objects = {} - self.object_help = {} config = self.ConfigParser() config.read(filename) try: items = config.items('pshell') except ConfigParser.NoSectionError: return + + resolver = DottedNameResolver(None) + self.loaded_objects = {} + self.object_help = {} + self.use_script = None for k, v in items: - self.loaded_objects[k] = resolver.maybe_resolve(v) - self.object_help[k] = v + if k == 'import': + self.use_script = v + else: + self.loaded_objects[k] = resolver.maybe_resolve(v) + self.object_help[k] = v def command(self, shell=None): config_uri = self.args[0] @@ -167,6 +176,19 @@ class PShellCommand(PCommand): env_help['root_factory'] = ( 'Default root factory used to create `root`.') + if self.use_script: + # store the env before muddling it with the script + orig_env = env.copy() + + # do this instead of an eval() to respect __all__ + exec 'from %s import *' % self.use_script in env + env.pop('__builtins__', None) + + # remove any objects from default help that were overidden + for k, v in orig_env.iteritems(): + if env[k] != orig_env[k]: + del env_help[k] + # load the pshell section of the ini file env.update(self.loaded_objects) @@ -187,6 +209,9 @@ class PShellCommand(PCommand): for var in sorted(self.object_help.keys()): help += '\n %-12s %s' % (var, self.object_help[var]) + if self.use_script: + help += '\n\nAll objects from %s are available.' % self.use_script + if shell is None and not self.options.disable_ipython: shell = self.make_ipython_v0_11_shell() if shell is None: diff --git a/pyramid/tests/pshellapp/__init__.py b/pyramid/tests/pshellapp/__init__.py new file mode 100644 index 000000000..dd0a3e2f9 --- /dev/null +++ b/pyramid/tests/pshellapp/__init__.py @@ -0,0 +1,7 @@ +__all__ = ['a', 'root', 'm'] + +a = 1 +b = 2 + +root = 'root override' +m = 'model override' diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 36c3a51be..45e36f129 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -156,6 +156,46 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) + def test_command_loads_use_script(self): + command = self._makeOne() + self.config_factory.items = [('import', 'pyramid.tests.pshellapp')] + shell = DummyShell() + command.command(shell) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':'root override', + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'a': 1, 'm': 'model override', + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + + def test_command_loads_use_script_check_order(self): + command = self._makeOne() + model = Dummy() + self.config_factory.items = [('import', 'pyramid.tests.pshellapp'), + ('m', model)] + shell = DummyShell() + command.command(shell) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':'root override', + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'a':1, 'm':model, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + def test_command_custom_section_override(self): command = self._makeOne() dummy = Dummy() -- cgit v1.2.3 From a5d9ff4e135feb8a8132cfd93dcb51fa2eacd35d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 02:07:16 -0500 Subject: Added support for --import option to pshell. --- pyramid/paster.py | 9 +++++++++ pyramid/tests/test_paster.py | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/pyramid/paster.py b/pyramid/paster.py index 87abdddd4..99ead1af3 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -129,6 +129,11 @@ class PShellCommand(PCommand): action='store_true', dest='disable_ipython', help="Don't use IPython even if it is available") + parser.add_option('--import-script', + dest='use_script', + help=("Execute the script and import all variables from " + "a dotted Python path. This option will override " + "the 'import' key in the [pshell] ini section.")) ConfigParser = ConfigParser.ConfigParser # testing @@ -176,6 +181,10 @@ class PShellCommand(PCommand): env_help['root_factory'] = ( 'Default root factory used to create `root`.') + # override use_script with command-line options + if self.options.use_script: + self.use_script = self.options.use_script + if self.use_script: # store the env before muddling it with the script orig_env = env.copy() diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 45e36f129..cb30dcc12 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -21,6 +21,7 @@ class TestPShellCommand(unittest.TestCase): class Options(object): pass self.options = Options() self.options.disable_ipython = True + self.options.use_script = None cmd.options = self.options return cmd @@ -196,6 +197,28 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) + def test_command_loads_use_script_override(self): + command = self._makeOne() + model = Dummy() + self.config_factory.items = [('import', 'abc'), + ('m', model)] + command.options.use_script = 'pyramid.tests.pshellapp' + shell = DummyShell() + command.command(shell) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':'root override', + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'a':1, 'm':model, + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + def test_command_custom_section_override(self): command = self._makeOne() dummy = Dummy() -- cgit v1.2.3 From 8a455dbc824393899fd465f6816091f789eb7e1d Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 02:30:36 -0500 Subject: Minor change to help output. --- pyramid/paster.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyramid/paster.py b/pyramid/paster.py index 99ead1af3..7beeada7a 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -207,7 +207,7 @@ class PShellCommand(PCommand): del env_help[k] # generate help text - help = '\n' + help = '' if env_help: help += 'Environment:' for var in sorted(env_help.keys()): @@ -238,7 +238,7 @@ class PShellCommand(PCommand): def shell(env, help): cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) - banner += '\n' + help + '\n' + banner += '\n\n' + help + '\n' interact(banner, local=env) return shell @@ -251,7 +251,7 @@ class PShellCommand(PCommand): except ImportError: return None def shell(env, help): - IPShell = IPShellFactory(banner2=help, user_ns=env) + IPShell = IPShellFactory(banner2=help + '\n', user_ns=env) IPShell() return shell -- cgit v1.2.3 From 6007dd9a6a22e1112afa6a72ca2ee406698d775f Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 03:09:34 -0500 Subject: Added tests for module without __all__. --- pyramid/tests/pshellapp/__init__.py | 7 ------- pyramid/tests/pshellapp/no_all.py | 5 +++++ pyramid/tests/pshellapp/with_all.py | 7 +++++++ pyramid/tests/test_paster.py | 33 +++++++++++++++++++++++++++------ 4 files changed, 39 insertions(+), 13 deletions(-) create mode 100644 pyramid/tests/pshellapp/no_all.py create mode 100644 pyramid/tests/pshellapp/with_all.py diff --git a/pyramid/tests/pshellapp/__init__.py b/pyramid/tests/pshellapp/__init__.py index dd0a3e2f9..e69de29bb 100644 --- a/pyramid/tests/pshellapp/__init__.py +++ b/pyramid/tests/pshellapp/__init__.py @@ -1,7 +0,0 @@ -__all__ = ['a', 'root', 'm'] - -a = 1 -b = 2 - -root = 'root override' -m = 'model override' diff --git a/pyramid/tests/pshellapp/no_all.py b/pyramid/tests/pshellapp/no_all.py new file mode 100644 index 000000000..177a34590 --- /dev/null +++ b/pyramid/tests/pshellapp/no_all.py @@ -0,0 +1,5 @@ +a = 1 +b = 2 + +root = 'root override' +m = 'model override' diff --git a/pyramid/tests/pshellapp/with_all.py b/pyramid/tests/pshellapp/with_all.py new file mode 100644 index 000000000..dd0a3e2f9 --- /dev/null +++ b/pyramid/tests/pshellapp/with_all.py @@ -0,0 +1,7 @@ +__all__ = ['a', 'root', 'm'] + +a = 1 +b = 2 + +root = 'root override' +m = 'model override' diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index cb30dcc12..49a54aecc 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -157,9 +157,10 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) - def test_command_loads_use_script(self): + def test_command_loads_use_script_with_all(self): command = self._makeOne() - self.config_factory.items = [('import', 'pyramid.tests.pshellapp')] + self.config_factory.items = [ + ('import', 'pyramid.tests.pshellapp.with_all')] shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) @@ -176,11 +177,31 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) - def test_command_loads_use_script_check_order(self): + def test_command_loads_use_script_without_all(self): + command = self._makeOne() + self.config_factory.items = [ + ('import', 'pyramid.tests.pshellapp.no_all')] + shell = DummyShell() + command.command(shell) + self.assertTrue(self.config_factory.parser) + self.assertEqual(self.config_factory.parser.filename, + '/foo/bar/myapp.ini') + self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') + self.assertEqual(shell.env, { + 'app':self.bootstrap.app, 'root':'root override', + 'registry':self.bootstrap.registry, + 'request':self.bootstrap.request, + 'root_factory':self.bootstrap.root_factory, + 'a': 1, 'b': 2, 'm': 'model override', + }) + self.assertTrue(self.bootstrap.closer.called) + self.assertTrue(shell.help) + + def test_command_loads_check_variable_override_order(self): command = self._makeOne() model = Dummy() - self.config_factory.items = [('import', 'pyramid.tests.pshellapp'), - ('m', model)] + self.config_factory.items = [ + ('import', 'pyramid.tests.pshellapp.with_all'), ('m', model)] shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) @@ -202,7 +223,7 @@ class TestPShellCommand(unittest.TestCase): model = Dummy() self.config_factory.items = [('import', 'abc'), ('m', model)] - command.options.use_script = 'pyramid.tests.pshellapp' + command.options.use_script = 'pyramid.tests.pshellapp.with_all' shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) -- cgit v1.2.3 From a83d944e5ca7fc565ea947b5c89f3f0710cd2ec8 Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 03:53:37 -0500 Subject: Added an initialization callable to pshell. Modified the use_script idea into a callable called 'setup', which expects the API: def setup(env): env['a'] = 1 --- pyramid/paster.py | 41 ++++++++++++++++--------------- pyramid/tests/pshellapp/__init__.py | 0 pyramid/tests/pshellapp/no_all.py | 5 ---- pyramid/tests/pshellapp/with_all.py | 7 ------ pyramid/tests/test_paster.py | 48 ++++++++++++++----------------------- 5 files changed, 38 insertions(+), 63 deletions(-) delete mode 100644 pyramid/tests/pshellapp/__init__.py delete mode 100644 pyramid/tests/pshellapp/no_all.py delete mode 100644 pyramid/tests/pshellapp/with_all.py diff --git a/pyramid/paster.py b/pyramid/paster.py index 7beeada7a..ec55dc126 100644 --- a/pyramid/paster.py +++ b/pyramid/paster.py @@ -129,17 +129,18 @@ class PShellCommand(PCommand): action='store_true', dest='disable_ipython', help="Don't use IPython even if it is available") - parser.add_option('--import-script', - dest='use_script', - help=("Execute the script and import all variables from " - "a dotted Python path. This option will override " - "the 'import' key in the [pshell] ini section.")) + parser.add_option('--setup', + dest='setup', + help=("A callable that will be passed the environment " + "before it is made available to the shell. This " + "option will override the 'setup' key in the " + "[pshell] ini section.")) ConfigParser = ConfigParser.ConfigParser # testing loaded_objects = {} object_help = {} - use_script = None + setup = None def pshell_file_config(self, filename): config = self.ConfigParser() @@ -152,10 +153,10 @@ class PShellCommand(PCommand): resolver = DottedNameResolver(None) self.loaded_objects = {} self.object_help = {} - self.use_script = None + self.setup = None for k, v in items: - if k == 'import': - self.use_script = v + if k == 'setup': + self.setup = v else: self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v @@ -182,21 +183,22 @@ class PShellCommand(PCommand): 'Default root factory used to create `root`.') # override use_script with command-line options - if self.options.use_script: - self.use_script = self.options.use_script + if self.options.setup: + self.setup = self.options.setup - if self.use_script: + if self.setup: # store the env before muddling it with the script orig_env = env.copy() - # do this instead of an eval() to respect __all__ - exec 'from %s import *' % self.use_script in env - env.pop('__builtins__', None) + # call the setup callable + resolver = DottedNameResolver(None) + setup = resolver.maybe_resolve(self.setup) + setup(env) # remove any objects from default help that were overidden - for k, v in orig_env.iteritems(): - if env[k] != orig_env[k]: - del env_help[k] + for k, v in env.iteritems(): + if k not in orig_env or env[k] != orig_env[k]: + env_help[k] = v # load the pshell section of the ini file env.update(self.loaded_objects) @@ -218,9 +220,6 @@ class PShellCommand(PCommand): for var in sorted(self.object_help.keys()): help += '\n %-12s %s' % (var, self.object_help[var]) - if self.use_script: - help += '\n\nAll objects from %s are available.' % self.use_script - if shell is None and not self.options.disable_ipython: shell = self.make_ipython_v0_11_shell() if shell is None: diff --git a/pyramid/tests/pshellapp/__init__.py b/pyramid/tests/pshellapp/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/pyramid/tests/pshellapp/no_all.py b/pyramid/tests/pshellapp/no_all.py deleted file mode 100644 index 177a34590..000000000 --- a/pyramid/tests/pshellapp/no_all.py +++ /dev/null @@ -1,5 +0,0 @@ -a = 1 -b = 2 - -root = 'root override' -m = 'model override' diff --git a/pyramid/tests/pshellapp/with_all.py b/pyramid/tests/pshellapp/with_all.py deleted file mode 100644 index dd0a3e2f9..000000000 --- a/pyramid/tests/pshellapp/with_all.py +++ /dev/null @@ -1,7 +0,0 @@ -__all__ = ['a', 'root', 'm'] - -a = 1 -b = 2 - -root = 'root override' -m = 'model override' diff --git a/pyramid/tests/test_paster.py b/pyramid/tests/test_paster.py index 49a54aecc..995e05d46 100644 --- a/pyramid/tests/test_paster.py +++ b/pyramid/tests/test_paster.py @@ -21,7 +21,7 @@ class TestPShellCommand(unittest.TestCase): class Options(object): pass self.options = Options() self.options.disable_ipython = True - self.options.use_script = None + self.options.setup = None cmd.options = self.options return cmd @@ -157,10 +157,12 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) - def test_command_loads_use_script_with_all(self): + def test_command_setup(self): command = self._makeOne() - self.config_factory.items = [ - ('import', 'pyramid.tests.pshellapp.with_all')] + def setup(env): + env['a'] = 1 + env['root'] = 'root override' + self.config_factory.items = [('setup', setup)] shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) @@ -172,27 +174,7 @@ class TestPShellCommand(unittest.TestCase): 'registry':self.bootstrap.registry, 'request':self.bootstrap.request, 'root_factory':self.bootstrap.root_factory, - 'a': 1, 'm': 'model override', - }) - self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(shell.help) - - def test_command_loads_use_script_without_all(self): - command = self._makeOne() - self.config_factory.items = [ - ('import', 'pyramid.tests.pshellapp.no_all')] - shell = DummyShell() - command.command(shell) - self.assertTrue(self.config_factory.parser) - self.assertEqual(self.config_factory.parser.filename, - '/foo/bar/myapp.ini') - self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') - self.assertEqual(shell.env, { - 'app':self.bootstrap.app, 'root':'root override', - 'registry':self.bootstrap.registry, - 'request':self.bootstrap.request, - 'root_factory':self.bootstrap.root_factory, - 'a': 1, 'b': 2, 'm': 'model override', + 'a':1, }) self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) @@ -200,8 +182,11 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_check_variable_override_order(self): command = self._makeOne() model = Dummy() - self.config_factory.items = [ - ('import', 'pyramid.tests.pshellapp.with_all'), ('m', model)] + def setup(env): + env['a'] = 1 + env['m'] = 'model override' + env['root'] = 'root override' + self.config_factory.items = [('setup', setup), ('m', model)] shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) @@ -218,12 +203,15 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) - def test_command_loads_use_script_override(self): + def test_command_loads_setup_from_options(self): command = self._makeOne() + def setup(env): + env['a'] = 1 + env['root'] = 'root override' model = Dummy() - self.config_factory.items = [('import', 'abc'), + self.config_factory.items = [('setup', 'abc'), ('m', model)] - command.options.use_script = 'pyramid.tests.pshellapp.with_all' + command.options.setup = setup shell = DummyShell() command.command(shell) self.assertTrue(self.config_factory.parser) -- cgit v1.2.3 From 33a3ead51c9ef4dcc4c4c839aae8616dcab2d3ce Mon Sep 17 00:00:00 2001 From: Michael Merickel Date: Tue, 16 Aug 2011 04:28:52 -0500 Subject: Added some docs for the new 'setup' option key in [pshell]. --- docs/narr/commandline.rst | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 1c9d0b15c..bb004c446 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -156,6 +156,7 @@ name ``MyProject`` as a section name: request Active request object. root Root of the default resource tree. root_factory Default root factory used to create `root`. + >>> root >>> registry @@ -191,12 +192,18 @@ 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 +It is 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. Each key is a variable name that will be global within the pshell session; each value is a :term:`dotted Python name`. +If specified, the special key ``setup`` should be a :term:`dotted Python name` +pointing to a callable that accepts the dictionary of globals that will +be loaded into the shell. This allows for some custom initializing code +to be executed each time the ``pshell`` is run. The ``setup`` callable +can also be specified from the commandline using the ``--setup`` option +which will override the key in the INI file. 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. @@ -206,12 +213,33 @@ Here, we'll assume your model is stored in the ``myapp.models`` package. :linenos: [pshell] + setup = myapp.lib.pshell.setup m = myapp.models session = myapp.models.DBSession t = transaction +By defining the ``setup`` callable, we will create the module +``myapp.lib.pshell`` containing a callable named ``setup`` that will receive +the global environment before it is exposed to the shell. Here we mutate the +environment's request as well as add a new value containing a WebTest version +of the application to which we can easily submit requests. + +.. code-block:: python + :linenos: + + # myapp/lib/pshell.py + from webtest import TestApp + + def setup(env): + env['request'].host = 'www.example.com' + env['request'].scheme = 'https' + env['testapp'] = TestApp(env['app']) + When this INI file is loaded, the extra variables ``m``, ``session`` and -``t`` will be available for use immediately. For example: +``t`` will be available for use immediately. Since a ``setup`` callable +was also specified, it is executed and a new variable ``testapp`` is +exposed, and the request is configured to generate urls from the host +``http://www.example.com``. For example: .. code-block:: text @@ -226,12 +254,17 @@ When this INI file is loaded, the extra variables ``m``, ``session`` and request Active request object. root Root of the default resource tree. root_factory Default root factory used to create `root`. + testapp Custom Variables: m myapp.models session myapp.models.DBSession t transaction - >>> + + >>> testapp.get('/') + <200 OK text/html body='\n'/3337> + >>> request.route_url('home') + 'https://www.example.com/' .. index:: single: IPython -- cgit v1.2.3