diff options
| author | Michael Merickel <michael@merickel.org> | 2015-10-11 12:36:25 -0500 |
|---|---|---|
| committer | Michael Merickel <michael@merickel.org> | 2015-10-11 12:36:25 -0500 |
| commit | 803f94d51f5d630a99b9aa2c47f7de40a5d75ea1 (patch) | |
| tree | 5354cf61b243049703d211854fc9f19d48696ef0 | |
| parent | 9b674ffd9a014c0eb2b6cd620164f85ea855e229 (diff) | |
| parent | b932a45f834ae6bdb5850a2c58d1317844b32914 (diff) | |
| download | pyramid-803f94d51f5d630a99b9aa2c47f7de40a5d75ea1.tar.gz pyramid-803f94d51f5d630a99b9aa2c47f7de40a5d75ea1.tar.bz2 pyramid-803f94d51f5d630a99b9aa2c47f7de40a5d75ea1.zip | |
Merge pull request #1891 from sontek/use_entry_points_for_shells
Use entry points for pshell
| -rw-r--r-- | CHANGES.txt | 3 | ||||
| -rw-r--r-- | docs/narr/commandline.rst | 38 | ||||
| -rw-r--r-- | pyramid/scripts/pshell.py | 88 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/dummy.py | 23 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_pshell.py | 190 | ||||
| -rw-r--r-- | setup.py | 3 |
6 files changed, 184 insertions, 161 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 7c8a9080a..8ed3e1137 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -134,6 +134,9 @@ Features that as the response class instead of the default ``HTTPFound``. See https://github.com/Pylons/pyramid/pull/1610 +- Additional shells for ``pshell`` can now be registered as entrypoints. See + https://github.com/Pylons/pyramid/pull/1891 + Bug Fixes --------- diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst index 1fe2d9278..cb0c914d7 100644 --- a/docs/narr/commandline.rst +++ b/docs/narr/commandline.rst @@ -245,7 +245,7 @@ exposed, and the request is configured to generate urls from the host .. code-block:: text $ $VENV/bin/pshell starter/development.ini - Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) + Python 2.6.5 (r265:79063, Apr 29 2010, 00:31:32) [GCC 4.4.3] on linux2 Type "help" for more information. @@ -278,16 +278,46 @@ IPython or bpython 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 +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 one of your choice with the ``-p choice`` or +IPython, bpython, standard Python interpreter. However you could +specifically invoke one of your choice with the ``-p choice`` or ``--python-shell choice`` option. .. code-block:: text $ $VENV/bin/pshell -p ipython | bpython | python development.ini#MyProject +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 entrypoint in your setup.py: + +.. code-block:: python + + setup( + entry_points = """\ + [pyramid.pshell] + myshell=my_app.ptpython_shell_factory + """ + ) + +and then your shell factory should return a function that accepts two arguments, +``env`` and ``help``, this would look like this: + +.. code-block:: python + + def ptpython_shell_factory(): + from ptpython.repl import embed + def PTPShell(banner, **kwargs): + print(banner) + return embed(**kwargs) + + def shell(env, help): + PTPShell(banner=help, locals=env) + + return shell + .. index:: pair: routes; printing single: proutes diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py index 1168ba78a..9c2573caa 100644 --- a/pyramid/scripts/pshell.py +++ b/pyramid/scripts/pshell.py @@ -3,6 +3,7 @@ import optparse import os import sys import textwrap +import pkg_resources from pyramid.compat import configparser from pyramid.compat import exec_ @@ -17,6 +18,7 @@ def main(argv=sys.argv, quiet=False): command = PShellCommand(argv, quiet) return command.run() + class PShellCommand(object): usage = '%prog config_uri' description = """\ @@ -32,7 +34,8 @@ class PShellCommand(object): than one Pyramid application within it, the loader will use the last one. """ - bootstrap = (bootstrap,) # for testing + bootstrap = (bootstrap,) # for testing + pkg_resources = pkg_resources # for testing parser = optparse.OptionParser( usage, @@ -95,7 +98,7 @@ class PShellCommand(object): env = self.bootstrap[0](config_uri, options=parse_vars(self.args[1:])) # remove the closer from the env - closer = env.pop('closer') + self.closer = env.pop('closer') # setup help text for default environment env_help = dict(env) @@ -145,7 +148,12 @@ class PShellCommand(object): help += '\n %-12s %s' % (var, self.object_help[var]) if shell is None: - shell = self.make_shell() + try: + shell = self.make_shell() + except ValueError as e: + self.out(str(e)) + self.closer() + return 1 if self.pystartup and os.path.isfile(self.pystartup): with open(self.pystartup, 'rb') as fp: @@ -156,21 +164,40 @@ class PShellCommand(object): try: shell(env, help) finally: - closer() + self.closer() def make_shell(self): + shells = {} + + priority_order = ['ipython', 'bpython', 'python'] + + for ep in self.pkg_resources.iter_entry_points('pyramid.pshell'): + name = ep.name + shell_module = ep.load() + shells[name] = shell_module + + sorted_shells = sorted( + shells.items(), key=lambda x: priority_order.index(x[0]) + ) + shell = None user_shell = self.options.python_shell.lower() + if not user_shell: - shell = self.make_ipython_shell() - if shell is None: - shell = self.make_bpython_shell() + for name, factory in sorted_shells: + shell = factory() - elif user_shell == 'ipython': - shell = self.make_ipython_shell() + if shell is not None: + break + else: + factory = shells.get(user_shell) - elif user_shell == 'bpython': - shell = self.make_bpython_shell() + if factory is not None: + shell = factory() + else: + raise ValueError( + 'could not find a shell named "%s"' % user_shell + ) if shell is None: shell = self.make_default_shell() @@ -185,7 +212,8 @@ class PShellCommand(object): interact(banner, local=env) return shell - def make_bpython_shell(self, BPShell=None): + @classmethod + def make_bpython_shell(cls, BPShell=None): if BPShell is None: # pragma: no cover try: from bpython import embed @@ -196,15 +224,8 @@ class PShellCommand(object): BPShell(locals_=env, banner=help + '\n') return shell - def make_ipython_shell(self): - shell = self.make_ipython_v1_1_shell() - if shell is None: - shell = self.make_ipython_v0_11_shell() - if shell is None: - shell = self.make_ipython_v0_10_shell() - return shell - - def make_ipython_v1_1_shell(self, IPShellFactory=None): + @classmethod + def make_ipython_shell(cls, IPShellFactory=None): if IPShellFactory is None: # pragma: no cover try: from IPython.terminal.embed import ( @@ -217,31 +238,6 @@ class PShellCommand(object): IPShell() return shell - def make_ipython_v0_11_shell(self, IPShellFactory=None): - if IPShellFactory is None: # pragma: no cover - try: - from IPython.frontend.terminal.embed import ( - InteractiveShellEmbed) - IPShellFactory = InteractiveShellEmbed - except ImportError: - return None - def shell(env, help): - IPShell = IPShellFactory(banner2=help + '\n', user_ns=env) - IPShell() - return shell - - def make_ipython_v0_10_shell(self, IPShellFactory=None): - if IPShellFactory is None: # pragma: no cover - try: - from IPython.Shell import IPShellEmbed - IPShellFactory = IPShellEmbed - except ImportError: - return None - def shell(env, help): - IPShell = IPShellFactory(argv=[], user_ns=env) - IPShell.set_banner(IPShell.IP.BANNER + '\n' + help + '\n') - IPShell() - return shell if __name__ == '__main__': # pragma: no cover sys.exit(main() or 0) diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index 930b9ed64..2788c0b32 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -40,9 +40,6 @@ class DummyIPShell(object): IP = Dummy() IP.BANNER = 'foo' - def set_banner(self, banner): - self.banner = banner - def __call__(self): self.called = True @@ -157,3 +154,23 @@ class DummyBootstrap(object): 'root_factory': self.root_factory, 'closer': self.closer, } + + +class DummyEntryPoint(object): + def __init__(self, name, module): + self.name = name + self.module = module + + def load(self): + return self.module + + +class DummyPkgResources(object): + def __init__(self, entry_point_values): + self.entry_points = [] + + for name, module in entry_point_values.items(): + self.entry_points.append(DummyEntryPoint(name, module)) + + def iter_entry_points(self, name): + return self.entry_points diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py index dab32fecd..985a3010e 100644 --- a/pyramid/tests/test_scripts/test_pshell.py +++ b/pyramid/tests/test_scripts/test_pshell.py @@ -2,6 +2,7 @@ import os import unittest from pyramid.tests.test_scripts import dummy + class TestPShellCommand(unittest.TestCase): def _getTargetClass(self): from pyramid.scripts.pshell import PShellCommand @@ -10,6 +11,7 @@ class TestPShellCommand(unittest.TestCase): def _makeOne(self, patch_bootstrap=True, patch_config=True, patch_args=True, patch_options=True): cmd = self._getTargetClass()([]) + if patch_bootstrap: self.bootstrap = dummy.DummyBootstrap() cmd.bootstrap = (self.bootstrap,) @@ -25,11 +27,15 @@ class TestPShellCommand(unittest.TestCase): self.options.python_shell = '' self.options.setup = None cmd.options = self.options + # default to None to prevent side-effects from running tests in # unknown environments cmd.pystartup = None return cmd + def _makeEntryPoints(self, command, shells): + command.pkg_resources = dummy.DummyPkgResources(shells) + def test_make_default_shell(self): command = self._makeOne() interact = dummy.DummyInteractor() @@ -49,36 +55,23 @@ class TestPShellCommand(unittest.TestCase): def test_make_ipython_v1_1_shell(self): command = self._makeOne() ipshell_factory = dummy.DummyIPShellFactory() - shell = command.make_ipython_v1_1_shell(ipshell_factory) - shell({'foo': 'bar'}, 'a help message') - self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) - self.assertTrue('a help message' in ipshell_factory.kw['banner2']) - self.assertTrue(ipshell_factory.shell.called) - - def test_make_ipython_v0_11_shell(self): - command = self._makeOne() - ipshell_factory = dummy.DummyIPShellFactory() - shell = command.make_ipython_v0_11_shell(ipshell_factory) + shell = command.make_ipython_shell(ipshell_factory) shell({'foo': 'bar'}, 'a help message') self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) self.assertTrue('a help message' in ipshell_factory.kw['banner2']) self.assertTrue(ipshell_factory.shell.called) - def test_make_ipython_v0_10_shell(self): - command = self._makeOne() - ipshell_factory = dummy.DummyIPShellFactory() - shell = command.make_ipython_v0_10_shell(ipshell_factory) - shell({'foo': 'bar'}, 'a help message') - self.assertEqual(ipshell_factory.kw['argv'], []) - self.assertEqual(ipshell_factory.kw['user_ns'], {'foo': 'bar'}) - self.assertTrue('a help message' in ipshell_factory.shell.banner) - self.assertTrue(ipshell_factory.shell.called) - def test_command_loads_default_shell(self): command = self._makeOne() shell = dummy.DummyShell() - command.make_ipython_shell = lambda: None - command.make_bpython_shell = lambda: None + self._makeEntryPoints( + command, + { + 'ipython': lambda: None, + 'bpython': lambda: None, + } + ) + command.make_default_shell = lambda: shell command.run() self.assertTrue(self.config_factory.parser) @@ -96,82 +89,50 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_default_shell_with_unknown_shell(self): command = self._makeOne() + out_calls = [] + + def out(msg): + out_calls.append(msg) + + command.out = out + shell = dummy.DummyShell() bad_shell = dummy.DummyShell() - command.make_ipython_shell = lambda: bad_shell - command.make_bpython_shell = lambda: bad_shell + + self._makeEntryPoints( + command, + { + 'ipython': lambda: bad_shell, + 'bpython': lambda: bad_shell, + } + ) + command.make_default_shell = lambda: shell - command.options.python_shell = 'unknow_python_shell' - command.run() + command.options.python_shell = 'unknown_python_shell' + result = command.run() + self.assertEqual(result, 1) + self.assertEqual( + out_calls, ['could not find a shell named "unknown_python_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':self.bootstrap.root, - 'registry':self.bootstrap.registry, - 'request':self.bootstrap.request, - 'root_factory':self.bootstrap.root_factory, - }) - self.assertEqual(bad_shell.env, {}) self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(shell.help) def test_command_loads_ipython_v1_1(self): command = self._makeOne() shell = dummy.DummyShell() - command.make_ipython_v1_1_shell = lambda: shell - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: None - command.make_bpython_shell = lambda: None - command.make_default_shell = lambda: None - command.options.python_shell = 'ipython' - 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.assertEqual(shell.env, { - 'app':self.bootstrap.app, 'root':self.bootstrap.root, - 'registry':self.bootstrap.registry, - 'request':self.bootstrap.request, - 'root_factory':self.bootstrap.root_factory, - }) - self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(shell.help) + self._makeEntryPoints( + command, + { + 'ipython': lambda: shell, + 'bpython': lambda: bad_shell, + } + ) - def test_command_loads_ipython_v0_11(self): - command = self._makeOne() - shell = dummy.DummyShell() - command.make_ipython_v1_1_shell = lambda: None - command.make_ipython_v0_11_shell = lambda: shell - command.make_ipython_v0_10_shell = lambda: None - command.make_bpython_shell = lambda: None - command.make_default_shell = lambda: None command.options.python_shell = 'ipython' - 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.assertEqual(shell.env, { - 'app':self.bootstrap.app, 'root':self.bootstrap.root, - 'registry':self.bootstrap.registry, - 'request':self.bootstrap.request, - 'root_factory':self.bootstrap.root_factory, - }) - self.assertTrue(self.bootstrap.closer.called) - self.assertTrue(shell.help) - def test_command_loads_ipython_v0_10(self): - command = self._makeOne() - shell = dummy.DummyShell() - command.make_ipython_v1_1_shell = lambda: None - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: shell - command.make_bpython_shell = lambda: None - command.make_default_shell = lambda: None - command.options.python_shell = 'ipython' command.run() self.assertTrue(self.config_factory.parser) self.assertEqual(self.config_factory.parser.filename, @@ -189,8 +150,15 @@ class TestPShellCommand(unittest.TestCase): def test_command_loads_bpython_shell(self): command = self._makeOne() shell = dummy.DummyBPythonShell() - command.make_ipython_shell = lambda: None - command.make_bpython_shell = lambda: shell + + self._makeEntryPoints( + command, + { + 'ipython': lambda: None, + 'bpython': lambda: shell, + } + ) + command.options.python_shell = 'bpython' command.run() self.assertTrue(self.config_factory.parser) @@ -206,37 +174,36 @@ class TestPShellCommand(unittest.TestCase): self.assertTrue(self.bootstrap.closer.called) self.assertTrue(shell.banner) - def test_shell_ipython_ordering(self): + def test_shell_entry_points(self): command = self._makeOne() - shell1_1 = dummy.DummyShell() - shell0_11 = dummy.DummyShell() - shell0_10 = dummy.DummyShell() - command.make_ipython_v1_1_shell = lambda: shell1_1 - shell = command.make_shell() - self.assertEqual(shell, shell1_1) - - command.make_ipython_v1_1_shell = lambda: None - command.make_ipython_v0_11_shell = lambda: shell0_11 - shell = command.make_shell() - self.assertEqual(shell, shell0_11) + dshell = dummy.DummyShell() - command.make_ipython_v0_11_shell = lambda: None - command.make_ipython_v0_10_shell = lambda: shell0_10 - shell = command.make_shell() - self.assertEqual(shell, shell0_10) + self._makeEntryPoints( + command, + { + 'ipython': lambda: dshell, + 'bpython': lambda: dhell, + } + ) - command.options.python_shell = 'ipython' - command.make_ipython_v1_1_shell = lambda: shell1_1 + command.make_default_shell = lambda: None shell = command.make_shell() - self.assertEqual(shell, shell1_1) + self.assertEqual(shell, dshell) def test_shell_ordering(self): command = self._makeOne() ipshell = dummy.DummyShell() bpshell = dummy.DummyShell() dshell = dummy.DummyShell() - command.make_ipython_shell = lambda: None - command.make_bpython_shell = lambda: None + + self._makeEntryPoints( + command, + { + 'ipython': lambda: None, + 'bpython': lambda: None, + } + ) + command.make_default_shell = lambda: dshell shell = command.make_shell() @@ -250,8 +217,15 @@ class TestPShellCommand(unittest.TestCase): shell = command.make_shell() self.assertEqual(shell, dshell) - command.make_ipython_shell = lambda: ipshell - command.make_bpython_shell = lambda: bpshell + self._makeEntryPoints( + command, + { + 'ipython': lambda: ipshell, + 'bpython': lambda: bpshell, + 'python': lambda: dshell, + } + ) + command.options.python_shell = 'ipython' shell = command.make_shell() self.assertEqual(shell, ipshell) @@ -110,6 +110,9 @@ setup(name='pyramid', starter=pyramid.scaffolds:StarterProjectTemplate zodb=pyramid.scaffolds:ZODBProjectTemplate alchemy=pyramid.scaffolds:AlchemyProjectTemplate + [pyramid.pshell] + ipython=pyramid.scripts.pshell:PShellCommand.make_ipython_shell + bpython=pyramid.scripts.pshell:PShellCommand.make_bpython_shell [console_scripts] pcreate = pyramid.scripts.pcreate:main pserve = pyramid.scripts.pserve:main |
