summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt21
-rw-r--r--docs/narr/commandline.rst72
-rw-r--r--pyramid/scripts/pshell.py116
-rw-r--r--pyramid/tests/test_scripts/dummy.py20
-rw-r--r--pyramid/tests/test_scripts/test_pshell.py202
-rw-r--r--setup.py5
6 files changed, 242 insertions, 194 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 4396c4356..8b63cf847 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,19 @@
1.6 (2015-04-14)
================
+Backward Incompatibilities
+--------------------------
+
+- IPython and BPython support have been removed from pshell in the core.
+ To continue using them on Pyramid 1.6+ you must install the binding
+ packages explicitly::
+
+ $ pip install pyramid_ipython
+
+ or
+
+ $ pip install pyramid_bpython
+
Features
--------
@@ -135,7 +148,8 @@ Features
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
+ https://github.com/Pylons/pyramid/pull/1891 and
+ https://github.com/Pylons/pyramid/pull/2012
- The variables injected into ``pshell`` are now displayed with their
docstrings instead of the default ``str(obj)`` when possible.
@@ -213,6 +227,11 @@ Bug Fixes
WebOb 1.5.
See https://github.com/Pylons/pyramid/pull/1865
+- ``pshell`` will now preserve the capitalization of variables in the
+ ``[pshell]`` section of the INI file. This makes exposing classes to the
+ shell a little more straightfoward.
+ See https://github.com/Pylons/pyramid/pull/1883
+
Deprecations
------------
diff --git a/docs/narr/commandline.rst b/docs/narr/commandline.rst
index 9db92b669..430641a50 100644
--- a/docs/narr/commandline.rst
+++ b/docs/narr/commandline.rst
@@ -107,9 +107,7 @@ found* message.
.. index::
single: interactive shell
- single: IPython
single: pshell
- single: bpython
.. _interactive_shell:
@@ -263,38 +261,40 @@ request is configured to generate urls from the host
>>> request.route_url('home')
'https://www.example.com/'
-.. index::
- single: IPython
- single: bpython
+Alternative Shells
+~~~~~~~~~~~~~~~~~~
-.. _ipython_or_bpython:
+The ``pshell`` command can be easily extended with alternate REPLs if the
+default python REPL is not satisfactory. Assuming you have a binding
+installed such as ``pyramid_ipython`` it will normally be auto-selected and
+used. You may also specifically invoke your choice with the ``-p choice`` or
+``--python-shell choice`` option.
-IPython or bpython
-~~~~~~~~~~~~~~~~~~
+.. code-block:: text
+
+ $ $VENV/bin/pshell -p ipython development.ini#MyProject
-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.
+You may use the ``--list-shells`` option to see the available shells.
.. code-block:: text
- $ $VENV/bin/pshell -p ipython | bpython | python development.ini#MyProject
+ $ $VENV/bin/pshell --list-shells
+ Available shells:
+ bpython
+ 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_runner': [
+ 'myshell=my_app:ptpython_shell_factory',
+ ],
+ },
)
And then your shell factory should return a function that accepts two
@@ -302,16 +302,30 @@ arguments, ``env`` and ``help``, which 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)
+ from ptpython.repl import embed
- def shell(env, help):
- PTPShell(banner=help, locals=env)
+ def ptpython_shell_runner(env, help):
+ print(help)
+ return embed(locals=env)
- return shell
+.. versionchanged:: 1.6
+ User-defined shells may be registered using entry points. Prior to this
+ the only supported shells were ``ipython``, ``bpython`` and ``python``.
+
+ ``ipython`` and ``bpython`` have been moved into their respective
+ packages ``pyramid_ipython`` and ``pyramid_bpython``.
+
+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
diff --git a/pyramid/scripts/pshell.py b/pyramid/scripts/pshell.py
index 5913220fc..0a7cfbbe5 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):
@@ -19,6 +21,13 @@ def main(argv=sys.argv, quiet=False):
return command.run()
+def python_shell_runner(env, help, interact=interact):
+ cprt = 'Type "help" for more information.'
+ banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt)
+ banner += '\n\n' + help + '\n'
+ interact(banner, local=env)
+
+
class PShellCommand(object):
usage = '%prog config_uri'
description = """\
@@ -43,7 +52,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 "
@@ -52,9 +68,11 @@ class PShellCommand(object):
"[pshell] ini section."))
ConfigParser = configparser.ConfigParser # testing
+ default_runner = python_shell_runner # testing
loaded_objects = {}
object_help = {}
+ preferred_shells = []
setup = None
pystartup = os.environ.get('PYTHONSTARTUP')
@@ -64,6 +82,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 +96,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 +107,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,71 +192,64 @@ class PShellCommand(object):
finally:
self.closer()
- def make_shell(self):
- shells = {}
+ def show_shells(self):
+ shells = self.find_all_shells()
+ sorted_names = sorted(shells.keys(), key=lambda x: x.lower())
- for ep in self.pkg_resources.iter_entry_points('pyramid.pshell'):
+ self.out('Available shells:')
+ for name in sorted_names:
+ self.out(' %s' % (name,))
+ return 0
+
+ def find_all_shells(self):
+ pkg_resources = self.pkg_resources
+
+ shells = {}
+ for ep in pkg_resources.iter_entry_points('pyramid.pshell_runner'):
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])
- for name, factory in sorted_shells:
- shell = factory()
+ 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)
+
+ if len(sorted_shells) > 0:
+ shell = sorted_shells[0][1]
- if shell is not None:
- break
else:
- factory = shells.get(user_shell)
+ runner = shells.get(user_shell)
- if factory is not None:
- shell = factory()
- else:
+ if runner is not None:
+ shell = runner
+
+ if shell is None:
raise ValueError(
'could not find a shell named "%s"' % user_shell
)
if shell is None:
- shell = self.make_default_shell()
-
- return shell
-
- def make_default_shell(self, interact=interact):
- def shell(env, help):
- cprt = 'Type "help" for more information.'
- banner = "Python %s on %s\n%s" % (sys.version, sys.platform, cprt)
- banner += '\n\n' + help + '\n'
- interact(banner, local=env)
- return shell
+ # should never happen, but just incase entry points are borked
+ shell = self.default_runner
- @classmethod
- def make_bpython_shell(cls, BPShell=None):
- if BPShell is None: # pragma: no cover
- try:
- from bpython import embed
- BPShell = embed
- except ImportError:
- return None
- def shell(env, help):
- BPShell(locals_=env, banner=help + '\n')
- return shell
-
- @classmethod
- def make_ipython_shell(cls, IPShellFactory=None):
- if IPShellFactory is None: # pragma: no cover
- try:
- from IPython.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
diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py
index 2788c0b32..a872e197c 100644
--- a/pyramid/tests/test_scripts/dummy.py
+++ b/pyramid/tests/test_scripts/dummy.py
@@ -21,34 +21,18 @@ 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):
self.banner = banner
self.local = local
-class DummyBPythonShell:
- def __call__(self, locals_, banner):
- self.locals_ = locals_
- self.banner = banner
-
-class DummyIPShell(object):
- IP = Dummy()
- IP.BANNER = 'foo'
-
- def __call__(self):
- self.called = True
-
-class DummyIPShellFactory(object):
- def __call__(self, **kw):
- self.kw = kw
- self.shell = DummyIPShell()
- return self.shell
-
class DummyApp:
def __init__(self):
self.registry = dummy_registry
diff --git a/pyramid/tests/test_scripts/test_pshell.py b/pyramid/tests/test_scripts/test_pshell.py
index 034f2109d..f98ded6d3 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
@@ -36,43 +37,12 @@ class TestPShellCommand(unittest.TestCase):
def _makeEntryPoints(self, command, shells):
command.pkg_resources = dummy.DummyPkgResources(shells)
- def test_make_default_shell(self):
- command = self._makeOne()
- interact = dummy.DummyInteractor()
- shell = command.make_default_shell(interact)
- shell({'foo': 'bar'}, 'a help message')
- self.assertEqual(interact.local, {'foo': 'bar'})
- self.assertTrue('a help message' in interact.banner)
-
- def test_make_bpython_shell(self):
- command = self._makeOne()
- bpython = dummy.DummyBPythonShell()
- shell = command.make_bpython_shell(bpython)
- shell({'foo': 'bar'}, 'a help message')
- self.assertEqual(bpython.locals_, {'foo': 'bar'})
- self.assertTrue('a help message' in bpython.banner)
-
- def test_make_ipython_v1_1_shell(self):
- command = self._makeOne()
- ipshell_factory = dummy.DummyIPShellFactory()
- 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_command_loads_default_shell(self):
command = self._makeOne()
shell = dummy.DummyShell()
- self._makeEntryPoints(
- command,
- {
- 'ipython': lambda: None,
- 'bpython': lambda: None,
- }
- )
+ self._makeEntryPoints(command, {})
- command.make_default_shell = lambda: shell
+ command.default_runner = shell
command.run()
self.assertTrue(self.config_factory.parser)
self.assertEqual(self.config_factory.parser.filename,
@@ -87,7 +57,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 = []
@@ -97,17 +67,10 @@ class TestPShellCommand(unittest.TestCase):
command.out = out
shell = dummy.DummyShell()
- bad_shell = dummy.DummyShell()
- self._makeEntryPoints(
- command,
- {
- 'ipython': lambda: bad_shell,
- 'bpython': lambda: bad_shell,
- }
- )
+ self._makeEntryPoints(command, {})
- command.make_default_shell = lambda: shell
+ command.default_runner = shell
command.options.python_shell = 'unknown_python_shell'
result = command.run()
self.assertEqual(result, 1)
@@ -120,14 +83,15 @@ 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,
{
- 'ipython': lambda: shell,
- 'bpython': lambda: bad_shell,
+ 'ipython': shell,
+ 'bpython': bad_shell,
}
)
@@ -147,33 +111,6 @@ class TestPShellCommand(unittest.TestCase):
self.assertTrue(self.bootstrap.closer.called)
self.assertTrue(shell.help)
- def test_command_loads_bpython_shell(self):
- command = self._makeOne()
- shell = dummy.DummyBPythonShell()
-
- self._makeEntryPoints(
- command,
- {
- 'ipython': lambda: None,
- 'bpython': lambda: shell,
- }
- )
-
- command.options.python_shell = 'bpython'
- 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.locals_, {
- '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.banner)
-
def test_shell_entry_points(self):
command = self._makeOne()
dshell = dummy.DummyShell()
@@ -181,67 +118,86 @@ class TestPShellCommand(unittest.TestCase):
self._makeEntryPoints(
command,
{
- 'ipython': lambda: dshell,
- 'bpython': lambda: dshell,
+ 'ipython': dshell,
+ 'bpython': dshell,
}
)
- command.make_default_shell = lambda: None
+ command.default_runner = None
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()
dshell = dummy.DummyShell()
+ self._makeEntryPoints(command, {})
+
+ command.default_runner = dshell
+
+ shell = command.make_shell()
+ self.assertEqual(shell, dshell)
+
+ command.options.python_shell = 'ipython'
+ self.assertRaises(ValueError, command.make_shell)
+
self._makeEntryPoints(
command,
{
- 'ipython': lambda: None,
- 'bpython': lambda: None,
+ 'ipython': ipshell,
+ 'bpython': bpshell,
+ 'python': dshell,
}
)
- command.make_default_shell = lambda: dshell
-
- shell = command.make_shell()
- self.assertEqual(shell, dshell)
-
command.options.python_shell = 'ipython'
shell = command.make_shell()
- self.assertEqual(shell, dshell)
+ self.assertEqual(shell, ipshell)
command.options.python_shell = 'bpython'
shell = command.make_shell()
+ self.assertEqual(shell, bpshell)
+
+ command.options.python_shell = 'python'
+ 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,
+ 'ipython': ipshell,
+ 'bpython': bpshell,
+ 'python': dshell,
}
)
- command.options.python_shell = 'ipython'
+ command.default_runner = dshell
+
+ command.preferred_shells = ['ipython', 'bpython']
shell = command.make_shell()
self.assertEqual(shell, ipshell)
- command.options.python_shell = 'bpython'
+ command.preferred_shells = ['bpython', 'python']
shell = command.make_shell()
self.assertEqual(shell, bpshell)
- command.options.python_shell = 'python'
+ 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()
- self.config_factory.items = [('m', model)]
+ user = dummy.Dummy()
+ self.config_factory.items = [('m', model), ('User', user)]
shell = dummy.DummyShell()
command.run(shell)
self.assertTrue(self.config_factory.parser)
@@ -254,6 +210,7 @@ class TestPShellCommand(unittest.TestCase):
'request':self.bootstrap.request,
'root_factory':self.bootstrap.root_factory,
'm':model,
+ 'User': user,
})
self.assertTrue(self.bootstrap.closer.called)
self.assertTrue(shell.help)
@@ -282,6 +239,26 @@ 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': ipshell,
+ 'python': 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 +346,46 @@ 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': dshell,
+ 'python': dshell,
+ }
+ )
+
+ command.options.list = True
+ result = command.run()
+ self.assertEqual(result, 0)
+ self.assertEqual(out_calls, [
+ 'Available shells:',
+ ' ipython',
+ ' python',
+ ])
+
+
+class Test_python_shell_runner(unittest.TestCase):
+ def _callFUT(self, env, help, interact):
+ from pyramid.scripts.pshell import python_shell_runner
+ return python_shell_runner(env, help, interact=interact)
+
+ def test_it(self):
+ interact = dummy.DummyInteractor()
+ self._callFUT({'foo': 'bar'}, 'a help message', interact)
+ self.assertEqual(interact.local, {'foo': 'bar'})
+ self.assertTrue('a help message' in interact.banner)
+
class Test_main(unittest.TestCase):
def _callFUT(self, argv):
from pyramid.scripts.pshell import main
@@ -377,4 +394,3 @@ class Test_main(unittest.TestCase):
def test_it(self):
result = self._callFUT(['pshell'])
self.assertEqual(result, 2)
-
diff --git a/setup.py b/setup.py
index 2a4271138..c81956e7f 100644
--- a/setup.py
+++ b/setup.py
@@ -111,9 +111,8 @@ 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
+ [pyramid.pshell_runner]
+ python=pyramid.scripts.pshell:python_shell_runner
[console_scripts]
pcreate = pyramid.scripts.pcreate:main
pserve = pyramid.scripts.pserve:main