summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2015-10-11 12:36:25 -0500
committerMichael Merickel <michael@merickel.org>2015-10-11 12:36:25 -0500
commit803f94d51f5d630a99b9aa2c47f7de40a5d75ea1 (patch)
tree5354cf61b243049703d211854fc9f19d48696ef0
parent9b674ffd9a014c0eb2b6cd620164f85ea855e229 (diff)
parentb932a45f834ae6bdb5850a2c58d1317844b32914 (diff)
downloadpyramid-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.txt3
-rw-r--r--docs/narr/commandline.rst38
-rw-r--r--pyramid/scripts/pshell.py88
-rw-r--r--pyramid/tests/test_scripts/dummy.py23
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py190
-rw-r--r--setup.py3
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)
diff --git a/setup.py b/setup.py
index ffe70cd00..bb71880b5 100644
--- a/setup.py
+++ b/setup.py
@@ -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