diff options
| author | Michael Merickel <michael@merickel.org> | 2015-10-19 23:44:17 -0500 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2015-10-19 23:44:17 -0500 |
| commit | 208e7b5e363b07476797d9f754962982c686e907 (patch) | |
| tree | d0a159c8cdb9f93946f4b16261649cc9a1bd7d42 | |
| parent | b8735932b92dd255b5dba467d8f52b9014e06544 (diff) | |
| download | pyramid-208e7b5e363b07476797d9f754962982c686e907.tar.gz pyramid-208e7b5e363b07476797d9f754962982c686e907.tar.bz2 pyramid-208e7b5e363b07476797d9f754962982c686e907.zip | |
add pshell --list and default_shell ini options
| -rw-r--r-- | docs/narr/commandline.rst | 59 | ||||
| -rw-r--r-- | pyramid/scripts/pshell.py | 66 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/dummy.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_pshell.py | 97 | ||||
| -rw-r--r-- | setup.py | 1 |
5 files changed, 196 insertions, 30 deletions
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 9db92b669..c3791adf2 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -269,32 +269,39 @@ request is configured to generate urls from the host .. _ipython_or_bpython: -IPython or bpython +Alternative Shells ~~~~~~~~~~~~~~~~~~ - If you have `IPython <http://en.wikipedia.org/wiki/IPython>`_ and/or `bpython <http://bpython-interpreter.org/>`_ in the interpreter you use to invoke the -``pshell`` command, ``pshell`` will autodiscover and use the first one found, -in this order: IPython, bpython, standard Python interpreter. However you could -specifically invoke your choice with the ``-p choice`` or ``--python-shell -choice`` option. +``pshell`` command, ``pshell`` will autodiscover and use the first one found. +However you could specifically invoke your choice with the ``-p choice`` or +``--python-shell choice`` option. .. code-block:: text - $ $VENV/bin/pshell -p ipython | bpython | python development.ini#MyProject + $ $VENV/bin/pshell -p ipython development.ini#MyProject + +You may use the ``--list-shells`` option to see the available shells. + +.. code-block:: text + + $ $VENV/bin/pshell --list-shells + Available shells: + bpython [not available] + ipython + python -Alternative Shells -~~~~~~~~~~~~~~~~~~ If you want to use a shell that isn't supported out of the box, you can introduce a new shell by registering an entry point in your setup.py: .. code-block:: python setup( - entry_points = """\ - [pyramid.pshell] - myshell=my_app:ptpython_shell_factory - """ + entry_points={ + 'pyramid.pshell': [ + 'myshell=my_app:ptpython_shell_factory', + ], + }, ) And then your shell factory should return a function that accepts two @@ -303,7 +310,12 @@ arguments, ``env`` and ``help``, which would look like this: .. code-block:: python def ptpython_shell_factory(): - from ptpython.repl import embed + try: + from ptpython.repl import embed + except ImportError: + # ptpython is not installed + return None + def PTPShell(banner, **kwargs): print(banner) return embed(**kwargs) @@ -313,6 +325,25 @@ arguments, ``env`` and ``help``, which would look like this: return shell +If the factory returns ``None`` then it is assumed that the shell is not +supported. + +.. versionchanged:: 1.6 + User-defined shells may be registered using entry points. Prior to this + the only supported shells were ``ipython``, ``bpython`` and ``python``. + +Setting a Default Shell +~~~~~~~~~~~~~~~~~~~~~~~ + +You may use the ``default_shell`` option in your ``[pshell]`` ini section to +specify a list of preferred shells. + +.. code-block:: ini + :linenos: + + [pshell] + default_shell = ptpython ipython bpython + .. versionadded:: 1.6 .. index:: diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py index 5913220fc..35c2245e3 100644 --- a/pyramid/scripts/pshell.py +++ b/pyramid/scripts/pshell.py @@ -12,6 +12,8 @@ from pyramid.paster import bootstrap from pyramid.paster import setup_logging +from pyramid.settings import aslist + from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): @@ -43,7 +45,14 @@ class PShellCommand(object): ) parser.add_option('-p', '--python-shell', action='store', type='string', dest='python_shell', - default='', help='ipython | bpython | python') + default='', + help=('Select the shell to use. A list of possible ' + 'shells is available using the --list-shells ' + 'option.')) + parser.add_option('-l', '--list-shells', + dest='list', + action='store_true', + help='List all available shells.') parser.add_option('--setup', dest='setup', help=("A callable that will be passed the environment " @@ -55,6 +64,7 @@ class PShellCommand(object): loaded_objects = {} object_help = {} + preferred_shells = [] setup = None pystartup = os.environ.get('PYTHONSTARTUP') @@ -64,6 +74,7 @@ class PShellCommand(object): def pshell_file_config(self, filename): config = self.ConfigParser() + config.optionxform = str config.read(filename) try: items = config.items('pshell') @@ -77,6 +88,8 @@ class PShellCommand(object): for k, v in items: if k == 'setup': self.setup = v + elif k == 'default_shell': + self.preferred_shells = [x.lower() for x in aslist(v)] else: self.loaded_objects[k] = resolver.maybe_resolve(v) self.object_help[k] = v @@ -86,6 +99,8 @@ class PShellCommand(object): print(msg) def run(self, shell=None): + if self.options.list: + return self.show_shells() if not self.args: self.out('Requires a config file argument') return 2 @@ -169,19 +184,50 @@ class PShellCommand(object): finally: self.closer() - def make_shell(self): - shells = {} + def show_shells(self): + shells = self.find_all_shells() + sorted_shells = sorted(shells.items(), key=lambda x: x[0].lower()) + max_name = max([len(s) for s in shells]) + self.out('Available shells:') + for name, factory in sorted_shells: + shell = factory() + if shell is not None: + self.out(' %s' % (name,)) + else: + self.out(' %s%s [not available]' % ( + name, + ' ' * (max_name - len(name)))) + return 0 + + def find_all_shells(self): + shells = {} for ep in self.pkg_resources.iter_entry_points('pyramid.pshell'): name = ep.name - shell_module = ep.load() - shells[name] = shell_module + shell_factory = ep.load() + shells[name] = shell_factory + return shells + + def make_shell(self): + shells = self.find_all_shells() shell = None user_shell = self.options.python_shell.lower() if not user_shell: - sorted_shells = sorted(shells.items(), key=lambda x: x[0]) + preferred_shells = self.preferred_shells + if not preferred_shells: + # by default prioritize all shells above python + preferred_shells = [k for k in shells.keys() if k != 'python'] + max_weight = len(preferred_shells) + def order(x): + # invert weight to reverse sort the list + # (closer to the front is higher priority) + try: + return preferred_shells.index(x[0].lower()) - max_weight + except ValueError: + return 1 + sorted_shells = sorted(shells.items(), key=order) for name, factory in sorted_shells: shell = factory() @@ -192,7 +238,8 @@ class PShellCommand(object): if factory is not None: shell = factory() - else: + + if shell is None: raise ValueError( 'could not find a shell named "%s"' % user_shell ) @@ -202,7 +249,8 @@ class PShellCommand(object): return shell - def make_default_shell(self, interact=interact): + @classmethod + def make_python_shell(cls, interact=interact): def shell(env, help): cprt = 'Type "help" for more information.' banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt) @@ -210,6 +258,8 @@ class PShellCommand(object): interact(banner, local=env) return shell + make_default_shell = make_python_shell + @classmethod def make_bpython_shell(cls, BPShell=None): if BPShell is None: # pragma: no cover diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 2788c0b32..49e7d822f 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -21,10 +21,12 @@ dummy_registry = DummyRegistry() class DummyShell(object): env = {} help = '' + called = False def __call__(self, env, help): self.env = env self.help = help + self.called = True class DummyInteractor: def __call__(self, banner, local): @@ -35,6 +37,7 @@ class DummyBPythonShell: def __call__(self, locals_, banner): self.locals_ = locals_ self.banner = banner + self.called = True class DummyIPShell(object): IP = Dummy() diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py index 034f2109d..d4ce315b8 100644 --- a/pyramid/tests/test_scripts/test_pshell.py +++ b/pyramid/tests/test_scripts/test_pshell.py @@ -26,6 +26,7 @@ class TestPShellCommand(unittest.TestCase): self.options = Options() self.options.python_shell = '' self.options.setup = None + self.options.list = None cmd.options = self.options # default to None to prevent side-effects from running tests in @@ -52,7 +53,7 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(bpython.locals_, {'foo': 'bar'}) self.assertTrue('a help message' in bpython.banner) - def test_make_ipython_v1_1_shell(self): + def test_make_ipython_shell(self): command = self._makeOne() ipshell_factory = dummy.DummyIPShellFactory() shell = command.make_ipython_shell(ipshell_factory) @@ -69,6 +70,7 @@ class TestPShellCommand(unittest.TestCase): { 'ipython': lambda: None, 'bpython': lambda: None, + 'python': lambda: None, } ) @@ -87,7 +89,7 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) - def test_command_loads_default_shell_with_unknown_shell(self): + def test_command_errors_with_unknown_shell(self): command = self._makeOne() out_calls = [] @@ -120,9 +122,10 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(self.bootstrap.a[0], '/foo/bar/myapp.ini#myapp') self.assertTrue(self.bootstrap.closer.called) - def test_command_loads_ipython_v1_1(self): + def test_command_loads_ipython(self): command = self._makeOne() shell = dummy.DummyShell() + bad_shell = dummy.DummyShell() self._makeEntryPoints( command, { @@ -190,7 +193,7 @@ class TestPShellCommand(unittest.TestCase): shell = command.make_shell() self.assertEqual(shell, dshell) - def test_shell_ordering(self): + def test_shell_override(self): command = self._makeOne() ipshell = dummy.DummyShell() bpshell = dummy.DummyShell() @@ -210,12 +213,10 @@ class TestPShellCommand(unittest.TestCase): self.assertEqual(shell, dshell) command.options.python_shell = 'ipython' - shell = command.make_shell() - self.assertEqual(shell, dshell) + self.assertRaises(ValueError, command.make_shell) command.options.python_shell = 'bpython' - shell = command.make_shell() - self.assertEqual(shell, dshell) + self.assertRaises(ValueError, command.make_shell) self._makeEntryPoints( command, @@ -238,6 +239,35 @@ class TestPShellCommand(unittest.TestCase): shell = command.make_shell() self.assertEqual(shell, dshell) + def test_shell_ordering(self): + command = self._makeOne() + ipshell = dummy.DummyShell() + bpshell = dummy.DummyShell() + dshell = dummy.DummyShell() + + self._makeEntryPoints( + command, + { + 'ipython': lambda: ipshell, + 'bpython': lambda: bpshell, + 'python': lambda: dshell, + } + ) + + command.make_default_shell = lambda: dshell + + command.preferred_shells = ['ipython', 'bpython'] + shell = command.make_shell() + self.assertEqual(shell, ipshell) + + command.preferred_shells = ['bpython', 'python'] + shell = command.make_shell() + self.assertEqual(shell, bpshell) + + command.preferred_shells = ['python', 'ipython'] + shell = command.make_shell() + self.assertEqual(shell, dshell) + def test_command_loads_custom_items(self): command = self._makeOne() model = dummy.Dummy() @@ -282,6 +312,27 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) + def test_command_default_shell_option(self): + command = self._makeOne() + ipshell = dummy.DummyShell() + dshell = dummy.DummyShell() + self._makeEntryPoints( + command, + { + 'ipython': lambda: ipshell, + 'bpython': lambda: None, + 'python': lambda: dshell, + } + ) + self.config_factory.items = [ + ('default_shell', 'bpython python\nipython')] + command.run() + 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.assertTrue(dshell.called) + def test_command_loads_check_variable_override_order(self): command = self._makeOne() model = dummy.Dummy() @@ -369,6 +420,36 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.help) + def test_list_shells(self): + command = self._makeOne() + + dshell = dummy.DummyShell() + out_calls = [] + + def out(msg): + out_calls.append(msg) + + command.out = out + + self._makeEntryPoints( + command, + { + 'ipython': lambda: dshell, + 'bpython': lambda: None, + 'python': lambda: dshell, + } + ) + + command.options.list = True + result = command.run() + self.assertEqual(result, 0) + self.assertEqual(out_calls, [ + 'Available shells:', + ' bpython [not available]', + ' ipython', + ' python', + ]) + class Test_main(unittest.TestCase): def _callFUT(self, argv): from pyramid.scripts.pshell import main @@ -114,6 +114,7 @@ setup(name='pyramid', [pyramid.pshell] ipython=pyramid.scripts.pshell:PShellCommand.make_ipython_shell bpython=pyramid.scripts.pshell:PShellCommand.make_bpython_shell + python=pyramid.scripts.pshell:PShellCommand.make_python_shell [console_scripts] pcreate = pyramid.scripts.pcreate:main pserve = pyramid.scripts.pserve:main |
