summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2015-10-19 23:44:17 -0500
committerMichael Merickel <michael@merickel.org>2015-10-19 23:44:17 -0500
commit208e7b5e363b07476797d9f754962982c686e907 (patch)
treed0a159c8cdb9f93946f4b16261649cc9a1bd7d42
parentb8735932b92dd255b5dba467d8f52b9014e06544 (diff)
downloadpyramid-208e7b5e363b07476797d9f754962982c686e907.tar.gz
pyramid-208e7b5e363b07476797d9f754962982c686e907.tar.bz2
pyramid-208e7b5e363b07476797d9f754962982c686e907.zip
add pshell --list and default_shell ini options
-rw-r--r--docs/narr/commandline.rst59
-rw-r--r--pyramid/scripts/pshell.py66
-rw-r--r--pyramid/tests/test_scripts/dummy.py3
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py97
-rw-r--r--setup.py1
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
diff --git a/setup.py b/setup.py
index 2a4271138..f3f0524c3 100644
--- a/setup.py
+++ b/setup.py
@@ -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