1
0
mirror of https://github.com/scrapy/scrapy.git synced 2025-02-22 21:03:41 +00:00

Add support for bpython console.

Adds support for configuration of shells from scrapy.cfg
and SCRAPY_PYTHON_SHELL.

config snippet:

cat <<EOF >> ~/.scrapy.cfg
[settings]
# shell can be one of ipython, bpython or python;
# to be tried as the interactive python console
# (in above order, unless set here).
shell = python
EOF

(closes #270, #1100, #1301)
This commit is contained in:
nyov 2015-03-25 10:24:07 +00:00 committed by Luar Roji
parent 280eab2416
commit 509cc8d41e
6 changed files with 162 additions and 31 deletions

View File

@ -16,6 +16,8 @@ accepts a different set of arguments and options.
(The ``scrapy deploy`` command has been removed in 1.0 in favor of the
standalone ``scrapyd-deploy``. See `Deploying your project`_.)
.. _topics-config-settings:
Configuration settings
======================
@ -34,8 +36,9 @@ and project-wide settings will override all others, when defined.
Scrapy also understands, and can be configured through, a number of environment
variables. Currently these are:
* ``SCRAPY_SETTINGS_MODULE`` (See :ref:`topics-settings-module-envvar`)
* ``SCRAPY_SETTINGS_MODULE`` (see :ref:`topics-settings-module-envvar`)
* ``SCRAPY_PROJECT``
* ``SCRAPY_PYTHON_SHELL`` (see :ref:`topics-shell`)
.. _topics-project-structure:

View File

@ -17,6 +17,9 @@ spider, without having to run the spider to test every change.
Once you get familiarized with the Scrapy shell, you'll see that it's an
invaluable tool for developing and debugging your spiders.
Configuring the shell
=====================
If you have `IPython`_ installed, the Scrapy shell will use it (instead of the
standard Python console). The `IPython`_ console is much more powerful and
provides smart auto-completion and colorized output, among other things.
@ -25,8 +28,20 @@ We highly recommend you install `IPython`_, specially if you're working on
Unix systems (where `IPython`_ excels). See the `IPython installation guide`_
for more info.
Scrapy also has support for `bpython`_, and will try to use it where `IPython`_
is unavailable.
Through scrapy's settings you can configure it to use any one of
``ipython``, ``bpython`` or the standard ``python`` shell, regardless of which
are installed. This is done by setting the ``SCRAPY_PYTHON_SHELL`` environment
variable; or by defining it in your :ref:`scrapy.cfg <topics-config-settings>`::
[settings]
shell = bpython
.. _IPython: http://ipython.org/
.. _IPython installation guide: http://ipython.org/install.html
.. _bpython: http://www.bpython-interpreter.org/
Launch the shell
================

View File

@ -5,6 +5,7 @@ See documentation in docs/topics/shell.rst
"""
from __future__ import print_function
import os
import signal
import warnings
@ -21,6 +22,8 @@ from scrapy.spiders import Spider
from scrapy.utils.console import start_python_console
from scrapy.utils.misc import load_object
from scrapy.utils.response import open_in_browser
from scrapy.utils.conf import get_config
from scrapy.utils.console import DEFAULT_PYTHON_SHELLS
class Shell(object):
@ -52,7 +55,28 @@ class Shell(object):
if self.code:
print(eval(self.code, globals(), self.vars))
else:
start_python_console(self.vars)
"""
Detect interactive shell setting in scrapy.cfg
e.g.: ~/.config/scrapy.cfg or ~/.scrapy.cfg
[settings]
# shell can be one of ipython, bpython or python;
# to be used as the interactive python console, if available.
# (default is ipython, fallbacks in the order listed above)
shell = python
"""
cfg = get_config()
section, option = 'settings', 'shell'
env = os.environ.get('SCRAPY_PYTHON_SHELL')
shells = []
if env:
shells += env.strip().lower().split(',')
elif cfg.has_option(section, option):
shells += [cfg.get(section, option).strip().lower()]
else: # try all by default
shells += DEFAULT_PYTHON_SHELLS.keys()
# always add standard shell as fallback
shells += ['python']
start_python_console(self.vars, shells=shells)
def _schedule(self, request, spider):
spider = self._open_spider(request, spider)

View File

@ -1,37 +1,79 @@
from functools import wraps
from collections import OrderedDict
def start_python_console(namespace=None, noipython=False, banner=''):
"""Start Python console binded to the given namespace. If IPython is
available, an IPython console will be started instead, unless `noipython`
is True. Also, tab completion will be used on Unix systems.
def _embed_ipython_shell(namespace={}, banner=''):
"""Start an IPython Shell"""
try:
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.terminal.ipapp import load_default_config
except ImportError:
from IPython.frontend.terminal.embed import InteractiveShellEmbed
from IPython.frontend.terminal.ipapp import load_default_config
@wraps(_embed_ipython_shell)
def wrapper(namespace=namespace, banner=''):
config = load_default_config()
shell = InteractiveShellEmbed(
banner1=banner, user_ns=namespace, config=config)
shell()
return wrapper
def _embed_bpython_shell(namespace={}, banner=''):
"""Start a bpython shell"""
import bpython
@wraps(_embed_bpython_shell)
def wrapper(namespace=namespace, banner=''):
bpython.embed(locals_=namespace, banner=banner)
return wrapper
def _embed_standard_shell(namespace={}, banner=''):
"""Start a standard python shell"""
import code
try: # readline module is only available on unix systems
import readline
except ImportError:
pass
else:
import rlcompleter
readline.parse_and_bind("tab:complete")
@wraps(_embed_standard_shell)
def wrapper(namespace=namespace, banner=''):
code.interact(banner=banner, local=namespace)
return wrapper
DEFAULT_PYTHON_SHELLS = OrderedDict([
('ipython', _embed_ipython_shell),
('bpython', _embed_bpython_shell),
( 'python', _embed_standard_shell),
])
def get_shell_embed_func(shells=None, known_shells=None):
"""Return the first acceptable shell-embed function
from a given list of shell names.
"""
if shells is None: # list, preference order of shells
shells = DEFAULT_PYTHON_SHELLS.keys()
if known_shells is None: # available embeddable shells
known_shells = DEFAULT_PYTHON_SHELLS.copy()
for shell in shells:
if shell in known_shells:
try:
# function test: run all setup code (imports),
# but dont fall into the shell
return known_shells[shell]()
except ImportError:
continue
def start_python_console(namespace=None, banner='', shells=None):
"""Start Python console bound to the given namespace.
Readline support and tab completion will be used on Unix, if available.
"""
if namespace is None:
namespace = {}
try:
try: # use IPython if available
if noipython:
raise ImportError()
try:
from IPython.terminal.embed import InteractiveShellEmbed
from IPython.terminal.ipapp import load_default_config
except ImportError:
from IPython.frontend.terminal.embed import InteractiveShellEmbed
from IPython.frontend.terminal.ipapp import load_default_config
config = load_default_config()
shell = InteractiveShellEmbed(
banner1=banner, user_ns=namespace, config=config)
shell()
except ImportError:
import code
try: # readline module is only available on unix systems
import readline
except ImportError:
pass
else:
import rlcompleter
readline.parse_and_bind("tab:complete")
code.interact(banner=banner, local=namespace)
shell = get_shell_embed_func(shells)
if shell is not None:
shell(namespace=namespace, banner=banner)
except SystemExit: # raised when using exit() in python code.interact
pass

View File

@ -6,3 +6,6 @@ pytest-twisted
pytest-cov
jmespath
testfixtures
# optional for shell wrapper tests
bpython
ipython

View File

@ -0,0 +1,44 @@
import unittest
from scrapy.utils.console import get_shell_embed_func
try:
import bpython
bpy = True
del bpython
except ImportError:
bpy = False
try:
import IPython
ipy = True
del IPython
except ImportError:
ipy = False
class UtilsConsoleTestCase(unittest.TestCase):
def test_get_shell_embed_func(self):
shell = get_shell_embed_func(['invalid'])
self.assertEqual(shell, None)
shell = get_shell_embed_func(['invalid','python'])
self.assertTrue(callable(shell))
self.assertEqual(shell.__name__, '_embed_standard_shell')
@unittest.skipIf(not bpy, 'bpython not available in testenv')
def test_get_shell_embed_func2(self):
shell = get_shell_embed_func(['bpython'])
self.assertTrue(callable(shell))
self.assertEqual(shell.__name__, '_embed_bpython_shell')
@unittest.skipIf(not ipy, 'IPython not available in testenv')
def test_get_shell_embed_func3(self):
# default shell should be 'ipython'
shell = get_shell_embed_func()
self.assertEqual(shell.__name__, '_embed_ipython_shell')
if __name__ == "__main__":
unittest.main()