##// END OF EJS Templates
Use `backcall` and introduce `ExecutionRequest`
Fabio Niephaus -
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,189 +1,181 b''
1 1 """Infrastructure for registering and firing callbacks on application events.
2 2
3 3 Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to
4 4 be called at specific times, or a collection of alternative methods to try,
5 5 callbacks are designed to be used by extension authors. A number of callbacks
6 6 can be registered for the same event without needing to be aware of one another.
7 7
8 8 The functions defined in this module are no-ops indicating the names of available
9 9 events and the arguments which will be passed to them.
10 10
11 11 .. note::
12 12
13 13 This API is experimental in IPython 2.0, and may be revised in future versions.
14 14 """
15 15
16 from functools import wraps
17 from inspect import isfunction
18 try:
19 from inspect import getfullargspec
20 except:
21 from inspect import getargspec as getfullargspec # for Python2 compatibility.
22
23 # original function -> wrapper function mapping
24 compatibility_wrapper_functions = {}
25
26 def _compatibility_wrapper_for(function):
27 """Returns a wrapper for a function without args that accepts any args."""
28 if len(getfullargspec(function).args) > 0:
29 raise TypeError('%s cannot have arguments' % function)
30 if function in compatibility_wrapper_functions:
31 return compatibility_wrapper_functions[function]
32 @wraps(function)
33 def wrapper(*args, **kwargs):
34 function()
35 compatibility_wrapper_functions[function] = wrapper
36 return wrapper
16 from backcall import callback_prototype
17
37 18
38 19 class EventManager(object):
39 20 """Manage a collection of events and a sequence of callbacks for each.
40 21
41 22 This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
42 23 instances as an ``events`` attribute.
43 24
44 25 .. note::
45 26
46 27 This API is experimental in IPython 2.0, and may be revised in future versions.
47 28 """
48 29 def __init__(self, shell, available_events):
49 30 """Initialise the :class:`CallbackManager`.
50 31
51 32 Parameters
52 33 ----------
53 34 shell
54 35 The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
55 36 available_callbacks
56 37 An iterable of names for callback events.
57 38 """
58 39 self.shell = shell
59 40 self.callbacks = {n:[] for n in available_events}
60 41
61 42 def register(self, event, function):
62 43 """Register a new event callback
63 44
64 45 Parameters
65 46 ----------
66 47 event : str
67 48 The event for which to register this callback.
68 49 function : callable
69 50 A function to be called on the given event. It should take the same
70 51 parameters as the appropriate callback prototype.
71 52
72 53 Raises
73 54 ------
74 55 TypeError
75 56 If ``function`` is not callable.
76 57 KeyError
77 58 If ``event`` is not one of the known events.
78 59 """
79 60 if not callable(function):
80 61 raise TypeError('Need a callable, got %r' % function)
81
82 callback_proto = available_events.get(event)
83 if (isfunction(callback_proto) and isfunction(function) and
84 len(getfullargspec(callback_proto).args) > 0 and
85 len(getfullargspec(function).args) == 0):
86 # `callback_proto` has args but `function` does not, so a
87 # compatibility wrapper is needed.
88 self.callbacks[event].append(_compatibility_wrapper_for(function))
89 else:
90 self.callbacks[event].append(function)
62 self.callbacks[event].append(_adapt_function(event, function))
91 63
92 64 def unregister(self, event, function):
93 65 """Remove a callback from the given event."""
94 wrapper = compatibility_wrapper_functions.get(function)
95 if wrapper:
96 self.callbacks[event].remove(wrapper)
97 else:
98 self.callbacks[event].remove(function)
66 self.callbacks[event].remove(_adapt_function(event, function))
99 67
100 68 def trigger(self, event, *args, **kwargs):
101 69 """Call callbacks for ``event``.
102 70
103 71 Any additional arguments are passed to all callbacks registered for this
104 72 event. Exceptions raised by callbacks are caught, and a message printed.
105 73 """
106 74 for func in self.callbacks[event][:]:
107 75 try:
108 76 func(*args, **kwargs)
109 77 except Exception:
110 78 print("Error in callback {} (for {}):".format(func, event))
111 79 self.shell.showtraceback()
112 80
113 81 # event_name -> prototype mapping
114 82 available_events = {}
115 83
84 # (event, function) -> adapted function mapping
85 adapted_functions = {}
86
87
116 88 def _define_event(callback_proto):
117 89 available_events[callback_proto.__name__] = callback_proto
118 90 return callback_proto
119 91
92
93 def _adapt_function(event, function):
94 """Adapts and caches a function using `backcall` to provide compatibility.
95
96 Function adaptations depend not only on the function but also on the event,
97 as events may expect different arguments (e.g. `request` vs. `result`).
98 Hence, `(event, function)` is used as the cache key.
99 """
100 if (event, function) in adapted_functions:
101 return adapted_functions[(event, function)]
102 callback_proto = available_events.get(event)
103 adapted_function = callback_proto.adapt(function)
104 adapted_functions[(event, function)] = adapted_function
105 return adapted_function
106
107
120 108 # ------------------------------------------------------------------------------
121 109 # Callback prototypes
122 110 #
123 111 # No-op functions which describe the names of available events and the
124 112 # signatures of callbacks for those events.
125 113 # ------------------------------------------------------------------------------
126 114
127 115 @_define_event
128 def pre_execute(result):
116 @callback_prototype
117 def pre_execute(request):
129 118 """Fires before code is executed in response to user/frontend action.
130 119
131 120 This includes comm and widget messages and silent execution, as well as user
132 121 code cells.
133 122
134 123 Parameters
135 124 ----------
136 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
137 The object which will be returned as the execution result.
125 request : :class:`~IPython.core.interactiveshell.ExecutionRequest`
126 The object representing the code execution request.
138 127 """
139 128 pass
140 129
141 130 @_define_event
142 def pre_run_cell(result):
131 @callback_prototype
132 def pre_run_cell(request):
143 133 """Fires before user-entered code runs.
144 134
145 135 Parameters
146 136 ----------
147 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
148 The object which will be returned as the execution result.
137 request : :class:`~IPython.core.interactiveshell.ExecutionRequest`
138 The object representing the code execution request.
149 139 """
150 140 pass
151 141
152 142 @_define_event
143 @callback_prototype
153 144 def post_execute(result):
154 145 """Fires after code is executed in response to user/frontend action.
155 146
156 147 This includes comm and widget messages and silent execution, as well as user
157 148 code cells.
158 149
159 150 Parameters
160 151 ----------
161 152 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
162 153 The object which will be returned as the execution result.
163 154 """
164 155 pass
165 156
166 157 @_define_event
158 @callback_prototype
167 159 def post_run_cell(result):
168 160 """Fires after user-entered code runs.
169 161
170 162 Parameters
171 163 ----------
172 164 result : :class:`~IPython.core.interactiveshell.ExecutionResult`
173 165 The object which will be returned as the execution result.
174 166 """
175 167 pass
176 168
177 169 @_define_event
178 170 def shell_initialized(ip):
179 171 """Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
180 172
181 173 This is before extensions and startup scripts are loaded, so it can only be
182 174 set by subclassing.
183 175
184 176 Parameters
185 177 ----------
186 178 ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
187 179 The newly initialised shell.
188 180 """
189 181 pass
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,69 +1,75 b''
1 from backcall import callback_prototype
1 2 import unittest
2 3 from unittest.mock import Mock
3 4
4 5 from IPython.core import events
5 6 import IPython.testing.tools as tt
6 7
8
7 9 @events._define_event
10 @callback_prototype
8 11 def ping_received():
9 12 pass
10 13
14
11 15 @events._define_event
16 @callback_prototype
12 17 def event_with_argument(argument):
13 18 pass
14 19
20
15 21 class CallbackTests(unittest.TestCase):
16 22 def setUp(self):
17 23 self.em = events.EventManager(get_ipython(), {'ping_received': ping_received, 'event_with_argument': event_with_argument})
18 24
19 25 def test_register_unregister(self):
20 26 cb = Mock()
21 27
22 28 self.em.register('ping_received', cb)
23 29 self.em.trigger('ping_received')
24 30 self.assertEqual(cb.call_count, 1)
25 31
26 32 self.em.unregister('ping_received', cb)
27 33 self.em.trigger('ping_received')
28 34 self.assertEqual(cb.call_count, 1)
29 35
30 36 def test_cb_error(self):
31 37 cb = Mock(side_effect=ValueError)
32 38 self.em.register('ping_received', cb)
33 39 with tt.AssertPrints("Error in callback"):
34 40 self.em.trigger('ping_received')
35 41
36 42 def test_unregister_during_callback(self):
37 43 invoked = [False] * 3
38 44
39 45 def func1(*_):
40 46 invoked[0] = True
41 47 self.em.unregister('ping_received', func1)
42 48 self.em.register('ping_received', func3)
43 49
44 50 def func2(*_):
45 51 invoked[1] = True
46 52 self.em.unregister('ping_received', func2)
47 53
48 54 def func3(*_):
49 55 invoked[2] = True
50 56
51 57 self.em.register('ping_received', func1)
52 58 self.em.register('ping_received', func2)
53 59
54 60 self.em.trigger('ping_received')
55 61 self.assertEqual([True, True, False], invoked)
56 62 self.assertEqual([func3], self.em.callbacks['ping_received'])
57 63
58 64 def test_ignore_event_arguments_if_no_argument_required(self):
59 65 call_count = [0]
60 66 def event_with_no_argument():
61 67 call_count[0] += 1
62 68
63 self.em.register('event_with_argument', event_with_no_argument)
69 self.em.register('event_with_argument', event_with_no_argument)
64 70 self.em.trigger('event_with_argument', 'the argument')
65 71 self.assertEqual(call_count[0], 1)
66 72
67 73 self.em.unregister('event_with_argument', event_with_no_argument)
68 74 self.em.trigger('ping_received')
69 75 self.assertEqual(call_count[0], 1)
@@ -1,920 +1,926 b''
1 1 # -*- coding: utf-8 -*-
2 2 """Tests for the key interactiveshell module.
3 3
4 4 Historically the main classes in interactiveshell have been under-tested. This
5 5 module should grow as many single-method tests as possible to trap many of the
6 6 recurring bugs we seem to encounter with high-level interaction.
7 7 """
8 8
9 9 # Copyright (c) IPython Development Team.
10 10 # Distributed under the terms of the Modified BSD License.
11 11
12 12 import ast
13 13 import os
14 14 import signal
15 15 import shutil
16 16 import sys
17 17 import tempfile
18 18 import unittest
19 19 from unittest import mock
20 20 from io import StringIO
21 21
22 22 from os.path import join
23 23
24 24 import nose.tools as nt
25 25
26 26 from IPython.core.error import InputRejected
27 27 from IPython.core.inputtransformer import InputTransformer
28 28 from IPython.testing.decorators import (
29 29 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 30 )
31 31 from IPython.testing import tools as tt
32 32 from IPython.utils.process import find_cmd
33 33 from IPython.utils import py3compat
34 34
35 35 #-----------------------------------------------------------------------------
36 36 # Globals
37 37 #-----------------------------------------------------------------------------
38 38 # This is used by every single test, no point repeating it ad nauseam
39 39 ip = get_ipython()
40 40
41 41 #-----------------------------------------------------------------------------
42 42 # Tests
43 43 #-----------------------------------------------------------------------------
44 44
45 45 class DerivedInterrupt(KeyboardInterrupt):
46 46 pass
47 47
48 48 class InteractiveShellTestCase(unittest.TestCase):
49 49 def test_naked_string_cells(self):
50 50 """Test that cells with only naked strings are fully executed"""
51 51 # First, single-line inputs
52 52 ip.run_cell('"a"\n')
53 53 self.assertEqual(ip.user_ns['_'], 'a')
54 54 # And also multi-line cells
55 55 ip.run_cell('"""a\nb"""\n')
56 56 self.assertEqual(ip.user_ns['_'], 'a\nb')
57 57
58 58 def test_run_empty_cell(self):
59 59 """Just make sure we don't get a horrible error with a blank
60 60 cell of input. Yes, I did overlook that."""
61 61 old_xc = ip.execution_count
62 62 res = ip.run_cell('')
63 63 self.assertEqual(ip.execution_count, old_xc)
64 64 self.assertEqual(res.execution_count, None)
65 65
66 66 def test_run_cell_multiline(self):
67 67 """Multi-block, multi-line cells must execute correctly.
68 68 """
69 69 src = '\n'.join(["x=1",
70 70 "y=2",
71 71 "if 1:",
72 72 " x += 1",
73 73 " y += 1",])
74 74 res = ip.run_cell(src)
75 75 self.assertEqual(ip.user_ns['x'], 2)
76 76 self.assertEqual(ip.user_ns['y'], 3)
77 77 self.assertEqual(res.success, True)
78 78 self.assertEqual(res.result, None)
79 79
80 80 def test_multiline_string_cells(self):
81 81 "Code sprinkled with multiline strings should execute (GH-306)"
82 82 ip.run_cell('tmp=0')
83 83 self.assertEqual(ip.user_ns['tmp'], 0)
84 84 res = ip.run_cell('tmp=1;"""a\nb"""\n')
85 85 self.assertEqual(ip.user_ns['tmp'], 1)
86 86 self.assertEqual(res.success, True)
87 87 self.assertEqual(res.result, "a\nb")
88 88
89 89 def test_dont_cache_with_semicolon(self):
90 90 "Ending a line with semicolon should not cache the returned object (GH-307)"
91 91 oldlen = len(ip.user_ns['Out'])
92 92 for cell in ['1;', '1;1;']:
93 93 res = ip.run_cell(cell, store_history=True)
94 94 newlen = len(ip.user_ns['Out'])
95 95 self.assertEqual(oldlen, newlen)
96 96 self.assertIsNone(res.result)
97 97 i = 0
98 98 #also test the default caching behavior
99 99 for cell in ['1', '1;1']:
100 100 ip.run_cell(cell, store_history=True)
101 101 newlen = len(ip.user_ns['Out'])
102 102 i += 1
103 103 self.assertEqual(oldlen+i, newlen)
104 104
105 105 def test_syntax_error(self):
106 106 res = ip.run_cell("raise = 3")
107 107 self.assertIsInstance(res.error_before_exec, SyntaxError)
108 108
109 109 def test_In_variable(self):
110 110 "Verify that In variable grows with user input (GH-284)"
111 111 oldlen = len(ip.user_ns['In'])
112 112 ip.run_cell('1;', store_history=True)
113 113 newlen = len(ip.user_ns['In'])
114 114 self.assertEqual(oldlen+1, newlen)
115 115 self.assertEqual(ip.user_ns['In'][-1],'1;')
116 116
117 117 def test_magic_names_in_string(self):
118 118 ip.run_cell('a = """\n%exit\n"""')
119 119 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
120 120
121 121 def test_trailing_newline(self):
122 122 """test that running !(command) does not raise a SyntaxError"""
123 123 ip.run_cell('!(true)\n', False)
124 124 ip.run_cell('!(true)\n\n\n', False)
125 125
126 126 def test_gh_597(self):
127 127 """Pretty-printing lists of objects with non-ascii reprs may cause
128 128 problems."""
129 129 class Spam(object):
130 130 def __repr__(self):
131 131 return "\xe9"*50
132 132 import IPython.core.formatters
133 133 f = IPython.core.formatters.PlainTextFormatter()
134 134 f([Spam(),Spam()])
135 135
136 136
137 137 def test_future_flags(self):
138 138 """Check that future flags are used for parsing code (gh-777)"""
139 139 ip.run_cell('from __future__ import barry_as_FLUFL')
140 140 try:
141 141 ip.run_cell('prfunc_return_val = 1 <> 2')
142 142 assert 'prfunc_return_val' in ip.user_ns
143 143 finally:
144 144 # Reset compiler flags so we don't mess up other tests.
145 145 ip.compile.reset_compiler_flags()
146 146
147 147 def test_can_pickle(self):
148 148 "Can we pickle objects defined interactively (GH-29)"
149 149 ip = get_ipython()
150 150 ip.reset()
151 151 ip.run_cell(("class Mylist(list):\n"
152 152 " def __init__(self,x=[]):\n"
153 153 " list.__init__(self,x)"))
154 154 ip.run_cell("w=Mylist([1,2,3])")
155 155
156 156 from pickle import dumps
157 157
158 158 # We need to swap in our main module - this is only necessary
159 159 # inside the test framework, because IPython puts the interactive module
160 160 # in place (but the test framework undoes this).
161 161 _main = sys.modules['__main__']
162 162 sys.modules['__main__'] = ip.user_module
163 163 try:
164 164 res = dumps(ip.user_ns["w"])
165 165 finally:
166 166 sys.modules['__main__'] = _main
167 167 self.assertTrue(isinstance(res, bytes))
168 168
169 169 def test_global_ns(self):
170 170 "Code in functions must be able to access variables outside them."
171 171 ip = get_ipython()
172 172 ip.run_cell("a = 10")
173 173 ip.run_cell(("def f(x):\n"
174 174 " return x + a"))
175 175 ip.run_cell("b = f(12)")
176 176 self.assertEqual(ip.user_ns["b"], 22)
177 177
178 178 def test_bad_custom_tb(self):
179 179 """Check that InteractiveShell is protected from bad custom exception handlers"""
180 180 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
181 181 self.assertEqual(ip.custom_exceptions, (IOError,))
182 182 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
183 183 ip.run_cell(u'raise IOError("foo")')
184 184 self.assertEqual(ip.custom_exceptions, ())
185 185
186 186 def test_bad_custom_tb_return(self):
187 187 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
188 188 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
189 189 self.assertEqual(ip.custom_exceptions, (NameError,))
190 190 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
191 191 ip.run_cell(u'a=abracadabra')
192 192 self.assertEqual(ip.custom_exceptions, ())
193 193
194 194 def test_drop_by_id(self):
195 195 myvars = {"a":object(), "b":object(), "c": object()}
196 196 ip.push(myvars, interactive=False)
197 197 for name in myvars:
198 198 assert name in ip.user_ns, name
199 199 assert name in ip.user_ns_hidden, name
200 200 ip.user_ns['b'] = 12
201 201 ip.drop_by_id(myvars)
202 202 for name in ["a", "c"]:
203 203 assert name not in ip.user_ns, name
204 204 assert name not in ip.user_ns_hidden, name
205 205 assert ip.user_ns['b'] == 12
206 206 ip.reset()
207 207
208 208 def test_var_expand(self):
209 209 ip.user_ns['f'] = u'Ca\xf1o'
210 210 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
211 211 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
212 212 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
213 213 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
214 214
215 215 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
216 216
217 217 ip.user_ns['f'] = b'Ca\xc3\xb1o'
218 218 # This should not raise any exception:
219 219 ip.var_expand(u'echo $f')
220 220
221 221 def test_var_expand_local(self):
222 222 """Test local variable expansion in !system and %magic calls"""
223 223 # !system
224 224 ip.run_cell('def test():\n'
225 225 ' lvar = "ttt"\n'
226 226 ' ret = !echo {lvar}\n'
227 227 ' return ret[0]\n')
228 228 res = ip.user_ns['test']()
229 229 nt.assert_in('ttt', res)
230 230
231 231 # %magic
232 232 ip.run_cell('def makemacro():\n'
233 233 ' macroname = "macro_var_expand_locals"\n'
234 234 ' %macro {macroname} codestr\n')
235 235 ip.user_ns['codestr'] = "str(12)"
236 236 ip.run_cell('makemacro()')
237 237 nt.assert_in('macro_var_expand_locals', ip.user_ns)
238 238
239 239 def test_var_expand_self(self):
240 240 """Test variable expansion with the name 'self', which was failing.
241 241
242 242 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
243 243 """
244 244 ip.run_cell('class cTest:\n'
245 245 ' classvar="see me"\n'
246 246 ' def test(self):\n'
247 247 ' res = !echo Variable: {self.classvar}\n'
248 248 ' return res[0]\n')
249 249 nt.assert_in('see me', ip.user_ns['cTest']().test())
250 250
251 251 def test_bad_var_expand(self):
252 252 """var_expand on invalid formats shouldn't raise"""
253 253 # SyntaxError
254 254 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
255 255 # NameError
256 256 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
257 257 # ZeroDivisionError
258 258 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
259 259
260 260 def test_silent_postexec(self):
261 261 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
262 262 pre_explicit = mock.Mock()
263 263 pre_always = mock.Mock()
264 264 post_explicit = mock.Mock()
265 265 post_always = mock.Mock()
266 266 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
267 267
268 268 ip.events.register('pre_run_cell', pre_explicit)
269 269 ip.events.register('pre_execute', pre_always)
270 270 ip.events.register('post_run_cell', post_explicit)
271 271 ip.events.register('post_execute', post_always)
272 272
273 273 try:
274 274 ip.run_cell("1", silent=True)
275 275 assert pre_always.called
276 276 assert not pre_explicit.called
277 277 assert post_always.called
278 278 assert not post_explicit.called
279 request, = pre_always.call_args[0]
280 result, = post_always.call_args[0]
281 self.assertEqual(request, result.request)
279 282 # double-check that non-silent exec did what we expected
280 283 # silent to avoid
281 284 ip.run_cell("1")
282 285 assert pre_explicit.called
283 286 assert post_explicit.called
284 287 # check that post hooks are always called
285 288 [m.reset_mock() for m in all_mocks]
286 289 ip.run_cell("syntax error")
287 290 assert pre_always.called
288 291 assert pre_explicit.called
289 292 assert post_always.called
290 293 assert post_explicit.called
294 request, = pre_always.call_args[0]
295 result, = post_always.call_args[0]
296 self.assertEqual(request, result.request)
291 297 finally:
292 298 # remove post-exec
293 299 ip.events.unregister('pre_run_cell', pre_explicit)
294 300 ip.events.unregister('pre_execute', pre_always)
295 301 ip.events.unregister('post_run_cell', post_explicit)
296 302 ip.events.unregister('post_execute', post_always)
297 303
298 304 def test_silent_noadvance(self):
299 305 """run_cell(silent=True) doesn't advance execution_count"""
300 306 ec = ip.execution_count
301 307 # silent should force store_history=False
302 308 ip.run_cell("1", store_history=True, silent=True)
303 309
304 310 self.assertEqual(ec, ip.execution_count)
305 311 # double-check that non-silent exec did what we expected
306 312 # silent to avoid
307 313 ip.run_cell("1", store_history=True)
308 314 self.assertEqual(ec+1, ip.execution_count)
309 315
310 316 def test_silent_nodisplayhook(self):
311 317 """run_cell(silent=True) doesn't trigger displayhook"""
312 318 d = dict(called=False)
313 319
314 320 trap = ip.display_trap
315 321 save_hook = trap.hook
316 322
317 323 def failing_hook(*args, **kwargs):
318 324 d['called'] = True
319 325
320 326 try:
321 327 trap.hook = failing_hook
322 328 res = ip.run_cell("1", silent=True)
323 329 self.assertFalse(d['called'])
324 330 self.assertIsNone(res.result)
325 331 # double-check that non-silent exec did what we expected
326 332 # silent to avoid
327 333 ip.run_cell("1")
328 334 self.assertTrue(d['called'])
329 335 finally:
330 336 trap.hook = save_hook
331 337
332 338 def test_ofind_line_magic(self):
333 339 from IPython.core.magic import register_line_magic
334 340
335 341 @register_line_magic
336 342 def lmagic(line):
337 343 "A line magic"
338 344
339 345 # Get info on line magic
340 346 lfind = ip._ofind('lmagic')
341 347 info = dict(found=True, isalias=False, ismagic=True,
342 348 namespace = 'IPython internal', obj= lmagic.__wrapped__,
343 349 parent = None)
344 350 nt.assert_equal(lfind, info)
345 351
346 352 def test_ofind_cell_magic(self):
347 353 from IPython.core.magic import register_cell_magic
348 354
349 355 @register_cell_magic
350 356 def cmagic(line, cell):
351 357 "A cell magic"
352 358
353 359 # Get info on cell magic
354 360 find = ip._ofind('cmagic')
355 361 info = dict(found=True, isalias=False, ismagic=True,
356 362 namespace = 'IPython internal', obj= cmagic.__wrapped__,
357 363 parent = None)
358 364 nt.assert_equal(find, info)
359 365
360 366 def test_ofind_property_with_error(self):
361 367 class A(object):
362 368 @property
363 369 def foo(self):
364 370 raise NotImplementedError()
365 371 a = A()
366 372
367 373 found = ip._ofind('a.foo', [('locals', locals())])
368 374 info = dict(found=True, isalias=False, ismagic=False,
369 375 namespace='locals', obj=A.foo, parent=a)
370 376 nt.assert_equal(found, info)
371 377
372 378 def test_ofind_multiple_attribute_lookups(self):
373 379 class A(object):
374 380 @property
375 381 def foo(self):
376 382 raise NotImplementedError()
377 383
378 384 a = A()
379 385 a.a = A()
380 386 a.a.a = A()
381 387
382 388 found = ip._ofind('a.a.a.foo', [('locals', locals())])
383 389 info = dict(found=True, isalias=False, ismagic=False,
384 390 namespace='locals', obj=A.foo, parent=a.a.a)
385 391 nt.assert_equal(found, info)
386 392
387 393 def test_ofind_slotted_attributes(self):
388 394 class A(object):
389 395 __slots__ = ['foo']
390 396 def __init__(self):
391 397 self.foo = 'bar'
392 398
393 399 a = A()
394 400 found = ip._ofind('a.foo', [('locals', locals())])
395 401 info = dict(found=True, isalias=False, ismagic=False,
396 402 namespace='locals', obj=a.foo, parent=a)
397 403 nt.assert_equal(found, info)
398 404
399 405 found = ip._ofind('a.bar', [('locals', locals())])
400 406 info = dict(found=False, isalias=False, ismagic=False,
401 407 namespace=None, obj=None, parent=a)
402 408 nt.assert_equal(found, info)
403 409
404 410 def test_ofind_prefers_property_to_instance_level_attribute(self):
405 411 class A(object):
406 412 @property
407 413 def foo(self):
408 414 return 'bar'
409 415 a = A()
410 416 a.__dict__['foo'] = 'baz'
411 417 nt.assert_equal(a.foo, 'bar')
412 418 found = ip._ofind('a.foo', [('locals', locals())])
413 419 nt.assert_is(found['obj'], A.foo)
414 420
415 421 def test_custom_syntaxerror_exception(self):
416 422 called = []
417 423 def my_handler(shell, etype, value, tb, tb_offset=None):
418 424 called.append(etype)
419 425 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
420 426
421 427 ip.set_custom_exc((SyntaxError,), my_handler)
422 428 try:
423 429 ip.run_cell("1f")
424 430 # Check that this was called, and only once.
425 431 self.assertEqual(called, [SyntaxError])
426 432 finally:
427 433 # Reset the custom exception hook
428 434 ip.set_custom_exc((), None)
429 435
430 436 def test_custom_exception(self):
431 437 called = []
432 438 def my_handler(shell, etype, value, tb, tb_offset=None):
433 439 called.append(etype)
434 440 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
435 441
436 442 ip.set_custom_exc((ValueError,), my_handler)
437 443 try:
438 444 res = ip.run_cell("raise ValueError('test')")
439 445 # Check that this was called, and only once.
440 446 self.assertEqual(called, [ValueError])
441 447 # Check that the error is on the result object
442 448 self.assertIsInstance(res.error_in_exec, ValueError)
443 449 finally:
444 450 # Reset the custom exception hook
445 451 ip.set_custom_exc((), None)
446 452
447 453 def test_mktempfile(self):
448 454 filename = ip.mktempfile()
449 455 # Check that we can open the file again on Windows
450 456 with open(filename, 'w') as f:
451 457 f.write('abc')
452 458
453 459 filename = ip.mktempfile(data='blah')
454 460 with open(filename, 'r') as f:
455 461 self.assertEqual(f.read(), 'blah')
456 462
457 463 def test_new_main_mod(self):
458 464 # Smoketest to check that this accepts a unicode module name
459 465 name = u'jiefmw'
460 466 mod = ip.new_main_mod(u'%s.py' % name, name)
461 467 self.assertEqual(mod.__name__, name)
462 468
463 469 def test_get_exception_only(self):
464 470 try:
465 471 raise KeyboardInterrupt
466 472 except KeyboardInterrupt:
467 473 msg = ip.get_exception_only()
468 474 self.assertEqual(msg, 'KeyboardInterrupt\n')
469 475
470 476 try:
471 477 raise DerivedInterrupt("foo")
472 478 except KeyboardInterrupt:
473 479 msg = ip.get_exception_only()
474 480 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
475 481
476 482 def test_inspect_text(self):
477 483 ip.run_cell('a = 5')
478 484 text = ip.object_inspect_text('a')
479 485 self.assertIsInstance(text, str)
480 486
481 487 def test_last_execution_result(self):
482 488 """ Check that last execution result gets set correctly (GH-10702) """
483 489 result = ip.run_cell('a = 5; a')
484 490 self.assertTrue(ip.last_execution_succeeded)
485 491 self.assertEqual(ip.last_execution_result.result, 5)
486 492
487 493 result = ip.run_cell('a = x_invalid_id_x')
488 494 self.assertFalse(ip.last_execution_succeeded)
489 495 self.assertFalse(ip.last_execution_result.success)
490 496 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
491 497
492 498
493 499 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
494 500
495 501 @onlyif_unicode_paths
496 502 def setUp(self):
497 503 self.BASETESTDIR = tempfile.mkdtemp()
498 504 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
499 505 os.mkdir(self.TESTDIR)
500 506 with open(join(self.TESTDIR, u"Γ₯Àâtestscript.py"), "w") as sfile:
501 507 sfile.write("pass\n")
502 508 self.oldpath = os.getcwd()
503 509 os.chdir(self.TESTDIR)
504 510 self.fname = u"Γ₯Àâtestscript.py"
505 511
506 512 def tearDown(self):
507 513 os.chdir(self.oldpath)
508 514 shutil.rmtree(self.BASETESTDIR)
509 515
510 516 @onlyif_unicode_paths
511 517 def test_1(self):
512 518 """Test safe_execfile with non-ascii path
513 519 """
514 520 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
515 521
516 522 class ExitCodeChecks(tt.TempFileMixin):
517 523 def test_exit_code_ok(self):
518 524 self.system('exit 0')
519 525 self.assertEqual(ip.user_ns['_exit_code'], 0)
520 526
521 527 def test_exit_code_error(self):
522 528 self.system('exit 1')
523 529 self.assertEqual(ip.user_ns['_exit_code'], 1)
524 530
525 531 @skipif(not hasattr(signal, 'SIGALRM'))
526 532 def test_exit_code_signal(self):
527 533 self.mktmp("import signal, time\n"
528 534 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
529 535 "time.sleep(1)\n")
530 536 self.system("%s %s" % (sys.executable, self.fname))
531 537 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
532 538
533 539 @onlyif_cmds_exist("csh")
534 540 def test_exit_code_signal_csh(self):
535 541 SHELL = os.environ.get('SHELL', None)
536 542 os.environ['SHELL'] = find_cmd("csh")
537 543 try:
538 544 self.test_exit_code_signal()
539 545 finally:
540 546 if SHELL is not None:
541 547 os.environ['SHELL'] = SHELL
542 548 else:
543 549 del os.environ['SHELL']
544 550
545 551 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
546 552 system = ip.system_raw
547 553
548 554 @onlyif_unicode_paths
549 555 def test_1(self):
550 556 """Test system_raw with non-ascii cmd
551 557 """
552 558 cmd = u'''python -c "'Γ₯Àâ'" '''
553 559 ip.system_raw(cmd)
554 560
555 561 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
556 562 @mock.patch('os.system', side_effect=KeyboardInterrupt)
557 563 def test_control_c(self, *mocks):
558 564 try:
559 565 self.system("sleep 1 # wont happen")
560 566 except KeyboardInterrupt:
561 567 self.fail("system call should intercept "
562 568 "keyboard interrupt from subprocess.call")
563 569 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGINT)
564 570
565 571 # TODO: Exit codes are currently ignored on Windows.
566 572 class TestSystemPipedExitCode(unittest.TestCase, ExitCodeChecks):
567 573 system = ip.system_piped
568 574
569 575 @skip_win32
570 576 def test_exit_code_ok(self):
571 577 ExitCodeChecks.test_exit_code_ok(self)
572 578
573 579 @skip_win32
574 580 def test_exit_code_error(self):
575 581 ExitCodeChecks.test_exit_code_error(self)
576 582
577 583 @skip_win32
578 584 def test_exit_code_signal(self):
579 585 ExitCodeChecks.test_exit_code_signal(self)
580 586
581 587 class TestModules(unittest.TestCase, tt.TempFileMixin):
582 588 def test_extraneous_loads(self):
583 589 """Test we're not loading modules on startup that we shouldn't.
584 590 """
585 591 self.mktmp("import sys\n"
586 592 "print('numpy' in sys.modules)\n"
587 593 "print('ipyparallel' in sys.modules)\n"
588 594 "print('ipykernel' in sys.modules)\n"
589 595 )
590 596 out = "False\nFalse\nFalse\n"
591 597 tt.ipexec_validate(self.fname, out)
592 598
593 599 class Negator(ast.NodeTransformer):
594 600 """Negates all number literals in an AST."""
595 601 def visit_Num(self, node):
596 602 node.n = -node.n
597 603 return node
598 604
599 605 class TestAstTransform(unittest.TestCase):
600 606 def setUp(self):
601 607 self.negator = Negator()
602 608 ip.ast_transformers.append(self.negator)
603 609
604 610 def tearDown(self):
605 611 ip.ast_transformers.remove(self.negator)
606 612
607 613 def test_run_cell(self):
608 614 with tt.AssertPrints('-34'):
609 615 ip.run_cell('print (12 + 22)')
610 616
611 617 # A named reference to a number shouldn't be transformed.
612 618 ip.user_ns['n'] = 55
613 619 with tt.AssertNotPrints('-55'):
614 620 ip.run_cell('print (n)')
615 621
616 622 def test_timeit(self):
617 623 called = set()
618 624 def f(x):
619 625 called.add(x)
620 626 ip.push({'f':f})
621 627
622 628 with tt.AssertPrints("std. dev. of"):
623 629 ip.run_line_magic("timeit", "-n1 f(1)")
624 630 self.assertEqual(called, {-1})
625 631 called.clear()
626 632
627 633 with tt.AssertPrints("std. dev. of"):
628 634 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
629 635 self.assertEqual(called, {-2, -3})
630 636
631 637 def test_time(self):
632 638 called = []
633 639 def f(x):
634 640 called.append(x)
635 641 ip.push({'f':f})
636 642
637 643 # Test with an expression
638 644 with tt.AssertPrints("Wall time: "):
639 645 ip.run_line_magic("time", "f(5+9)")
640 646 self.assertEqual(called, [-14])
641 647 called[:] = []
642 648
643 649 # Test with a statement (different code path)
644 650 with tt.AssertPrints("Wall time: "):
645 651 ip.run_line_magic("time", "a = f(-3 + -2)")
646 652 self.assertEqual(called, [5])
647 653
648 654 def test_macro(self):
649 655 ip.push({'a':10})
650 656 # The AST transformation makes this do a+=-1
651 657 ip.define_macro("amacro", "a+=1\nprint(a)")
652 658
653 659 with tt.AssertPrints("9"):
654 660 ip.run_cell("amacro")
655 661 with tt.AssertPrints("8"):
656 662 ip.run_cell("amacro")
657 663
658 664 class IntegerWrapper(ast.NodeTransformer):
659 665 """Wraps all integers in a call to Integer()"""
660 666 def visit_Num(self, node):
661 667 if isinstance(node.n, int):
662 668 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
663 669 args=[node], keywords=[])
664 670 return node
665 671
666 672 class TestAstTransform2(unittest.TestCase):
667 673 def setUp(self):
668 674 self.intwrapper = IntegerWrapper()
669 675 ip.ast_transformers.append(self.intwrapper)
670 676
671 677 self.calls = []
672 678 def Integer(*args):
673 679 self.calls.append(args)
674 680 return args
675 681 ip.push({"Integer": Integer})
676 682
677 683 def tearDown(self):
678 684 ip.ast_transformers.remove(self.intwrapper)
679 685 del ip.user_ns['Integer']
680 686
681 687 def test_run_cell(self):
682 688 ip.run_cell("n = 2")
683 689 self.assertEqual(self.calls, [(2,)])
684 690
685 691 # This shouldn't throw an error
686 692 ip.run_cell("o = 2.0")
687 693 self.assertEqual(ip.user_ns['o'], 2.0)
688 694
689 695 def test_timeit(self):
690 696 called = set()
691 697 def f(x):
692 698 called.add(x)
693 699 ip.push({'f':f})
694 700
695 701 with tt.AssertPrints("std. dev. of"):
696 702 ip.run_line_magic("timeit", "-n1 f(1)")
697 703 self.assertEqual(called, {(1,)})
698 704 called.clear()
699 705
700 706 with tt.AssertPrints("std. dev. of"):
701 707 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
702 708 self.assertEqual(called, {(2,), (3,)})
703 709
704 710 class ErrorTransformer(ast.NodeTransformer):
705 711 """Throws an error when it sees a number."""
706 712 def visit_Num(self, node):
707 713 raise ValueError("test")
708 714
709 715 class TestAstTransformError(unittest.TestCase):
710 716 def test_unregistering(self):
711 717 err_transformer = ErrorTransformer()
712 718 ip.ast_transformers.append(err_transformer)
713 719
714 720 with tt.AssertPrints("unregister", channel='stderr'):
715 721 ip.run_cell("1 + 2")
716 722
717 723 # This should have been removed.
718 724 nt.assert_not_in(err_transformer, ip.ast_transformers)
719 725
720 726
721 727 class StringRejector(ast.NodeTransformer):
722 728 """Throws an InputRejected when it sees a string literal.
723 729
724 730 Used to verify that NodeTransformers can signal that a piece of code should
725 731 not be executed by throwing an InputRejected.
726 732 """
727 733
728 734 def visit_Str(self, node):
729 735 raise InputRejected("test")
730 736
731 737
732 738 class TestAstTransformInputRejection(unittest.TestCase):
733 739
734 740 def setUp(self):
735 741 self.transformer = StringRejector()
736 742 ip.ast_transformers.append(self.transformer)
737 743
738 744 def tearDown(self):
739 745 ip.ast_transformers.remove(self.transformer)
740 746
741 747 def test_input_rejection(self):
742 748 """Check that NodeTransformers can reject input."""
743 749
744 750 expect_exception_tb = tt.AssertPrints("InputRejected: test")
745 751 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
746 752
747 753 # Run the same check twice to verify that the transformer is not
748 754 # disabled after raising.
749 755 with expect_exception_tb, expect_no_cell_output:
750 756 ip.run_cell("'unsafe'")
751 757
752 758 with expect_exception_tb, expect_no_cell_output:
753 759 res = ip.run_cell("'unsafe'")
754 760
755 761 self.assertIsInstance(res.error_before_exec, InputRejected)
756 762
757 763 def test__IPYTHON__():
758 764 # This shouldn't raise a NameError, that's all
759 765 __IPYTHON__
760 766
761 767
762 768 class DummyRepr(object):
763 769 def __repr__(self):
764 770 return "DummyRepr"
765 771
766 772 def _repr_html_(self):
767 773 return "<b>dummy</b>"
768 774
769 775 def _repr_javascript_(self):
770 776 return "console.log('hi');", {'key': 'value'}
771 777
772 778
773 779 def test_user_variables():
774 780 # enable all formatters
775 781 ip.display_formatter.active_types = ip.display_formatter.format_types
776 782
777 783 ip.user_ns['dummy'] = d = DummyRepr()
778 784 keys = {'dummy', 'doesnotexist'}
779 785 r = ip.user_expressions({ key:key for key in keys})
780 786
781 787 nt.assert_equal(keys, set(r.keys()))
782 788 dummy = r['dummy']
783 789 nt.assert_equal({'status', 'data', 'metadata'}, set(dummy.keys()))
784 790 nt.assert_equal(dummy['status'], 'ok')
785 791 data = dummy['data']
786 792 metadata = dummy['metadata']
787 793 nt.assert_equal(data.get('text/html'), d._repr_html_())
788 794 js, jsmd = d._repr_javascript_()
789 795 nt.assert_equal(data.get('application/javascript'), js)
790 796 nt.assert_equal(metadata.get('application/javascript'), jsmd)
791 797
792 798 dne = r['doesnotexist']
793 799 nt.assert_equal(dne['status'], 'error')
794 800 nt.assert_equal(dne['ename'], 'NameError')
795 801
796 802 # back to text only
797 803 ip.display_formatter.active_types = ['text/plain']
798 804
799 805 def test_user_expression():
800 806 # enable all formatters
801 807 ip.display_formatter.active_types = ip.display_formatter.format_types
802 808 query = {
803 809 'a' : '1 + 2',
804 810 'b' : '1/0',
805 811 }
806 812 r = ip.user_expressions(query)
807 813 import pprint
808 814 pprint.pprint(r)
809 815 nt.assert_equal(set(r.keys()), set(query.keys()))
810 816 a = r['a']
811 817 nt.assert_equal({'status', 'data', 'metadata'}, set(a.keys()))
812 818 nt.assert_equal(a['status'], 'ok')
813 819 data = a['data']
814 820 metadata = a['metadata']
815 821 nt.assert_equal(data.get('text/plain'), '3')
816 822
817 823 b = r['b']
818 824 nt.assert_equal(b['status'], 'error')
819 825 nt.assert_equal(b['ename'], 'ZeroDivisionError')
820 826
821 827 # back to text only
822 828 ip.display_formatter.active_types = ['text/plain']
823 829
824 830
825 831
826 832
827 833
828 834 class TestSyntaxErrorTransformer(unittest.TestCase):
829 835 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
830 836
831 837 class SyntaxErrorTransformer(InputTransformer):
832 838
833 839 def push(self, line):
834 840 pos = line.find('syntaxerror')
835 841 if pos >= 0:
836 842 e = SyntaxError('input contains "syntaxerror"')
837 843 e.text = line
838 844 e.offset = pos + 1
839 845 raise e
840 846 return line
841 847
842 848 def reset(self):
843 849 pass
844 850
845 851 def setUp(self):
846 852 self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
847 853 ip.input_splitter.python_line_transforms.append(self.transformer)
848 854 ip.input_transformer_manager.python_line_transforms.append(self.transformer)
849 855
850 856 def tearDown(self):
851 857 ip.input_splitter.python_line_transforms.remove(self.transformer)
852 858 ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
853 859
854 860 def test_syntaxerror_input_transformer(self):
855 861 with tt.AssertPrints('1234'):
856 862 ip.run_cell('1234')
857 863 with tt.AssertPrints('SyntaxError: invalid syntax'):
858 864 ip.run_cell('1 2 3') # plain python syntax error
859 865 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
860 866 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
861 867 with tt.AssertPrints('3456'):
862 868 ip.run_cell('3456')
863 869
864 870
865 871
866 872 def test_warning_suppression():
867 873 ip.run_cell("import warnings")
868 874 try:
869 875 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
870 876 ip.run_cell("warnings.warn('asdf')")
871 877 # Here's the real test -- if we run that again, we should get the
872 878 # warning again. Traditionally, each warning was only issued once per
873 879 # IPython session (approximately), even if the user typed in new and
874 880 # different code that should have also triggered the warning, leading
875 881 # to much confusion.
876 882 with tt.AssertPrints("UserWarning: asdf", channel="stderr"):
877 883 ip.run_cell("warnings.warn('asdf')")
878 884 finally:
879 885 ip.run_cell("del warnings")
880 886
881 887
882 888 def test_deprecation_warning():
883 889 ip.run_cell("""
884 890 import warnings
885 891 def wrn():
886 892 warnings.warn(
887 893 "I AM A WARNING",
888 894 DeprecationWarning
889 895 )
890 896 """)
891 897 try:
892 898 with tt.AssertPrints("I AM A WARNING", channel="stderr"):
893 899 ip.run_cell("wrn()")
894 900 finally:
895 901 ip.run_cell("del warnings")
896 902 ip.run_cell("del wrn")
897 903
898 904
899 905 class TestImportNoDeprecate(tt.TempFileMixin):
900 906
901 907 def setup(self):
902 908 """Make a valid python temp file."""
903 909 self.mktmp("""
904 910 import warnings
905 911 def wrn():
906 912 warnings.warn(
907 913 "I AM A WARNING",
908 914 DeprecationWarning
909 915 )
910 916 """)
911 917
912 918 def test_no_dep(self):
913 919 """
914 920 No deprecation warning should be raised from imported functions
915 921 """
916 922 ip.run_cell("from {} import wrn".format(self.fname))
917 923
918 924 with tt.AssertNotPrints("I AM A WARNING"):
919 925 ip.run_cell("wrn()")
920 926 ip.run_cell("del wrn")
@@ -1,94 +1,94 b''
1 1 .. _events:
2 2 .. _callbacks:
3 3
4 4 ==============
5 5 IPython Events
6 6 ==============
7 7
8 8 Extension code can register callbacks functions which will be called on specific
9 9 events within the IPython code. You can see the current list of available
10 10 callbacks, and the parameters that will be passed with each, in the callback
11 11 prototype functions defined in :mod:`IPython.core.callbacks`.
12 12
13 13 To register callbacks, use :meth:`IPython.core.events.EventManager.register`.
14 14 For example::
15 15
16 16 class VarWatcher(object):
17 17 def __init__(self, ip):
18 18 self.shell = ip
19 19 self.last_x = None
20 20
21 def pre_execute(self):
21 def pre_execute(self, request):
22 print('Cell code: "%s"' % request.raw_cell)
22 23 self.last_x = self.shell.user_ns.get('x', None)
23 24
24 25 def post_execute(self, result):
26 print('Cell code: "%s"' % result.request.raw_cell)
25 27 if result.error_before_exec:
26 28 print('Error before execution: %s' % result.error_before_exec)
27 29 if self.shell.user_ns.get('x', None) != self.last_x:
28 30 print("x changed!")
29 31
30 32 def load_ipython_extension(ip):
31 33 vw = VarWatcher(ip)
32 34 ip.events.register('pre_execute', vw.pre_execute)
33 35 ip.events.register('post_execute', vw.post_execute)
34 36
35 37
36 38 Events
37 39 ======
38 40
39 41 These are the events IPython will emit. Callbacks will be passed no arguments, unless otherwise specified.
40 42
41 43 shell_initialized
42 44 -----------------
43 45
44 46 .. code-block:: python
45 47
46 48 def shell_initialized(ipython):
47 49 ...
48 50
49 51 This event is triggered only once, at the end of setting up IPython.
50 52 Extensions registered to load by default as part of configuration can use this to execute code to finalize setup.
51 53 Callbacks will be passed the InteractiveShell instance.
52 54
53 55 pre_run_cell
54 56 ------------
55 57
56 58 ``pre_run_cell`` fires prior to interactive execution (e.g. a cell in a notebook).
57 59 It can be used to note the state prior to execution, and keep track of changes.
58 The object which will be returned as the execution result is provided as an
59 argument, even though the actual result is not yet available.
60 The object representing the code execution request is provided as an argument.
60 61
61 62 pre_execute
62 63 -----------
63 64
64 65 ``pre_execute`` is like ``pre_run_cell``, but is triggered prior to *any* execution.
65 66 Sometimes code can be executed by libraries, etc. which
66 67 skipping the history/display mechanisms, in which cases ``pre_run_cell`` will not fire.
67 The object which will be returned as the execution result is provided as an
68 argument, even though the actual result is not yet available.
68 The object representing the code execution request is provided as an argument.
69 69
70 70 post_run_cell
71 71 -------------
72 72
73 73 ``post_run_cell`` runs after interactive execution (e.g. a cell in a notebook).
74 74 It can be used to cleanup or notify or perform operations on any side effects produced during execution.
75 75 For instance, the inline matplotlib backend uses this event to display any figures created but not explicitly displayed during the course of the cell.
76 76 The object which will be returned as the execution result is provided as an
77 77 argument.
78 78
79 79 post_execute
80 80 ------------
81 81
82 82 The same as ``pre_execute``, ``post_execute`` is like ``post_run_cell``,
83 83 but fires for *all* executions, not just interactive ones.
84 84
85 85
86 86 .. seealso::
87 87
88 88 Module :mod:`IPython.core.hooks`
89 89 The older 'hooks' system allows end users to customise some parts of
90 90 IPython's behaviour.
91 91
92 92 :doc:`inputtransforms`
93 93 By registering input transformers that don't change code, you can monitor
94 94 what is being executed.
@@ -1,6 +1,7 b''
1 1 The *post* event callbacks are now always called, even when the execution failed
2 2 (for example because of a ``SyntaxError``).
3 Additionally, the execution result object is now made available in both *pre*
4 and *post* event callbacks in a backward compatible manner.
3 Additionally, the execution request and result objects are now made available in
4 the corresponding *pre* or *post* event callbacks in a backward compatible
5 manner.
5 6
6 7 * `Related GitHub issue <https://github.com/ipython/ipython/issues/10774>`__
@@ -1,265 +1,266 b''
1 1 #!/usr/bin/env python
2 2 # -*- coding: utf-8 -*-
3 3 """Setup script for IPython.
4 4
5 5 Under Posix environments it works like a typical setup.py script.
6 6 Under Windows, the command sdist is not supported, since IPython
7 7 requires utilities which are not available under Windows."""
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (c) 2008-2011, IPython Development Team.
11 11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 14 #
15 15 # Distributed under the terms of the Modified BSD License.
16 16 #
17 17 # The full license is in the file COPYING.rst, distributed with this software.
18 18 #-----------------------------------------------------------------------------
19 19
20 20 from __future__ import print_function
21 21
22 22 import os
23 23 import sys
24 24
25 25 # **Python version check**
26 26 #
27 27 # This check is also made in IPython/__init__, don't forget to update both when
28 28 # changing Python version requirements.
29 29 if sys.version_info < (3, 3):
30 30 pip_message = 'This may be due to an out of date pip. Make sure you have pip >= 9.0.1.'
31 31 try:
32 32 import pip
33 33 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
34 34 if pip_version < (9, 0, 1) :
35 35 pip_message = 'Your pip version is out of date, please install pip >= 9.0.1. '\
36 36 'pip {} detected.'.format(pip.__version__)
37 37 else:
38 38 # pip is new enough - it must be something else
39 39 pip_message = ''
40 40 except Exception:
41 41 pass
42 42
43 43
44 44 error = """
45 45 IPython 6.0+ does not support Python 2.6, 2.7, 3.0, 3.1, or 3.2.
46 46 When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
47 47 Beginning with IPython 6.0, Python 3.3 and above is required.
48 48
49 49 See IPython `README.rst` file for more information:
50 50
51 51 https://github.com/ipython/ipython/blob/master/README.rst
52 52
53 53 Python {py} detected.
54 54 {pip}
55 55 """.format(py=sys.version_info, pip=pip_message )
56 56
57 57 print(error, file=sys.stderr)
58 58 sys.exit(1)
59 59
60 60 # At least we're on the python version we need, move on.
61 61
62 62 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
63 63 # update it when the contents of directories change.
64 64 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
65 65
66 66 from distutils.core import setup
67 67
68 68 # Our own imports
69 69 from setupbase import target_update
70 70
71 71 from setupbase import (
72 72 setup_args,
73 73 find_packages,
74 74 find_package_data,
75 75 check_package_data_first,
76 76 find_entry_points,
77 77 build_scripts_entrypt,
78 78 find_data_files,
79 79 git_prebuild,
80 80 install_symlinked,
81 81 install_lib_symlink,
82 82 install_scripts_for_symlink,
83 83 unsymlink,
84 84 )
85 85
86 86 isfile = os.path.isfile
87 87 pjoin = os.path.join
88 88
89 89 #-------------------------------------------------------------------------------
90 90 # Handle OS specific things
91 91 #-------------------------------------------------------------------------------
92 92
93 93 if os.name in ('nt','dos'):
94 94 os_name = 'windows'
95 95 else:
96 96 os_name = os.name
97 97
98 98 # Under Windows, 'sdist' has not been supported. Now that the docs build with
99 99 # Sphinx it might work, but let's not turn it on until someone confirms that it
100 100 # actually works.
101 101 if os_name == 'windows' and 'sdist' in sys.argv:
102 102 print('The sdist command is not available under Windows. Exiting.')
103 103 sys.exit(1)
104 104
105 105
106 106 #-------------------------------------------------------------------------------
107 107 # Things related to the IPython documentation
108 108 #-------------------------------------------------------------------------------
109 109
110 110 # update the manuals when building a source dist
111 111 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
112 112
113 113 # List of things to be updated. Each entry is a triplet of args for
114 114 # target_update()
115 115 to_update = [
116 116 ('docs/man/ipython.1.gz',
117 117 ['docs/man/ipython.1'],
118 118 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
119 119 ]
120 120
121 121
122 122 [ target_update(*t) for t in to_update ]
123 123
124 124 #---------------------------------------------------------------------------
125 125 # Find all the packages, package data, and data_files
126 126 #---------------------------------------------------------------------------
127 127
128 128 packages = find_packages()
129 129 package_data = find_package_data()
130 130
131 131 data_files = find_data_files()
132 132
133 133 setup_args['packages'] = packages
134 134 setup_args['package_data'] = package_data
135 135 setup_args['data_files'] = data_files
136 136
137 137 #---------------------------------------------------------------------------
138 138 # custom distutils commands
139 139 #---------------------------------------------------------------------------
140 140 # imports here, so they are after setuptools import if there was one
141 141 from distutils.command.sdist import sdist
142 142
143 143 setup_args['cmdclass'] = {
144 144 'build_py': \
145 145 check_package_data_first(git_prebuild('IPython')),
146 146 'sdist' : git_prebuild('IPython', sdist),
147 147 'symlink': install_symlinked,
148 148 'install_lib_symlink': install_lib_symlink,
149 149 'install_scripts_sym': install_scripts_for_symlink,
150 150 'unsymlink': unsymlink,
151 151 }
152 152
153 153
154 154 #---------------------------------------------------------------------------
155 155 # Handle scripts, dependencies, and setuptools specific things
156 156 #---------------------------------------------------------------------------
157 157
158 158 # For some commands, use setuptools. Note that we do NOT list install here!
159 159 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
160 160 needs_setuptools = {'develop', 'release', 'bdist_egg', 'bdist_rpm',
161 161 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
162 162 'egg_info', 'easy_install', 'upload', 'install_egg_info',
163 163 }
164 164
165 165 if len(needs_setuptools.intersection(sys.argv)) > 0:
166 166 import setuptools
167 167
168 168 # This dict is used for passing extra arguments that are setuptools
169 169 # specific to setup
170 170 setuptools_extra_args = {}
171 171
172 172 # setuptools requirements
173 173
174 174 extras_require = dict(
175 175 parallel = ['ipyparallel'],
176 176 qtconsole = ['qtconsole'],
177 177 doc = ['Sphinx>=1.3'],
178 178 test = ['nose>=0.10.1', 'requests', 'testpath', 'pygments', 'nbformat', 'ipykernel'],
179 179 terminal = [],
180 180 kernel = ['ipykernel'],
181 181 nbformat = ['nbformat'],
182 182 notebook = ['notebook', 'ipywidgets'],
183 183 nbconvert = ['nbconvert'],
184 184 )
185 185
186 186 install_requires = [
187 187 'setuptools>=18.5',
188 188 'jedi>=0.10',
189 189 'decorator',
190 190 'pickleshare',
191 191 'simplegeneric>0.8',
192 192 'traitlets>=4.2',
193 193 'prompt_toolkit>=1.0.4,<2.0.0',
194 194 'pygments',
195 'backcall',
195 196 ]
196 197
197 198 # Platform-specific dependencies:
198 199 # This is the correct way to specify these,
199 200 # but requires pip >= 6. pip < 6 ignores these.
200 201
201 202 extras_require.update({
202 203 'test:python_version >= "3.4"': ['numpy'],
203 204 ':python_version == "3.3"': ['pathlib2'],
204 205 ':python_version <= "3.4"': ['typing'],
205 206 ':sys_platform != "win32"': ['pexpect'],
206 207 ':sys_platform == "darwin"': ['appnope'],
207 208 ':sys_platform == "win32"': ['colorama'],
208 209 ':sys_platform == "win32" and python_version < "3.6"': ['win_unicode_console>=0.5'],
209 210 })
210 211 # FIXME: re-specify above platform dependencies for pip < 6
211 212 # These would result in non-portable bdists.
212 213 if not any(arg.startswith('bdist') for arg in sys.argv):
213 214 if sys.platform == 'darwin':
214 215 install_requires.extend(['appnope'])
215 216
216 217 if not sys.platform.startswith('win'):
217 218 install_requires.append('pexpect')
218 219
219 220 # workaround pypa/setuptools#147, where setuptools misspells
220 221 # platform_python_implementation as python_implementation
221 222 if 'setuptools' in sys.modules:
222 223 for key in list(extras_require):
223 224 if 'platform_python_implementation' in key:
224 225 new_key = key.replace('platform_python_implementation', 'python_implementation')
225 226 extras_require[new_key] = extras_require.pop(key)
226 227
227 228 everything = set()
228 229 for key, deps in extras_require.items():
229 230 if ':' not in key:
230 231 everything.update(deps)
231 232 extras_require['all'] = everything
232 233
233 234 if 'setuptools' in sys.modules:
234 235 setuptools_extra_args['python_requires'] = '>=3.3'
235 236 setuptools_extra_args['zip_safe'] = False
236 237 setuptools_extra_args['entry_points'] = {
237 238 'console_scripts': find_entry_points(),
238 239 'pygments.lexers': [
239 240 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
240 241 'ipython = IPython.lib.lexers:IPythonLexer',
241 242 'ipython3 = IPython.lib.lexers:IPython3Lexer',
242 243 ],
243 244 }
244 245 setup_args['extras_require'] = extras_require
245 246 setup_args['install_requires'] = install_requires
246 247
247 248 else:
248 249 # scripts has to be a non-empty list, or install_scripts isn't called
249 250 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
250 251
251 252 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
252 253
253 254 #---------------------------------------------------------------------------
254 255 # Do the actual setup now
255 256 #---------------------------------------------------------------------------
256 257
257 258 setup_args.update(setuptools_extra_args)
258 259
259 260
260 261
261 262 def main():
262 263 setup(**setup_args)
263 264
264 265 if __name__ == '__main__':
265 266 main()
General Comments 0
You need to be logged in to leave comments. Login now