##// END OF EJS Templates
move _encode_binary to jsonutil.encode_images...
MinRK -
Show More
@@ -1,166 +1,185 b''
1 1 """Utilities to manipulate JSON objects.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import re
15 15 import sys
16 16 import types
17 from base64 import encodestring
17 18 from datetime import datetime
18 19
19 20 from IPython.utils import py3compat
20 21 from IPython.utils.encoding import DEFAULT_ENCODING
21 22 from IPython.utils import text
22 23 next_attr_name = '__next__' if py3compat.PY3 else 'next'
23 24
24 25 #-----------------------------------------------------------------------------
25 26 # Globals and constants
26 27 #-----------------------------------------------------------------------------
27 28
28 29 # timestamp formats
29 30 ISO8601="%Y-%m-%dT%H:%M:%S.%f"
30 31 ISO8601_PAT=re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+$")
31 32
32 33 #-----------------------------------------------------------------------------
33 34 # Classes and functions
34 35 #-----------------------------------------------------------------------------
35 36
36 37 def rekey(dikt):
37 38 """Rekey a dict that has been forced to use str keys where there should be
38 39 ints by json."""
39 40 for k in dikt.iterkeys():
40 41 if isinstance(k, basestring):
41 42 ik=fk=None
42 43 try:
43 44 ik = int(k)
44 45 except ValueError:
45 46 try:
46 47 fk = float(k)
47 48 except ValueError:
48 49 continue
49 50 if ik is not None:
50 51 nk = ik
51 52 else:
52 53 nk = fk
53 54 if nk in dikt:
54 55 raise KeyError("already have key %r"%nk)
55 56 dikt[nk] = dikt.pop(k)
56 57 return dikt
57 58
58 59
59 60 def extract_dates(obj):
60 61 """extract ISO8601 dates from unpacked JSON"""
61 62 if isinstance(obj, dict):
62 63 obj = dict(obj) # don't clobber
63 64 for k,v in obj.iteritems():
64 65 obj[k] = extract_dates(v)
65 66 elif isinstance(obj, (list, tuple)):
66 67 obj = [ extract_dates(o) for o in obj ]
67 68 elif isinstance(obj, basestring):
68 69 if ISO8601_PAT.match(obj):
69 70 obj = datetime.strptime(obj, ISO8601)
70 71 return obj
71 72
72 73 def squash_dates(obj):
73 74 """squash datetime objects into ISO8601 strings"""
74 75 if isinstance(obj, dict):
75 76 obj = dict(obj) # don't clobber
76 77 for k,v in obj.iteritems():
77 78 obj[k] = squash_dates(v)
78 79 elif isinstance(obj, (list, tuple)):
79 80 obj = [ squash_dates(o) for o in obj ]
80 81 elif isinstance(obj, datetime):
81 82 obj = obj.strftime(ISO8601)
82 83 return obj
83 84
84 85 def date_default(obj):
85 86 """default function for packing datetime objects in JSON."""
86 87 if isinstance(obj, datetime):
87 88 return obj.strftime(ISO8601)
88 89 else:
89 90 raise TypeError("%r is not JSON serializable"%obj)
90 91
91 92
93 # constants for identifying png/jpeg data
94 PNG = b'\x89PNG\r\n\x1a\n'
95 JPEG = b'\xff\xd8'
96
97 def encode_images(format_dict):
98 """b64-encodes images in a displaypub format dict
99
100 Perhaps this should be handled in json_clean itself?
101 """
102 encoded = format_dict.copy()
103 pngdata = format_dict.get('image/png')
104 if isinstance(pngdata, bytes) and pngdata[:8] == PNG:
105 encoded['image/png'] = encodestring(pngdata).decode('ascii')
106 jpegdata = format_dict.get('image/jpeg')
107 if isinstance(jpegdata, bytes) and jpegdata[:2] == JPEG:
108 encoded['image/jpeg'] = encodestring(jpegdata).decode('ascii')
109 return encoded
110
92 111
93 112 def json_clean(obj):
94 113 """Clean an object to ensure it's safe to encode in JSON.
95 114
96 115 Atomic, immutable objects are returned unmodified. Sets and tuples are
97 116 converted to lists, lists are copied and dicts are also copied.
98 117
99 118 Note: dicts whose keys could cause collisions upon encoding (such as a dict
100 119 with both the number 1 and the string '1' as keys) will cause a ValueError
101 120 to be raised.
102 121
103 122 Parameters
104 123 ----------
105 124 obj : any python object
106 125
107 126 Returns
108 127 -------
109 128 out : object
110 129
111 130 A version of the input which will not cause an encoding error when
112 131 encoded as JSON. Note that this function does not *encode* its inputs,
113 132 it simply sanitizes it so that there will be no encoding errors later.
114 133
115 134 Examples
116 135 --------
117 136 >>> json_clean(4)
118 137 4
119 138 >>> json_clean(range(10))
120 139 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
121 140 >>> sorted(json_clean(dict(x=1, y=2)).items())
122 141 [('x', 1), ('y', 2)]
123 142 >>> sorted(json_clean(dict(x=1, y=2, z=[1,2,3])).items())
124 143 [('x', 1), ('y', 2), ('z', [1, 2, 3])]
125 144 >>> json_clean(True)
126 145 True
127 146 """
128 147 # types that are 'atomic' and ok in json as-is. bool doesn't need to be
129 148 # listed explicitly because bools pass as int instances
130 149 atomic_ok = (unicode, int, float, types.NoneType)
131 150
132 151 # containers that we need to convert into lists
133 152 container_to_list = (tuple, set, types.GeneratorType)
134 153
135 154 if isinstance(obj, atomic_ok):
136 155 return obj
137 156
138 157 if isinstance(obj, bytes):
139 158 return obj.decode(DEFAULT_ENCODING, 'replace')
140 159
141 160 if isinstance(obj, container_to_list) or (
142 161 hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)):
143 162 obj = list(obj)
144 163
145 164 if isinstance(obj, list):
146 165 return [json_clean(x) for x in obj]
147 166
148 167 if isinstance(obj, dict):
149 168 # First, validate that the dict won't lose data in conversion due to
150 169 # key collisions after stringification. This can happen with keys like
151 170 # True and 'true' or 1 and '1', which collide in JSON.
152 171 nkeys = len(obj)
153 172 nkeys_collapsed = len(set(map(str, obj)))
154 173 if nkeys != nkeys_collapsed:
155 174 raise ValueError('dict can not be safely converted to JSON: '
156 175 'key collision would lead to dropped values')
157 176 # If all OK, proceed by making the new dict that will be json-safe
158 177 out = {}
159 178 for k,v in obj.iteritems():
160 179 out[str(k)] = json_clean(v)
161 180 return out
162 181
163 182 # If we get here, we don't know how to handle the object, so we just get
164 183 # its repr and return that. This will catch lambdas, open sockets, class
165 184 # objects, and any other complicated contraption that json can't encode
166 185 return repr(obj)
@@ -1,72 +1,104 b''
1 1 """Test suite for our JSON utilities.
2 2 """
3 3 #-----------------------------------------------------------------------------
4 4 # Copyright (C) 2010-2011 The IPython Development Team
5 5 #
6 6 # Distributed under the terms of the BSD License. The full license is in
7 7 # the file COPYING.txt, distributed as part of this software.
8 8 #-----------------------------------------------------------------------------
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13 # stdlib
14 14 import json
15 from base64 import decodestring
15 16
16 17 # third party
17 18 import nose.tools as nt
18 19
19 20 # our own
20 from ..jsonutil import json_clean
21 from IPython.testing import decorators as dec
22 from ..jsonutil import json_clean, encode_images
23 from ..py3compat import unicode_to_str, str_to_bytes
21 24
22 25 #-----------------------------------------------------------------------------
23 26 # Test functions
24 27 #-----------------------------------------------------------------------------
25 28
26 29 def test():
27 30 # list of input/expected output. Use None for the expected output if it
28 31 # can be the same as the input.
29 32 pairs = [(1, None), # start with scalars
30 33 (1.0, None),
31 34 ('a', None),
32 35 (True, None),
33 36 (False, None),
34 37 (None, None),
35 38 # complex numbers for now just go to strings, as otherwise they
36 39 # are unserializable
37 40 (1j, '1j'),
38 41 # Containers
39 42 ([1, 2], None),
40 43 ((1, 2), [1, 2]),
41 44 (set([1, 2]), [1, 2]),
42 45 (dict(x=1), None),
43 46 ({'x': 1, 'y':[1,2,3], '1':'int'}, None),
44 47 # More exotic objects
45 48 ((x for x in range(3)), [0, 1, 2]),
46 49 (iter([1, 2]), [1, 2]),
47 50 ]
48 51
49 52 for val, jval in pairs:
50 53 if jval is None:
51 54 jval = val
52 55 out = json_clean(val)
53 56 # validate our cleanup
54 57 nt.assert_equal(out, jval)
55 58 # and ensure that what we return, indeed encodes cleanly
56 59 json.loads(json.dumps(out))
57 60
58 61
62
63 @dec.parametric
64 def test_encode_images():
65 # invalid data, but the header and footer are from real files
66 pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82'
67 jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9'
68
69 fmt = {
70 'image/png' : pngdata,
71 'image/jpeg' : jpegdata,
72 }
73 encoded = encode_images(fmt)
74 for key, value in fmt.iteritems():
75 # encoded has unicode, want bytes
76 decoded = decodestring(encoded[key].encode('ascii'))
77 yield nt.assert_equal(decoded, value)
78 encoded2 = encode_images(encoded)
79 yield nt.assert_equal(encoded, encoded2)
80
81 b64_str = {}
82 for key, encoded in encoded.iteritems():
83 b64_str[key] = unicode_to_str(encoded)
84 encoded3 = encode_images(b64_str)
85 yield nt.assert_equal(encoded3, b64_str)
86 for key, value in fmt.iteritems():
87 # encoded3 has str, want bytes
88 decoded = decodestring(str_to_bytes(encoded3[key]))
89 yield nt.assert_equal(decoded, value)
90
59 91 def test_lambda():
60 92 jc = json_clean(lambda : 1)
61 93 assert isinstance(jc, str)
62 94 assert '<lambda>' in jc
63 95 json.dumps(jc)
64 96
65 97
66 98 def test_exception():
67 99 bad_dicts = [{1:'number', '1':'string'},
68 100 {True:'bool', 'True':'string'},
69 101 ]
70 102 for d in bad_dicts:
71 103 nt.assert_raises(ValueError, json_clean, d)
72 104
@@ -1,75 +1,63 b''
1 1 import __builtin__
2 2 import sys
3 from base64 import encodestring
4 3
5 4 from IPython.core.displayhook import DisplayHook
5 from IPython.utils.jsonutil import encode_images
6 6 from IPython.utils.traitlets import Instance, Dict
7 7 from session import extract_header, Session
8 8
9 9 class ZMQDisplayHook(object):
10 10 """A simple displayhook that publishes the object's repr over a ZeroMQ
11 11 socket."""
12 12 topic=None
13 13
14 14 def __init__(self, session, pub_socket):
15 15 self.session = session
16 16 self.pub_socket = pub_socket
17 17 self.parent_header = {}
18 18
19 19 def __call__(self, obj):
20 20 if obj is None:
21 21 return
22 22
23 23 __builtin__._ = obj
24 24 sys.stdout.flush()
25 25 sys.stderr.flush()
26 26 msg = self.session.send(self.pub_socket, u'pyout', {u'data':repr(obj)},
27 27 parent=self.parent_header, ident=self.topic)
28 28
29 29 def set_parent(self, parent):
30 30 self.parent_header = extract_header(parent)
31 31
32 32
33 def _encode_binary(format_dict):
34 encoded = format_dict.copy()
35 pngdata = format_dict.get('image/png')
36 if isinstance(pngdata, bytes):
37 encoded['image/png'] = encodestring(pngdata).decode('ascii')
38 jpegdata = format_dict.get('image/jpeg')
39 if isinstance(jpegdata, bytes):
40 encoded['image/jpeg'] = encodestring(jpegdata).decode('ascii')
41
42 return encoded
43
44
45 33 class ZMQShellDisplayHook(DisplayHook):
46 34 """A displayhook subclass that publishes data using ZeroMQ. This is intended
47 35 to work with an InteractiveShell instance. It sends a dict of different
48 36 representations of the object."""
49 37 topic=None
50 38
51 39 session = Instance(Session)
52 40 pub_socket = Instance('zmq.Socket')
53 41 parent_header = Dict({})
54 42
55 43 def set_parent(self, parent):
56 44 """Set the parent for outbound messages."""
57 45 self.parent_header = extract_header(parent)
58 46
59 47 def start_displayhook(self):
60 48 self.msg = self.session.msg(u'pyout', {}, parent=self.parent_header)
61 49
62 50 def write_output_prompt(self):
63 51 """Write the output prompt."""
64 52 self.msg['content']['execution_count'] = self.prompt_count
65 53
66 54 def write_format_data(self, format_dict):
67 self.msg['content']['data'] = _encode_binary(format_dict)
55 self.msg['content']['data'] = encode_images(format_dict)
68 56
69 57 def finish_displayhook(self):
70 58 """Finish up all displayhook activities."""
71 59 sys.stdout.flush()
72 60 sys.stderr.flush()
73 61 self.session.send(self.pub_socket, self.msg, ident=self.topic)
74 62 self.msg = None
75 63
@@ -1,581 +1,581 b''
1 1 """A ZMQ-based subclass of InteractiveShell.
2 2
3 3 This code is meant to ease the refactoring of the base InteractiveShell into
4 4 something with a cleaner architecture for 2-process use, without actually
5 5 breaking InteractiveShell itself. So we're doing something a bit ugly, where
6 6 we subclass and override what we want to fix. Once this is working well, we
7 7 can go back to the base class and refactor the code for a cleaner inheritance
8 8 implementation that doesn't rely on so much monkeypatching.
9 9
10 10 But this lets us maintain a fully working IPython as we develop the new
11 11 machinery. This should thus be thought of as scaffolding.
12 12 """
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16 from __future__ import print_function
17 17
18 18 # Stdlib
19 19 import os
20 20 import sys
21 21 import time
22 22
23 23 # System library imports
24 24 from zmq.eventloop import ioloop
25 25
26 26 # Our own
27 27 from IPython.core.interactiveshell import (
28 28 InteractiveShell, InteractiveShellABC
29 29 )
30 30 from IPython.core import page
31 31 from IPython.core.autocall import ZMQExitAutocall
32 32 from IPython.core.displaypub import DisplayPublisher
33 33 from IPython.core.magics import MacroToEdit, CodeMagics
34 34 from IPython.core.magic import magics_class, line_magic, Magics
35 35 from IPython.core.payloadpage import install_payload_page
36 36 from IPython.lib.kernel import (
37 37 get_connection_file, get_connection_info, connect_qtconsole
38 38 )
39 39 from IPython.testing.skipdoctest import skip_doctest
40 40 from IPython.utils import io
41 from IPython.utils.jsonutil import json_clean
41 from IPython.utils.jsonutil import json_clean, encode_images
42 42 from IPython.utils.process import arg_split
43 43 from IPython.utils import py3compat
44 44 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
45 45 from IPython.utils.warn import warn, error
46 from IPython.zmq.displayhook import ZMQShellDisplayHook, _encode_binary
46 from IPython.zmq.displayhook import ZMQShellDisplayHook
47 47 from IPython.zmq.session import extract_header
48 48 from session import Session
49 49
50 50 #-----------------------------------------------------------------------------
51 51 # Functions and classes
52 52 #-----------------------------------------------------------------------------
53 53
54 54 class ZMQDisplayPublisher(DisplayPublisher):
55 55 """A display publisher that publishes data using a ZeroMQ PUB socket."""
56 56
57 57 session = Instance(Session)
58 58 pub_socket = Instance('zmq.Socket')
59 59 parent_header = Dict({})
60 60 topic = CBytes(b'displaypub')
61 61
62 62 def set_parent(self, parent):
63 63 """Set the parent for outbound messages."""
64 64 self.parent_header = extract_header(parent)
65 65
66 66 def _flush_streams(self):
67 67 """flush IO Streams prior to display"""
68 68 sys.stdout.flush()
69 69 sys.stderr.flush()
70 70
71 71 def publish(self, source, data, metadata=None):
72 72 self._flush_streams()
73 73 if metadata is None:
74 74 metadata = {}
75 75 self._validate_data(source, data, metadata)
76 76 content = {}
77 77 content['source'] = source
78 content['data'] = _encode_binary(data)
78 content['data'] = encode_images(data)
79 79 content['metadata'] = metadata
80 80 self.session.send(
81 81 self.pub_socket, u'display_data', json_clean(content),
82 82 parent=self.parent_header, ident=self.topic,
83 83 )
84 84
85 85 def clear_output(self, stdout=True, stderr=True, other=True):
86 86 content = dict(stdout=stdout, stderr=stderr, other=other)
87 87
88 88 if stdout:
89 89 print('\r', file=sys.stdout, end='')
90 90 if stderr:
91 91 print('\r', file=sys.stderr, end='')
92 92
93 93 self._flush_streams()
94 94
95 95 self.session.send(
96 96 self.pub_socket, u'clear_output', content,
97 97 parent=self.parent_header, ident=self.topic,
98 98 )
99 99
100 100 @magics_class
101 101 class KernelMagics(Magics):
102 102 #------------------------------------------------------------------------
103 103 # Magic overrides
104 104 #------------------------------------------------------------------------
105 105 # Once the base class stops inheriting from magic, this code needs to be
106 106 # moved into a separate machinery as well. For now, at least isolate here
107 107 # the magics which this class needs to implement differently from the base
108 108 # class, or that are unique to it.
109 109
110 110 @line_magic
111 111 def doctest_mode(self, parameter_s=''):
112 112 """Toggle doctest mode on and off.
113 113
114 114 This mode is intended to make IPython behave as much as possible like a
115 115 plain Python shell, from the perspective of how its prompts, exceptions
116 116 and output look. This makes it easy to copy and paste parts of a
117 117 session into doctests. It does so by:
118 118
119 119 - Changing the prompts to the classic ``>>>`` ones.
120 120 - Changing the exception reporting mode to 'Plain'.
121 121 - Disabling pretty-printing of output.
122 122
123 123 Note that IPython also supports the pasting of code snippets that have
124 124 leading '>>>' and '...' prompts in them. This means that you can paste
125 125 doctests from files or docstrings (even if they have leading
126 126 whitespace), and the code will execute correctly. You can then use
127 127 '%history -t' to see the translated history; this will give you the
128 128 input after removal of all the leading prompts and whitespace, which
129 129 can be pasted back into an editor.
130 130
131 131 With these features, you can switch into this mode easily whenever you
132 132 need to do testing and changes to doctests, without having to leave
133 133 your existing IPython session.
134 134 """
135 135
136 136 from IPython.utils.ipstruct import Struct
137 137
138 138 # Shorthands
139 139 shell = self.shell
140 140 disp_formatter = self.shell.display_formatter
141 141 ptformatter = disp_formatter.formatters['text/plain']
142 142 # dstore is a data store kept in the instance metadata bag to track any
143 143 # changes we make, so we can undo them later.
144 144 dstore = shell.meta.setdefault('doctest_mode', Struct())
145 145 save_dstore = dstore.setdefault
146 146
147 147 # save a few values we'll need to recover later
148 148 mode = save_dstore('mode', False)
149 149 save_dstore('rc_pprint', ptformatter.pprint)
150 150 save_dstore('rc_plain_text_only',disp_formatter.plain_text_only)
151 151 save_dstore('xmode', shell.InteractiveTB.mode)
152 152
153 153 if mode == False:
154 154 # turn on
155 155 ptformatter.pprint = False
156 156 disp_formatter.plain_text_only = True
157 157 shell.magic('xmode Plain')
158 158 else:
159 159 # turn off
160 160 ptformatter.pprint = dstore.rc_pprint
161 161 disp_formatter.plain_text_only = dstore.rc_plain_text_only
162 162 shell.magic("xmode " + dstore.xmode)
163 163
164 164 # Store new mode and inform on console
165 165 dstore.mode = bool(1-int(mode))
166 166 mode_label = ['OFF','ON'][dstore.mode]
167 167 print('Doctest mode is:', mode_label)
168 168
169 169 # Send the payload back so that clients can modify their prompt display
170 170 payload = dict(
171 171 source='IPython.zmq.zmqshell.ZMQInteractiveShell.doctest_mode',
172 172 mode=dstore.mode)
173 173 shell.payload_manager.write_payload(payload)
174 174
175 175
176 176 _find_edit_target = CodeMagics._find_edit_target
177 177
178 178 @skip_doctest
179 179 @line_magic
180 180 def edit(self, parameter_s='', last_call=['','']):
181 181 """Bring up an editor and execute the resulting code.
182 182
183 183 Usage:
184 184 %edit [options] [args]
185 185
186 186 %edit runs an external text editor. You will need to set the command for
187 187 this editor via the ``TerminalInteractiveShell.editor`` option in your
188 188 configuration file before it will work.
189 189
190 190 This command allows you to conveniently edit multi-line code right in
191 191 your IPython session.
192 192
193 193 If called without arguments, %edit opens up an empty editor with a
194 194 temporary file and will execute the contents of this file when you
195 195 close it (don't forget to save it!).
196 196
197 197
198 198 Options:
199 199
200 200 -n <number>: open the editor at a specified line number. By default,
201 201 the IPython editor hook uses the unix syntax 'editor +N filename', but
202 202 you can configure this by providing your own modified hook if your
203 203 favorite editor supports line-number specifications with a different
204 204 syntax.
205 205
206 206 -p: this will call the editor with the same data as the previous time
207 207 it was used, regardless of how long ago (in your current session) it
208 208 was.
209 209
210 210 -r: use 'raw' input. This option only applies to input taken from the
211 211 user's history. By default, the 'processed' history is used, so that
212 212 magics are loaded in their transformed version to valid Python. If
213 213 this option is given, the raw input as typed as the command line is
214 214 used instead. When you exit the editor, it will be executed by
215 215 IPython's own processor.
216 216
217 217 -x: do not execute the edited code immediately upon exit. This is
218 218 mainly useful if you are editing programs which need to be called with
219 219 command line arguments, which you can then do using %run.
220 220
221 221
222 222 Arguments:
223 223
224 224 If arguments are given, the following possibilites exist:
225 225
226 226 - The arguments are numbers or pairs of colon-separated numbers (like
227 227 1 4:8 9). These are interpreted as lines of previous input to be
228 228 loaded into the editor. The syntax is the same of the %macro command.
229 229
230 230 - If the argument doesn't start with a number, it is evaluated as a
231 231 variable and its contents loaded into the editor. You can thus edit
232 232 any string which contains python code (including the result of
233 233 previous edits).
234 234
235 235 - If the argument is the name of an object (other than a string),
236 236 IPython will try to locate the file where it was defined and open the
237 237 editor at the point where it is defined. You can use `%edit function`
238 238 to load an editor exactly at the point where 'function' is defined,
239 239 edit it and have the file be executed automatically.
240 240
241 241 If the object is a macro (see %macro for details), this opens up your
242 242 specified editor with a temporary file containing the macro's data.
243 243 Upon exit, the macro is reloaded with the contents of the file.
244 244
245 245 Note: opening at an exact line is only supported under Unix, and some
246 246 editors (like kedit and gedit up to Gnome 2.8) do not understand the
247 247 '+NUMBER' parameter necessary for this feature. Good editors like
248 248 (X)Emacs, vi, jed, pico and joe all do.
249 249
250 250 - If the argument is not found as a variable, IPython will look for a
251 251 file with that name (adding .py if necessary) and load it into the
252 252 editor. It will execute its contents with execfile() when you exit,
253 253 loading any code in the file into your interactive namespace.
254 254
255 255 After executing your code, %edit will return as output the code you
256 256 typed in the editor (except when it was an existing file). This way
257 257 you can reload the code in further invocations of %edit as a variable,
258 258 via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
259 259 the output.
260 260
261 261 Note that %edit is also available through the alias %ed.
262 262
263 263 This is an example of creating a simple function inside the editor and
264 264 then modifying it. First, start up the editor:
265 265
266 266 In [1]: ed
267 267 Editing... done. Executing edited code...
268 268 Out[1]: 'def foo():n print "foo() was defined in an editing session"n'
269 269
270 270 We can then call the function foo():
271 271
272 272 In [2]: foo()
273 273 foo() was defined in an editing session
274 274
275 275 Now we edit foo. IPython automatically loads the editor with the
276 276 (temporary) file where foo() was previously defined:
277 277
278 278 In [3]: ed foo
279 279 Editing... done. Executing edited code...
280 280
281 281 And if we call foo() again we get the modified version:
282 282
283 283 In [4]: foo()
284 284 foo() has now been changed!
285 285
286 286 Here is an example of how to edit a code snippet successive
287 287 times. First we call the editor:
288 288
289 289 In [5]: ed
290 290 Editing... done. Executing edited code...
291 291 hello
292 292 Out[5]: "print 'hello'n"
293 293
294 294 Now we call it again with the previous output (stored in _):
295 295
296 296 In [6]: ed _
297 297 Editing... done. Executing edited code...
298 298 hello world
299 299 Out[6]: "print 'hello world'n"
300 300
301 301 Now we call it with the output #8 (stored in _8, also as Out[8]):
302 302
303 303 In [7]: ed _8
304 304 Editing... done. Executing edited code...
305 305 hello again
306 306 Out[7]: "print 'hello again'n"
307 307 """
308 308
309 309 opts,args = self.parse_options(parameter_s,'prn:')
310 310
311 311 try:
312 312 filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call)
313 313 except MacroToEdit as e:
314 314 # TODO: Implement macro editing over 2 processes.
315 315 print("Macro editing not yet implemented in 2-process model.")
316 316 return
317 317
318 318 # Make sure we send to the client an absolute path, in case the working
319 319 # directory of client and kernel don't match
320 320 filename = os.path.abspath(filename)
321 321
322 322 payload = {
323 323 'source' : 'IPython.zmq.zmqshell.ZMQInteractiveShell.edit_magic',
324 324 'filename' : filename,
325 325 'line_number' : lineno
326 326 }
327 327 self.shell.payload_manager.write_payload(payload)
328 328
329 329 # A few magics that are adapted to the specifics of using pexpect and a
330 330 # remote terminal
331 331
332 332 @line_magic
333 333 def clear(self, arg_s):
334 334 """Clear the terminal."""
335 335 if os.name == 'posix':
336 336 self.shell.system("clear")
337 337 else:
338 338 self.shell.system("cls")
339 339
340 340 if os.name == 'nt':
341 341 # This is the usual name in windows
342 342 cls = line_magic('cls')(clear)
343 343
344 344 # Terminal pagers won't work over pexpect, but we do have our own pager
345 345
346 346 @line_magic
347 347 def less(self, arg_s):
348 348 """Show a file through the pager.
349 349
350 350 Files ending in .py are syntax-highlighted."""
351 351 cont = open(arg_s).read()
352 352 if arg_s.endswith('.py'):
353 353 cont = self.shell.pycolorize(cont)
354 354 page.page(cont)
355 355
356 356 more = line_magic('more')(less)
357 357
358 358 # Man calls a pager, so we also need to redefine it
359 359 if os.name == 'posix':
360 360 @line_magic
361 361 def man(self, arg_s):
362 362 """Find the man page for the given command and display in pager."""
363 363 page.page(self.shell.getoutput('man %s | col -b' % arg_s,
364 364 split=False))
365 365
366 366 @line_magic
367 367 def connect_info(self, arg_s):
368 368 """Print information for connecting other clients to this kernel
369 369
370 370 It will print the contents of this session's connection file, as well as
371 371 shortcuts for local clients.
372 372
373 373 In the simplest case, when called from the most recently launched kernel,
374 374 secondary clients can be connected, simply with:
375 375
376 376 $> ipython <app> --existing
377 377
378 378 """
379 379
380 380 from IPython.core.application import BaseIPythonApplication as BaseIPApp
381 381
382 382 if BaseIPApp.initialized():
383 383 app = BaseIPApp.instance()
384 384 security_dir = app.profile_dir.security_dir
385 385 profile = app.profile
386 386 else:
387 387 profile = 'default'
388 388 security_dir = ''
389 389
390 390 try:
391 391 connection_file = get_connection_file()
392 392 info = get_connection_info(unpack=False)
393 393 except Exception as e:
394 394 error("Could not get connection info: %r" % e)
395 395 return
396 396
397 397 # add profile flag for non-default profile
398 398 profile_flag = "--profile %s" % profile if profile != 'default' else ""
399 399
400 400 # if it's in the security dir, truncate to basename
401 401 if security_dir == os.path.dirname(connection_file):
402 402 connection_file = os.path.basename(connection_file)
403 403
404 404
405 405 print (info + '\n')
406 406 print ("Paste the above JSON into a file, and connect with:\n"
407 407 " $> ipython <app> --existing <file>\n"
408 408 "or, if you are local, you can connect with just:\n"
409 409 " $> ipython <app> --existing {0} {1}\n"
410 410 "or even just:\n"
411 411 " $> ipython <app> --existing {1}\n"
412 412 "if this is the most recent IPython session you have started.".format(
413 413 connection_file, profile_flag
414 414 )
415 415 )
416 416
417 417 @line_magic
418 418 def qtconsole(self, arg_s):
419 419 """Open a qtconsole connected to this kernel.
420 420
421 421 Useful for connecting a qtconsole to running notebooks, for better
422 422 debugging.
423 423 """
424 424
425 425 # %qtconsole should imply bind_kernel for engines:
426 426 try:
427 427 from IPython.parallel import bind_kernel
428 428 except ImportError:
429 429 # technically possible, because parallel has higher pyzmq min-version
430 430 pass
431 431 else:
432 432 bind_kernel()
433 433
434 434 try:
435 435 p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix'))
436 436 except Exception as e:
437 437 error("Could not start qtconsole: %r" % e)
438 438 return
439 439
440 440 def safe_unicode(e):
441 441 """unicode(e) with various fallbacks. Used for exceptions, which may not be
442 442 safe to call unicode() on.
443 443 """
444 444 try:
445 445 return unicode(e)
446 446 except UnicodeError:
447 447 pass
448 448
449 449 try:
450 450 return py3compat.str_to_unicode(str(e))
451 451 except UnicodeError:
452 452 pass
453 453
454 454 try:
455 455 return py3compat.str_to_unicode(repr(e))
456 456 except UnicodeError:
457 457 pass
458 458
459 459 return u'Unrecoverably corrupt evalue'
460 460
461 461
462 462 class ZMQInteractiveShell(InteractiveShell):
463 463 """A subclass of InteractiveShell for ZMQ."""
464 464
465 465 displayhook_class = Type(ZMQShellDisplayHook)
466 466 display_pub_class = Type(ZMQDisplayPublisher)
467 467
468 468 # Override the traitlet in the parent class, because there's no point using
469 469 # readline for the kernel. Can be removed when the readline code is moved
470 470 # to the terminal frontend.
471 471 colors_force = CBool(True)
472 472 readline_use = CBool(False)
473 473 # autoindent has no meaning in a zmqshell, and attempting to enable it
474 474 # will print a warning in the absence of readline.
475 475 autoindent = CBool(False)
476 476
477 477 exiter = Instance(ZMQExitAutocall)
478 478 def _exiter_default(self):
479 479 return ZMQExitAutocall(self)
480 480
481 481 def _exit_now_changed(self, name, old, new):
482 482 """stop eventloop when exit_now fires"""
483 483 if new:
484 484 loop = ioloop.IOLoop.instance()
485 485 loop.add_timeout(time.time()+0.1, loop.stop)
486 486
487 487 keepkernel_on_exit = None
488 488
489 489 # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no
490 490 # interactive input being read; we provide event loop support in ipkernel
491 491 from .eventloops import enable_gui
492 492 enable_gui = staticmethod(enable_gui)
493 493
494 494 def init_environment(self):
495 495 """Configure the user's environment.
496 496
497 497 """
498 498 env = os.environ
499 499 # These two ensure 'ls' produces nice coloring on BSD-derived systems
500 500 env['TERM'] = 'xterm-color'
501 501 env['CLICOLOR'] = '1'
502 502 # Since normal pagers don't work at all (over pexpect we don't have
503 503 # single-key control of the subprocess), try to disable paging in
504 504 # subprocesses as much as possible.
505 505 env['PAGER'] = 'cat'
506 506 env['GIT_PAGER'] = 'cat'
507 507
508 508 # And install the payload version of page.
509 509 install_payload_page()
510 510
511 511 def auto_rewrite_input(self, cmd):
512 512 """Called to show the auto-rewritten input for autocall and friends.
513 513
514 514 FIXME: this payload is currently not correctly processed by the
515 515 frontend.
516 516 """
517 517 new = self.prompt_manager.render('rewrite') + cmd
518 518 payload = dict(
519 519 source='IPython.zmq.zmqshell.ZMQInteractiveShell.auto_rewrite_input',
520 520 transformed_input=new,
521 521 )
522 522 self.payload_manager.write_payload(payload)
523 523
524 524 def ask_exit(self):
525 525 """Engage the exit actions."""
526 526 self.exit_now = True
527 527 payload = dict(
528 528 source='IPython.zmq.zmqshell.ZMQInteractiveShell.ask_exit',
529 529 exit=True,
530 530 keepkernel=self.keepkernel_on_exit,
531 531 )
532 532 self.payload_manager.write_payload(payload)
533 533
534 534 def _showtraceback(self, etype, evalue, stb):
535 535
536 536 exc_content = {
537 537 u'traceback' : stb,
538 538 u'ename' : unicode(etype.__name__),
539 539 u'evalue' : safe_unicode(evalue)
540 540 }
541 541
542 542 dh = self.displayhook
543 543 # Send exception info over pub socket for other clients than the caller
544 544 # to pick up
545 545 topic = None
546 546 if dh.topic:
547 547 topic = dh.topic.replace(b'pyout', b'pyerr')
548 548
549 549 exc_msg = dh.session.send(dh.pub_socket, u'pyerr', json_clean(exc_content), dh.parent_header, ident=topic)
550 550
551 551 # FIXME - Hack: store exception info in shell object. Right now, the
552 552 # caller is reading this info after the fact, we need to fix this logic
553 553 # to remove this hack. Even uglier, we need to store the error status
554 554 # here, because in the main loop, the logic that sets it is being
555 555 # skipped because runlines swallows the exceptions.
556 556 exc_content[u'status'] = u'error'
557 557 self._reply_content = exc_content
558 558 # /FIXME
559 559
560 560 return exc_content
561 561
562 562 def set_next_input(self, text):
563 563 """Send the specified text to the frontend to be presented at the next
564 564 input cell."""
565 565 payload = dict(
566 566 source='IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input',
567 567 text=text
568 568 )
569 569 self.payload_manager.write_payload(payload)
570 570
571 571 #-------------------------------------------------------------------------
572 572 # Things related to magics
573 573 #-------------------------------------------------------------------------
574 574
575 575 def init_magics(self):
576 576 super(ZMQInteractiveShell, self).init_magics()
577 577 self.register_magics(KernelMagics)
578 578
579 579
580 580
581 581 InteractiveShellABC.register(ZMQInteractiveShell)
General Comments 0
You need to be logged in to leave comments. Login now