##// END OF EJS Templates
Convert matplotlib gui name in enable_gui
Ian Thomas -
Show More
@@ -1,1201 +1,1221 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for the key interactiveshell module.
2 """Tests for the key interactiveshell module.
3
3
4 Historically the main classes in interactiveshell have been under-tested. This
4 Historically the main classes in interactiveshell have been under-tested. This
5 module should grow as many single-method tests as possible to trap many of the
5 module should grow as many single-method tests as possible to trap many of the
6 recurring bugs we seem to encounter with high-level interaction.
6 recurring bugs we seem to encounter with high-level interaction.
7 """
7 """
8
8
9 # Copyright (c) IPython Development Team.
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
10 # Distributed under the terms of the Modified BSD License.
11
11
12 import asyncio
12 import asyncio
13 import ast
13 import ast
14 import os
14 import os
15 import signal
15 import signal
16 import shutil
16 import shutil
17 import sys
17 import sys
18 import tempfile
18 import tempfile
19 import unittest
19 import unittest
20 import pytest
20 import pytest
21 from unittest import mock
21 from unittest import mock
22
22
23 from os.path import join
23 from os.path import join
24
24
25 from IPython.core.error import InputRejected
25 from IPython.core.error import InputRejected
26 from IPython.core.inputtransformer import InputTransformer
26 from IPython.core.inputtransformer import InputTransformer
27 from IPython.core import interactiveshell
27 from IPython.core import interactiveshell
28 from IPython.core.oinspect import OInfo
28 from IPython.core.oinspect import OInfo
29 from IPython.testing.decorators import (
29 from IPython.testing.decorators import (
30 skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
30 skipif,
31 skip_win32,
32 onlyif_unicode_paths,
33 onlyif_cmds_exist,
34 skip_if_not_osx,
31 )
35 )
32 from IPython.testing import tools as tt
36 from IPython.testing import tools as tt
33 from IPython.utils.process import find_cmd
37 from IPython.utils.process import find_cmd
34
38
35 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
36 # Globals
40 # Globals
37 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
38 # This is used by every single test, no point repeating it ad nauseam
42 # This is used by every single test, no point repeating it ad nauseam
39
43
40 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
41 # Tests
45 # Tests
42 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
43
47
44 class DerivedInterrupt(KeyboardInterrupt):
48 class DerivedInterrupt(KeyboardInterrupt):
45 pass
49 pass
46
50
47 class InteractiveShellTestCase(unittest.TestCase):
51 class InteractiveShellTestCase(unittest.TestCase):
48 def test_naked_string_cells(self):
52 def test_naked_string_cells(self):
49 """Test that cells with only naked strings are fully executed"""
53 """Test that cells with only naked strings are fully executed"""
50 # First, single-line inputs
54 # First, single-line inputs
51 ip.run_cell('"a"\n')
55 ip.run_cell('"a"\n')
52 self.assertEqual(ip.user_ns['_'], 'a')
56 self.assertEqual(ip.user_ns['_'], 'a')
53 # And also multi-line cells
57 # And also multi-line cells
54 ip.run_cell('"""a\nb"""\n')
58 ip.run_cell('"""a\nb"""\n')
55 self.assertEqual(ip.user_ns['_'], 'a\nb')
59 self.assertEqual(ip.user_ns['_'], 'a\nb')
56
60
57 def test_run_empty_cell(self):
61 def test_run_empty_cell(self):
58 """Just make sure we don't get a horrible error with a blank
62 """Just make sure we don't get a horrible error with a blank
59 cell of input. Yes, I did overlook that."""
63 cell of input. Yes, I did overlook that."""
60 old_xc = ip.execution_count
64 old_xc = ip.execution_count
61 res = ip.run_cell('')
65 res = ip.run_cell('')
62 self.assertEqual(ip.execution_count, old_xc)
66 self.assertEqual(ip.execution_count, old_xc)
63 self.assertEqual(res.execution_count, None)
67 self.assertEqual(res.execution_count, None)
64
68
65 def test_run_cell_multiline(self):
69 def test_run_cell_multiline(self):
66 """Multi-block, multi-line cells must execute correctly.
70 """Multi-block, multi-line cells must execute correctly.
67 """
71 """
68 src = '\n'.join(["x=1",
72 src = '\n'.join(["x=1",
69 "y=2",
73 "y=2",
70 "if 1:",
74 "if 1:",
71 " x += 1",
75 " x += 1",
72 " y += 1",])
76 " y += 1",])
73 res = ip.run_cell(src)
77 res = ip.run_cell(src)
74 self.assertEqual(ip.user_ns['x'], 2)
78 self.assertEqual(ip.user_ns['x'], 2)
75 self.assertEqual(ip.user_ns['y'], 3)
79 self.assertEqual(ip.user_ns['y'], 3)
76 self.assertEqual(res.success, True)
80 self.assertEqual(res.success, True)
77 self.assertEqual(res.result, None)
81 self.assertEqual(res.result, None)
78
82
79 def test_multiline_string_cells(self):
83 def test_multiline_string_cells(self):
80 "Code sprinkled with multiline strings should execute (GH-306)"
84 "Code sprinkled with multiline strings should execute (GH-306)"
81 ip.run_cell('tmp=0')
85 ip.run_cell('tmp=0')
82 self.assertEqual(ip.user_ns['tmp'], 0)
86 self.assertEqual(ip.user_ns['tmp'], 0)
83 res = ip.run_cell('tmp=1;"""a\nb"""\n')
87 res = ip.run_cell('tmp=1;"""a\nb"""\n')
84 self.assertEqual(ip.user_ns['tmp'], 1)
88 self.assertEqual(ip.user_ns['tmp'], 1)
85 self.assertEqual(res.success, True)
89 self.assertEqual(res.success, True)
86 self.assertEqual(res.result, "a\nb")
90 self.assertEqual(res.result, "a\nb")
87
91
88 def test_dont_cache_with_semicolon(self):
92 def test_dont_cache_with_semicolon(self):
89 "Ending a line with semicolon should not cache the returned object (GH-307)"
93 "Ending a line with semicolon should not cache the returned object (GH-307)"
90 oldlen = len(ip.user_ns['Out'])
94 oldlen = len(ip.user_ns['Out'])
91 for cell in ['1;', '1;1;']:
95 for cell in ['1;', '1;1;']:
92 res = ip.run_cell(cell, store_history=True)
96 res = ip.run_cell(cell, store_history=True)
93 newlen = len(ip.user_ns['Out'])
97 newlen = len(ip.user_ns['Out'])
94 self.assertEqual(oldlen, newlen)
98 self.assertEqual(oldlen, newlen)
95 self.assertIsNone(res.result)
99 self.assertIsNone(res.result)
96 i = 0
100 i = 0
97 #also test the default caching behavior
101 #also test the default caching behavior
98 for cell in ['1', '1;1']:
102 for cell in ['1', '1;1']:
99 ip.run_cell(cell, store_history=True)
103 ip.run_cell(cell, store_history=True)
100 newlen = len(ip.user_ns['Out'])
104 newlen = len(ip.user_ns['Out'])
101 i += 1
105 i += 1
102 self.assertEqual(oldlen+i, newlen)
106 self.assertEqual(oldlen+i, newlen)
103
107
104 def test_syntax_error(self):
108 def test_syntax_error(self):
105 res = ip.run_cell("raise = 3")
109 res = ip.run_cell("raise = 3")
106 self.assertIsInstance(res.error_before_exec, SyntaxError)
110 self.assertIsInstance(res.error_before_exec, SyntaxError)
107
111
108 def test_open_standard_input_stream(self):
112 def test_open_standard_input_stream(self):
109 res = ip.run_cell("open(0)")
113 res = ip.run_cell("open(0)")
110 self.assertIsInstance(res.error_in_exec, ValueError)
114 self.assertIsInstance(res.error_in_exec, ValueError)
111
115
112 def test_open_standard_output_stream(self):
116 def test_open_standard_output_stream(self):
113 res = ip.run_cell("open(1)")
117 res = ip.run_cell("open(1)")
114 self.assertIsInstance(res.error_in_exec, ValueError)
118 self.assertIsInstance(res.error_in_exec, ValueError)
115
119
116 def test_open_standard_error_stream(self):
120 def test_open_standard_error_stream(self):
117 res = ip.run_cell("open(2)")
121 res = ip.run_cell("open(2)")
118 self.assertIsInstance(res.error_in_exec, ValueError)
122 self.assertIsInstance(res.error_in_exec, ValueError)
119
123
120 def test_In_variable(self):
124 def test_In_variable(self):
121 "Verify that In variable grows with user input (GH-284)"
125 "Verify that In variable grows with user input (GH-284)"
122 oldlen = len(ip.user_ns['In'])
126 oldlen = len(ip.user_ns['In'])
123 ip.run_cell('1;', store_history=True)
127 ip.run_cell('1;', store_history=True)
124 newlen = len(ip.user_ns['In'])
128 newlen = len(ip.user_ns['In'])
125 self.assertEqual(oldlen+1, newlen)
129 self.assertEqual(oldlen+1, newlen)
126 self.assertEqual(ip.user_ns['In'][-1],'1;')
130 self.assertEqual(ip.user_ns['In'][-1],'1;')
127
131
128 def test_magic_names_in_string(self):
132 def test_magic_names_in_string(self):
129 ip.run_cell('a = """\n%exit\n"""')
133 ip.run_cell('a = """\n%exit\n"""')
130 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
134 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
131
135
132 def test_trailing_newline(self):
136 def test_trailing_newline(self):
133 """test that running !(command) does not raise a SyntaxError"""
137 """test that running !(command) does not raise a SyntaxError"""
134 ip.run_cell('!(true)\n', False)
138 ip.run_cell('!(true)\n', False)
135 ip.run_cell('!(true)\n\n\n', False)
139 ip.run_cell('!(true)\n\n\n', False)
136
140
137 def test_gh_597(self):
141 def test_gh_597(self):
138 """Pretty-printing lists of objects with non-ascii reprs may cause
142 """Pretty-printing lists of objects with non-ascii reprs may cause
139 problems."""
143 problems."""
140 class Spam(object):
144 class Spam(object):
141 def __repr__(self):
145 def __repr__(self):
142 return "\xe9"*50
146 return "\xe9"*50
143 import IPython.core.formatters
147 import IPython.core.formatters
144 f = IPython.core.formatters.PlainTextFormatter()
148 f = IPython.core.formatters.PlainTextFormatter()
145 f([Spam(),Spam()])
149 f([Spam(), Spam()])
146
147
150
148 def test_future_flags(self):
151 def test_future_flags(self):
149 """Check that future flags are used for parsing code (gh-777)"""
152 """Check that future flags are used for parsing code (gh-777)"""
150 ip.run_cell('from __future__ import barry_as_FLUFL')
153 ip.run_cell('from __future__ import barry_as_FLUFL')
151 try:
154 try:
152 ip.run_cell('prfunc_return_val = 1 <> 2')
155 ip.run_cell('prfunc_return_val = 1 <> 2')
153 assert 'prfunc_return_val' in ip.user_ns
156 assert 'prfunc_return_val' in ip.user_ns
154 finally:
157 finally:
155 # Reset compiler flags so we don't mess up other tests.
158 # Reset compiler flags so we don't mess up other tests.
156 ip.compile.reset_compiler_flags()
159 ip.compile.reset_compiler_flags()
157
160
158 def test_can_pickle(self):
161 def test_can_pickle(self):
159 "Can we pickle objects defined interactively (GH-29)"
162 "Can we pickle objects defined interactively (GH-29)"
160 ip = get_ipython()
163 ip = get_ipython()
161 ip.reset()
164 ip.reset()
162 ip.run_cell(("class Mylist(list):\n"
165 ip.run_cell(("class Mylist(list):\n"
163 " def __init__(self,x=[]):\n"
166 " def __init__(self,x=[]):\n"
164 " list.__init__(self,x)"))
167 " list.__init__(self,x)"))
165 ip.run_cell("w=Mylist([1,2,3])")
168 ip.run_cell("w=Mylist([1,2,3])")
166
169
167 from pickle import dumps
170 from pickle import dumps
168
171
169 # We need to swap in our main module - this is only necessary
172 # We need to swap in our main module - this is only necessary
170 # inside the test framework, because IPython puts the interactive module
173 # inside the test framework, because IPython puts the interactive module
171 # in place (but the test framework undoes this).
174 # in place (but the test framework undoes this).
172 _main = sys.modules['__main__']
175 _main = sys.modules['__main__']
173 sys.modules['__main__'] = ip.user_module
176 sys.modules['__main__'] = ip.user_module
174 try:
177 try:
175 res = dumps(ip.user_ns["w"])
178 res = dumps(ip.user_ns["w"])
176 finally:
179 finally:
177 sys.modules['__main__'] = _main
180 sys.modules['__main__'] = _main
178 self.assertTrue(isinstance(res, bytes))
181 self.assertTrue(isinstance(res, bytes))
179
182
180 def test_global_ns(self):
183 def test_global_ns(self):
181 "Code in functions must be able to access variables outside them."
184 "Code in functions must be able to access variables outside them."
182 ip = get_ipython()
185 ip = get_ipython()
183 ip.run_cell("a = 10")
186 ip.run_cell("a = 10")
184 ip.run_cell(("def f(x):\n"
187 ip.run_cell(("def f(x):\n"
185 " return x + a"))
188 " return x + a"))
186 ip.run_cell("b = f(12)")
189 ip.run_cell("b = f(12)")
187 self.assertEqual(ip.user_ns["b"], 22)
190 self.assertEqual(ip.user_ns["b"], 22)
188
191
189 def test_bad_custom_tb(self):
192 def test_bad_custom_tb(self):
190 """Check that InteractiveShell is protected from bad custom exception handlers"""
193 """Check that InteractiveShell is protected from bad custom exception handlers"""
191 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
194 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
192 self.assertEqual(ip.custom_exceptions, (IOError,))
195 self.assertEqual(ip.custom_exceptions, (IOError,))
193 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
196 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
194 ip.run_cell(u'raise IOError("foo")')
197 ip.run_cell(u'raise IOError("foo")')
195 self.assertEqual(ip.custom_exceptions, ())
198 self.assertEqual(ip.custom_exceptions, ())
196
199
197 def test_bad_custom_tb_return(self):
200 def test_bad_custom_tb_return(self):
198 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
201 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
199 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
202 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
200 self.assertEqual(ip.custom_exceptions, (NameError,))
203 self.assertEqual(ip.custom_exceptions, (NameError,))
201 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
204 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
202 ip.run_cell(u'a=abracadabra')
205 ip.run_cell(u'a=abracadabra')
203 self.assertEqual(ip.custom_exceptions, ())
206 self.assertEqual(ip.custom_exceptions, ())
204
207
205 def test_drop_by_id(self):
208 def test_drop_by_id(self):
206 myvars = {"a":object(), "b":object(), "c": object()}
209 myvars = {"a":object(), "b":object(), "c": object()}
207 ip.push(myvars, interactive=False)
210 ip.push(myvars, interactive=False)
208 for name in myvars:
211 for name in myvars:
209 assert name in ip.user_ns, name
212 assert name in ip.user_ns, name
210 assert name in ip.user_ns_hidden, name
213 assert name in ip.user_ns_hidden, name
211 ip.user_ns['b'] = 12
214 ip.user_ns['b'] = 12
212 ip.drop_by_id(myvars)
215 ip.drop_by_id(myvars)
213 for name in ["a", "c"]:
216 for name in ["a", "c"]:
214 assert name not in ip.user_ns, name
217 assert name not in ip.user_ns, name
215 assert name not in ip.user_ns_hidden, name
218 assert name not in ip.user_ns_hidden, name
216 assert ip.user_ns['b'] == 12
219 assert ip.user_ns['b'] == 12
217 ip.reset()
220 ip.reset()
218
221
219 def test_var_expand(self):
222 def test_var_expand(self):
220 ip.user_ns['f'] = u'Ca\xf1o'
223 ip.user_ns['f'] = u'Ca\xf1o'
221 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
224 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
222 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
225 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
223 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
226 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
224 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
227 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
225
228
226 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
229 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
227
230
228 ip.user_ns['f'] = b'Ca\xc3\xb1o'
231 ip.user_ns['f'] = b'Ca\xc3\xb1o'
229 # This should not raise any exception:
232 # This should not raise any exception:
230 ip.var_expand(u'echo $f')
233 ip.var_expand(u'echo $f')
231
234
232 def test_var_expand_local(self):
235 def test_var_expand_local(self):
233 """Test local variable expansion in !system and %magic calls"""
236 """Test local variable expansion in !system and %magic calls"""
234 # !system
237 # !system
235 ip.run_cell(
238 ip.run_cell(
236 "def test():\n"
239 "def test():\n"
237 ' lvar = "ttt"\n'
240 ' lvar = "ttt"\n'
238 " ret = !echo {lvar}\n"
241 " ret = !echo {lvar}\n"
239 " return ret[0]\n"
242 " return ret[0]\n"
240 )
243 )
241 res = ip.user_ns["test"]()
244 res = ip.user_ns["test"]()
242 self.assertIn("ttt", res)
245 self.assertIn("ttt", res)
243
246
244 # %magic
247 # %magic
245 ip.run_cell(
248 ip.run_cell(
246 "def makemacro():\n"
249 "def makemacro():\n"
247 ' macroname = "macro_var_expand_locals"\n'
250 ' macroname = "macro_var_expand_locals"\n'
248 " %macro {macroname} codestr\n"
251 " %macro {macroname} codestr\n"
249 )
252 )
250 ip.user_ns["codestr"] = "str(12)"
253 ip.user_ns["codestr"] = "str(12)"
251 ip.run_cell("makemacro()")
254 ip.run_cell("makemacro()")
252 self.assertIn("macro_var_expand_locals", ip.user_ns)
255 self.assertIn("macro_var_expand_locals", ip.user_ns)
253
256
254 def test_var_expand_self(self):
257 def test_var_expand_self(self):
255 """Test variable expansion with the name 'self', which was failing.
258 """Test variable expansion with the name 'self', which was failing.
256
259
257 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
260 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
258 """
261 """
259 ip.run_cell(
262 ip.run_cell(
260 "class cTest:\n"
263 "class cTest:\n"
261 ' classvar="see me"\n'
264 ' classvar="see me"\n'
262 " def test(self):\n"
265 " def test(self):\n"
263 " res = !echo Variable: {self.classvar}\n"
266 " res = !echo Variable: {self.classvar}\n"
264 " return res[0]\n"
267 " return res[0]\n"
265 )
268 )
266 self.assertIn("see me", ip.user_ns["cTest"]().test())
269 self.assertIn("see me", ip.user_ns["cTest"]().test())
267
270
268 def test_bad_var_expand(self):
271 def test_bad_var_expand(self):
269 """var_expand on invalid formats shouldn't raise"""
272 """var_expand on invalid formats shouldn't raise"""
270 # SyntaxError
273 # SyntaxError
271 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
274 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
272 # NameError
275 # NameError
273 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
276 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
274 # ZeroDivisionError
277 # ZeroDivisionError
275 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
278 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
276
279
277 def test_silent_postexec(self):
280 def test_silent_postexec(self):
278 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
281 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
279 pre_explicit = mock.Mock()
282 pre_explicit = mock.Mock()
280 pre_always = mock.Mock()
283 pre_always = mock.Mock()
281 post_explicit = mock.Mock()
284 post_explicit = mock.Mock()
282 post_always = mock.Mock()
285 post_always = mock.Mock()
283 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
286 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
284
287
285 ip.events.register('pre_run_cell', pre_explicit)
288 ip.events.register('pre_run_cell', pre_explicit)
286 ip.events.register('pre_execute', pre_always)
289 ip.events.register('pre_execute', pre_always)
287 ip.events.register('post_run_cell', post_explicit)
290 ip.events.register('post_run_cell', post_explicit)
288 ip.events.register('post_execute', post_always)
291 ip.events.register('post_execute', post_always)
289
292
290 try:
293 try:
291 ip.run_cell("1", silent=True)
294 ip.run_cell("1", silent=True)
292 assert pre_always.called
295 assert pre_always.called
293 assert not pre_explicit.called
296 assert not pre_explicit.called
294 assert post_always.called
297 assert post_always.called
295 assert not post_explicit.called
298 assert not post_explicit.called
296 # double-check that non-silent exec did what we expected
299 # double-check that non-silent exec did what we expected
297 # silent to avoid
300 # silent to avoid
298 ip.run_cell("1")
301 ip.run_cell("1")
299 assert pre_explicit.called
302 assert pre_explicit.called
300 assert post_explicit.called
303 assert post_explicit.called
301 info, = pre_explicit.call_args[0]
304 info, = pre_explicit.call_args[0]
302 result, = post_explicit.call_args[0]
305 result, = post_explicit.call_args[0]
303 self.assertEqual(info, result.info)
306 self.assertEqual(info, result.info)
304 # check that post hooks are always called
307 # check that post hooks are always called
305 [m.reset_mock() for m in all_mocks]
308 [m.reset_mock() for m in all_mocks]
306 ip.run_cell("syntax error")
309 ip.run_cell("syntax error")
307 assert pre_always.called
310 assert pre_always.called
308 assert pre_explicit.called
311 assert pre_explicit.called
309 assert post_always.called
312 assert post_always.called
310 assert post_explicit.called
313 assert post_explicit.called
311 info, = pre_explicit.call_args[0]
314 info, = pre_explicit.call_args[0]
312 result, = post_explicit.call_args[0]
315 result, = post_explicit.call_args[0]
313 self.assertEqual(info, result.info)
316 self.assertEqual(info, result.info)
314 finally:
317 finally:
315 # remove post-exec
318 # remove post-exec
316 ip.events.unregister('pre_run_cell', pre_explicit)
319 ip.events.unregister('pre_run_cell', pre_explicit)
317 ip.events.unregister('pre_execute', pre_always)
320 ip.events.unregister('pre_execute', pre_always)
318 ip.events.unregister('post_run_cell', post_explicit)
321 ip.events.unregister('post_run_cell', post_explicit)
319 ip.events.unregister('post_execute', post_always)
322 ip.events.unregister('post_execute', post_always)
320
323
321 def test_silent_noadvance(self):
324 def test_silent_noadvance(self):
322 """run_cell(silent=True) doesn't advance execution_count"""
325 """run_cell(silent=True) doesn't advance execution_count"""
323 ec = ip.execution_count
326 ec = ip.execution_count
324 # silent should force store_history=False
327 # silent should force store_history=False
325 ip.run_cell("1", store_history=True, silent=True)
328 ip.run_cell("1", store_history=True, silent=True)
326
329
327 self.assertEqual(ec, ip.execution_count)
330 self.assertEqual(ec, ip.execution_count)
328 # double-check that non-silent exec did what we expected
331 # double-check that non-silent exec did what we expected
329 # silent to avoid
332 # silent to avoid
330 ip.run_cell("1", store_history=True)
333 ip.run_cell("1", store_history=True)
331 self.assertEqual(ec+1, ip.execution_count)
334 self.assertEqual(ec+1, ip.execution_count)
332
335
333 def test_silent_nodisplayhook(self):
336 def test_silent_nodisplayhook(self):
334 """run_cell(silent=True) doesn't trigger displayhook"""
337 """run_cell(silent=True) doesn't trigger displayhook"""
335 d = dict(called=False)
338 d = dict(called=False)
336
339
337 trap = ip.display_trap
340 trap = ip.display_trap
338 save_hook = trap.hook
341 save_hook = trap.hook
339
342
340 def failing_hook(*args, **kwargs):
343 def failing_hook(*args, **kwargs):
341 d['called'] = True
344 d['called'] = True
342
345
343 try:
346 try:
344 trap.hook = failing_hook
347 trap.hook = failing_hook
345 res = ip.run_cell("1", silent=True)
348 res = ip.run_cell("1", silent=True)
346 self.assertFalse(d['called'])
349 self.assertFalse(d['called'])
347 self.assertIsNone(res.result)
350 self.assertIsNone(res.result)
348 # double-check that non-silent exec did what we expected
351 # double-check that non-silent exec did what we expected
349 # silent to avoid
352 # silent to avoid
350 ip.run_cell("1")
353 ip.run_cell("1")
351 self.assertTrue(d['called'])
354 self.assertTrue(d['called'])
352 finally:
355 finally:
353 trap.hook = save_hook
356 trap.hook = save_hook
354
357
355 def test_ofind_line_magic(self):
358 def test_ofind_line_magic(self):
356 from IPython.core.magic import register_line_magic
359 from IPython.core.magic import register_line_magic
357
360
358 @register_line_magic
361 @register_line_magic
359 def lmagic(line):
362 def lmagic(line):
360 "A line magic"
363 "A line magic"
361
364
362 # Get info on line magic
365 # Get info on line magic
363 lfind = ip._ofind("lmagic")
366 lfind = ip._ofind("lmagic")
364 info = OInfo(
367 info = OInfo(
365 found=True,
368 found=True,
366 isalias=False,
369 isalias=False,
367 ismagic=True,
370 ismagic=True,
368 namespace="IPython internal",
371 namespace="IPython internal",
369 obj=lmagic,
372 obj=lmagic,
370 parent=None,
373 parent=None,
371 )
374 )
372 self.assertEqual(lfind, info)
375 self.assertEqual(lfind, info)
373
376
374 def test_ofind_cell_magic(self):
377 def test_ofind_cell_magic(self):
375 from IPython.core.magic import register_cell_magic
378 from IPython.core.magic import register_cell_magic
376
379
377 @register_cell_magic
380 @register_cell_magic
378 def cmagic(line, cell):
381 def cmagic(line, cell):
379 "A cell magic"
382 "A cell magic"
380
383
381 # Get info on cell magic
384 # Get info on cell magic
382 find = ip._ofind("cmagic")
385 find = ip._ofind("cmagic")
383 info = OInfo(
386 info = OInfo(
384 found=True,
387 found=True,
385 isalias=False,
388 isalias=False,
386 ismagic=True,
389 ismagic=True,
387 namespace="IPython internal",
390 namespace="IPython internal",
388 obj=cmagic,
391 obj=cmagic,
389 parent=None,
392 parent=None,
390 )
393 )
391 self.assertEqual(find, info)
394 self.assertEqual(find, info)
392
395
393 def test_ofind_property_with_error(self):
396 def test_ofind_property_with_error(self):
394 class A(object):
397 class A(object):
395 @property
398 @property
396 def foo(self):
399 def foo(self):
397 raise NotImplementedError() # pragma: no cover
400 raise NotImplementedError() # pragma: no cover
398
401
399 a = A()
402 a = A()
400
403
401 found = ip._ofind("a.foo", [("locals", locals())])
404 found = ip._ofind("a.foo", [("locals", locals())])
402 info = OInfo(
405 info = OInfo(
403 found=True,
406 found=True,
404 isalias=False,
407 isalias=False,
405 ismagic=False,
408 ismagic=False,
406 namespace="locals",
409 namespace="locals",
407 obj=A.foo,
410 obj=A.foo,
408 parent=a,
411 parent=a,
409 )
412 )
410 self.assertEqual(found, info)
413 self.assertEqual(found, info)
411
414
412 def test_ofind_multiple_attribute_lookups(self):
415 def test_ofind_multiple_attribute_lookups(self):
413 class A(object):
416 class A(object):
414 @property
417 @property
415 def foo(self):
418 def foo(self):
416 raise NotImplementedError() # pragma: no cover
419 raise NotImplementedError() # pragma: no cover
417
420
418 a = A()
421 a = A()
419 a.a = A()
422 a.a = A()
420 a.a.a = A()
423 a.a.a = A()
421
424
422 found = ip._ofind("a.a.a.foo", [("locals", locals())])
425 found = ip._ofind("a.a.a.foo", [("locals", locals())])
423 info = OInfo(
426 info = OInfo(
424 found=True,
427 found=True,
425 isalias=False,
428 isalias=False,
426 ismagic=False,
429 ismagic=False,
427 namespace="locals",
430 namespace="locals",
428 obj=A.foo,
431 obj=A.foo,
429 parent=a.a.a,
432 parent=a.a.a,
430 )
433 )
431 self.assertEqual(found, info)
434 self.assertEqual(found, info)
432
435
433 def test_ofind_slotted_attributes(self):
436 def test_ofind_slotted_attributes(self):
434 class A(object):
437 class A(object):
435 __slots__ = ['foo']
438 __slots__ = ['foo']
436 def __init__(self):
439 def __init__(self):
437 self.foo = 'bar'
440 self.foo = 'bar'
438
441
439 a = A()
442 a = A()
440 found = ip._ofind("a.foo", [("locals", locals())])
443 found = ip._ofind("a.foo", [("locals", locals())])
441 info = OInfo(
444 info = OInfo(
442 found=True,
445 found=True,
443 isalias=False,
446 isalias=False,
444 ismagic=False,
447 ismagic=False,
445 namespace="locals",
448 namespace="locals",
446 obj=a.foo,
449 obj=a.foo,
447 parent=a,
450 parent=a,
448 )
451 )
449 self.assertEqual(found, info)
452 self.assertEqual(found, info)
450
453
451 found = ip._ofind("a.bar", [("locals", locals())])
454 found = ip._ofind("a.bar", [("locals", locals())])
452 expected = OInfo(
455 expected = OInfo(
453 found=False,
456 found=False,
454 isalias=False,
457 isalias=False,
455 ismagic=False,
458 ismagic=False,
456 namespace=None,
459 namespace=None,
457 obj=None,
460 obj=None,
458 parent=a,
461 parent=a,
459 )
462 )
460 assert found == expected
463 assert found == expected
461
464
462 def test_ofind_prefers_property_to_instance_level_attribute(self):
465 def test_ofind_prefers_property_to_instance_level_attribute(self):
463 class A(object):
466 class A(object):
464 @property
467 @property
465 def foo(self):
468 def foo(self):
466 return 'bar'
469 return 'bar'
467 a = A()
470 a = A()
468 a.__dict__["foo"] = "baz"
471 a.__dict__["foo"] = "baz"
469 self.assertEqual(a.foo, "bar")
472 self.assertEqual(a.foo, "bar")
470 found = ip._ofind("a.foo", [("locals", locals())])
473 found = ip._ofind("a.foo", [("locals", locals())])
471 self.assertIs(found.obj, A.foo)
474 self.assertIs(found.obj, A.foo)
472
475
473 def test_custom_syntaxerror_exception(self):
476 def test_custom_syntaxerror_exception(self):
474 called = []
477 called = []
475 def my_handler(shell, etype, value, tb, tb_offset=None):
478 def my_handler(shell, etype, value, tb, tb_offset=None):
476 called.append(etype)
479 called.append(etype)
477 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
480 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
478
481
479 ip.set_custom_exc((SyntaxError,), my_handler)
482 ip.set_custom_exc((SyntaxError,), my_handler)
480 try:
483 try:
481 ip.run_cell("1f")
484 ip.run_cell("1f")
482 # Check that this was called, and only once.
485 # Check that this was called, and only once.
483 self.assertEqual(called, [SyntaxError])
486 self.assertEqual(called, [SyntaxError])
484 finally:
487 finally:
485 # Reset the custom exception hook
488 # Reset the custom exception hook
486 ip.set_custom_exc((), None)
489 ip.set_custom_exc((), None)
487
490
488 def test_custom_exception(self):
491 def test_custom_exception(self):
489 called = []
492 called = []
490 def my_handler(shell, etype, value, tb, tb_offset=None):
493 def my_handler(shell, etype, value, tb, tb_offset=None):
491 called.append(etype)
494 called.append(etype)
492 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
495 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
493
496
494 ip.set_custom_exc((ValueError,), my_handler)
497 ip.set_custom_exc((ValueError,), my_handler)
495 try:
498 try:
496 res = ip.run_cell("raise ValueError('test')")
499 res = ip.run_cell("raise ValueError('test')")
497 # Check that this was called, and only once.
500 # Check that this was called, and only once.
498 self.assertEqual(called, [ValueError])
501 self.assertEqual(called, [ValueError])
499 # Check that the error is on the result object
502 # Check that the error is on the result object
500 self.assertIsInstance(res.error_in_exec, ValueError)
503 self.assertIsInstance(res.error_in_exec, ValueError)
501 finally:
504 finally:
502 # Reset the custom exception hook
505 # Reset the custom exception hook
503 ip.set_custom_exc((), None)
506 ip.set_custom_exc((), None)
504
507
505 @mock.patch("builtins.print")
508 @mock.patch("builtins.print")
506 def test_showtraceback_with_surrogates(self, mocked_print):
509 def test_showtraceback_with_surrogates(self, mocked_print):
507 values = []
510 values = []
508
511
509 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
512 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
510 values.append(value)
513 values.append(value)
511 if value == chr(0xD8FF):
514 if value == chr(0xD8FF):
512 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
515 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
513
516
514 # mock builtins.print
517 # mock builtins.print
515 mocked_print.side_effect = mock_print_func
518 mocked_print.side_effect = mock_print_func
516
519
517 # ip._showtraceback() is replaced in globalipapp.py.
520 # ip._showtraceback() is replaced in globalipapp.py.
518 # Call original method to test.
521 # Call original method to test.
519 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
522 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
520
523
521 self.assertEqual(mocked_print.call_count, 2)
524 self.assertEqual(mocked_print.call_count, 2)
522 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
525 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
523
526
524 def test_mktempfile(self):
527 def test_mktempfile(self):
525 filename = ip.mktempfile()
528 filename = ip.mktempfile()
526 # Check that we can open the file again on Windows
529 # Check that we can open the file again on Windows
527 with open(filename, "w", encoding="utf-8") as f:
530 with open(filename, "w", encoding="utf-8") as f:
528 f.write("abc")
531 f.write("abc")
529
532
530 filename = ip.mktempfile(data="blah")
533 filename = ip.mktempfile(data="blah")
531 with open(filename, "r", encoding="utf-8") as f:
534 with open(filename, "r", encoding="utf-8") as f:
532 self.assertEqual(f.read(), "blah")
535 self.assertEqual(f.read(), "blah")
533
536
534 def test_new_main_mod(self):
537 def test_new_main_mod(self):
535 # Smoketest to check that this accepts a unicode module name
538 # Smoketest to check that this accepts a unicode module name
536 name = u'jiefmw'
539 name = u'jiefmw'
537 mod = ip.new_main_mod(u'%s.py' % name, name)
540 mod = ip.new_main_mod(u'%s.py' % name, name)
538 self.assertEqual(mod.__name__, name)
541 self.assertEqual(mod.__name__, name)
539
542
540 def test_get_exception_only(self):
543 def test_get_exception_only(self):
541 try:
544 try:
542 raise KeyboardInterrupt
545 raise KeyboardInterrupt
543 except KeyboardInterrupt:
546 except KeyboardInterrupt:
544 msg = ip.get_exception_only()
547 msg = ip.get_exception_only()
545 self.assertEqual(msg, 'KeyboardInterrupt\n')
548 self.assertEqual(msg, 'KeyboardInterrupt\n')
546
549
547 try:
550 try:
548 raise DerivedInterrupt("foo")
551 raise DerivedInterrupt("foo")
549 except KeyboardInterrupt:
552 except KeyboardInterrupt:
550 msg = ip.get_exception_only()
553 msg = ip.get_exception_only()
551 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
554 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
552
555
553 def test_inspect_text(self):
556 def test_inspect_text(self):
554 ip.run_cell('a = 5')
557 ip.run_cell('a = 5')
555 text = ip.object_inspect_text('a')
558 text = ip.object_inspect_text('a')
556 self.assertIsInstance(text, str)
559 self.assertIsInstance(text, str)
557
560
558 def test_last_execution_result(self):
561 def test_last_execution_result(self):
559 """ Check that last execution result gets set correctly (GH-10702) """
562 """ Check that last execution result gets set correctly (GH-10702) """
560 result = ip.run_cell('a = 5; a')
563 result = ip.run_cell('a = 5; a')
561 self.assertTrue(ip.last_execution_succeeded)
564 self.assertTrue(ip.last_execution_succeeded)
562 self.assertEqual(ip.last_execution_result.result, 5)
565 self.assertEqual(ip.last_execution_result.result, 5)
563
566
564 result = ip.run_cell('a = x_invalid_id_x')
567 result = ip.run_cell('a = x_invalid_id_x')
565 self.assertFalse(ip.last_execution_succeeded)
568 self.assertFalse(ip.last_execution_succeeded)
566 self.assertFalse(ip.last_execution_result.success)
569 self.assertFalse(ip.last_execution_result.success)
567 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
570 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
568
571
569 def test_reset_aliasing(self):
572 def test_reset_aliasing(self):
570 """ Check that standard posix aliases work after %reset. """
573 """ Check that standard posix aliases work after %reset. """
571 if os.name != 'posix':
574 if os.name != 'posix':
572 return
575 return
573
576
574 ip.reset()
577 ip.reset()
575 for cmd in ('clear', 'more', 'less', 'man'):
578 for cmd in ('clear', 'more', 'less', 'man'):
576 res = ip.run_cell('%' + cmd)
579 res = ip.run_cell('%' + cmd)
577 self.assertEqual(res.success, True)
580 self.assertEqual(res.success, True)
578
581
579
582
580 @pytest.mark.skipif(
583 @pytest.mark.skipif(
581 sys.implementation.name == "pypy"
584 sys.implementation.name == "pypy"
582 and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)),
585 and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)),
583 reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860",
586 reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860",
584 )
587 )
585 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
588 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
586 @onlyif_unicode_paths
589 @onlyif_unicode_paths
587 def setUp(self):
590 def setUp(self):
588 self.BASETESTDIR = tempfile.mkdtemp()
591 self.BASETESTDIR = tempfile.mkdtemp()
589 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
592 self.TESTDIR = join(self.BASETESTDIR, u"åäö")
590 os.mkdir(self.TESTDIR)
593 os.mkdir(self.TESTDIR)
591 with open(
594 with open(
592 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
595 join(self.TESTDIR, "åäötestscript.py"), "w", encoding="utf-8"
593 ) as sfile:
596 ) as sfile:
594 sfile.write("pass\n")
597 sfile.write("pass\n")
595 self.oldpath = os.getcwd()
598 self.oldpath = os.getcwd()
596 os.chdir(self.TESTDIR)
599 os.chdir(self.TESTDIR)
597 self.fname = u"åäötestscript.py"
600 self.fname = u"åäötestscript.py"
598
601
599 def tearDown(self):
602 def tearDown(self):
600 os.chdir(self.oldpath)
603 os.chdir(self.oldpath)
601 shutil.rmtree(self.BASETESTDIR)
604 shutil.rmtree(self.BASETESTDIR)
602
605
603 @onlyif_unicode_paths
606 @onlyif_unicode_paths
604 def test_1(self):
607 def test_1(self):
605 """Test safe_execfile with non-ascii path
608 """Test safe_execfile with non-ascii path
606 """
609 """
607 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
610 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
608
611
609 class ExitCodeChecks(tt.TempFileMixin):
612 class ExitCodeChecks(tt.TempFileMixin):
610
613
611 def setUp(self):
614 def setUp(self):
612 self.system = ip.system_raw
615 self.system = ip.system_raw
613
616
614 def test_exit_code_ok(self):
617 def test_exit_code_ok(self):
615 self.system('exit 0')
618 self.system('exit 0')
616 self.assertEqual(ip.user_ns['_exit_code'], 0)
619 self.assertEqual(ip.user_ns['_exit_code'], 0)
617
620
618 def test_exit_code_error(self):
621 def test_exit_code_error(self):
619 self.system('exit 1')
622 self.system('exit 1')
620 self.assertEqual(ip.user_ns['_exit_code'], 1)
623 self.assertEqual(ip.user_ns['_exit_code'], 1)
621
624
622 @skipif(not hasattr(signal, 'SIGALRM'))
625 @skipif(not hasattr(signal, 'SIGALRM'))
623 def test_exit_code_signal(self):
626 def test_exit_code_signal(self):
624 self.mktmp("import signal, time\n"
627 self.mktmp("import signal, time\n"
625 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
628 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
626 "time.sleep(1)\n")
629 "time.sleep(1)\n")
627 self.system("%s %s" % (sys.executable, self.fname))
630 self.system("%s %s" % (sys.executable, self.fname))
628 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
631 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
629
632
630 @onlyif_cmds_exist("csh")
633 @onlyif_cmds_exist("csh")
631 def test_exit_code_signal_csh(self): # pragma: no cover
634 def test_exit_code_signal_csh(self): # pragma: no cover
632 SHELL = os.environ.get("SHELL", None)
635 SHELL = os.environ.get("SHELL", None)
633 os.environ["SHELL"] = find_cmd("csh")
636 os.environ["SHELL"] = find_cmd("csh")
634 try:
637 try:
635 self.test_exit_code_signal()
638 self.test_exit_code_signal()
636 finally:
639 finally:
637 if SHELL is not None:
640 if SHELL is not None:
638 os.environ['SHELL'] = SHELL
641 os.environ['SHELL'] = SHELL
639 else:
642 else:
640 del os.environ['SHELL']
643 del os.environ['SHELL']
641
644
642
645
643 class TestSystemRaw(ExitCodeChecks):
646 class TestSystemRaw(ExitCodeChecks):
644
647
645 def setUp(self):
648 def setUp(self):
646 super().setUp()
649 super().setUp()
647 self.system = ip.system_raw
650 self.system = ip.system_raw
648
651
649 @onlyif_unicode_paths
652 @onlyif_unicode_paths
650 def test_1(self):
653 def test_1(self):
651 """Test system_raw with non-ascii cmd
654 """Test system_raw with non-ascii cmd
652 """
655 """
653 cmd = u'''python -c "'åäö'" '''
656 cmd = u'''python -c "'åäö'" '''
654 ip.system_raw(cmd)
657 ip.system_raw(cmd)
655
658
656 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
659 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
657 @mock.patch('os.system', side_effect=KeyboardInterrupt)
660 @mock.patch('os.system', side_effect=KeyboardInterrupt)
658 def test_control_c(self, *mocks):
661 def test_control_c(self, *mocks):
659 try:
662 try:
660 self.system("sleep 1 # wont happen")
663 self.system("sleep 1 # wont happen")
661 except KeyboardInterrupt: # pragma: no cove
664 except KeyboardInterrupt: # pragma: no cove
662 self.fail(
665 self.fail(
663 "system call should intercept "
666 "system call should intercept "
664 "keyboard interrupt from subprocess.call"
667 "keyboard interrupt from subprocess.call"
665 )
668 )
666 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
669 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
667
670
668
671
669 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
672 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
670 def test_magic_warnings(magic_cmd):
673 def test_magic_warnings(magic_cmd):
671 if sys.platform == "win32":
674 if sys.platform == "win32":
672 to_mock = "os.system"
675 to_mock = "os.system"
673 expected_arg, expected_kwargs = magic_cmd, dict()
676 expected_arg, expected_kwargs = magic_cmd, dict()
674 else:
677 else:
675 to_mock = "subprocess.call"
678 to_mock = "subprocess.call"
676 expected_arg, expected_kwargs = magic_cmd, dict(
679 expected_arg, expected_kwargs = magic_cmd, dict(
677 shell=True, executable=os.environ.get("SHELL", None)
680 shell=True, executable=os.environ.get("SHELL", None)
678 )
681 )
679
682
680 with mock.patch(to_mock, return_value=0) as mock_sub:
683 with mock.patch(to_mock, return_value=0) as mock_sub:
681 with pytest.warns(Warning, match=r"You executed the system command"):
684 with pytest.warns(Warning, match=r"You executed the system command"):
682 ip.system_raw(magic_cmd)
685 ip.system_raw(magic_cmd)
683 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
686 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
684
687
685
688
686 # TODO: Exit codes are currently ignored on Windows.
689 # TODO: Exit codes are currently ignored on Windows.
687 class TestSystemPipedExitCode(ExitCodeChecks):
690 class TestSystemPipedExitCode(ExitCodeChecks):
688
691
689 def setUp(self):
692 def setUp(self):
690 super().setUp()
693 super().setUp()
691 self.system = ip.system_piped
694 self.system = ip.system_piped
692
695
693 @skip_win32
696 @skip_win32
694 def test_exit_code_ok(self):
697 def test_exit_code_ok(self):
695 ExitCodeChecks.test_exit_code_ok(self)
698 ExitCodeChecks.test_exit_code_ok(self)
696
699
697 @skip_win32
700 @skip_win32
698 def test_exit_code_error(self):
701 def test_exit_code_error(self):
699 ExitCodeChecks.test_exit_code_error(self)
702 ExitCodeChecks.test_exit_code_error(self)
700
703
701 @skip_win32
704 @skip_win32
702 def test_exit_code_signal(self):
705 def test_exit_code_signal(self):
703 ExitCodeChecks.test_exit_code_signal(self)
706 ExitCodeChecks.test_exit_code_signal(self)
704
707
705 class TestModules(tt.TempFileMixin):
708 class TestModules(tt.TempFileMixin):
706 def test_extraneous_loads(self):
709 def test_extraneous_loads(self):
707 """Test we're not loading modules on startup that we shouldn't.
710 """Test we're not loading modules on startup that we shouldn't.
708 """
711 """
709 self.mktmp("import sys\n"
712 self.mktmp("import sys\n"
710 "print('numpy' in sys.modules)\n"
713 "print('numpy' in sys.modules)\n"
711 "print('ipyparallel' in sys.modules)\n"
714 "print('ipyparallel' in sys.modules)\n"
712 "print('ipykernel' in sys.modules)\n"
715 "print('ipykernel' in sys.modules)\n"
713 )
716 )
714 out = "False\nFalse\nFalse\n"
717 out = "False\nFalse\nFalse\n"
715 tt.ipexec_validate(self.fname, out)
718 tt.ipexec_validate(self.fname, out)
716
719
717 class Negator(ast.NodeTransformer):
720 class Negator(ast.NodeTransformer):
718 """Negates all number literals in an AST."""
721 """Negates all number literals in an AST."""
719
722
720 def visit_Num(self, node):
723 def visit_Num(self, node):
721 node.n = -node.n
724 node.n = -node.n
722 return node
725 return node
723
726
724 def visit_Constant(self, node):
727 def visit_Constant(self, node):
725 if isinstance(node.value, int):
728 if isinstance(node.value, int):
726 return self.visit_Num(node)
729 return self.visit_Num(node)
727 return node
730 return node
728
731
729 class TestAstTransform(unittest.TestCase):
732 class TestAstTransform(unittest.TestCase):
730 def setUp(self):
733 def setUp(self):
731 self.negator = Negator()
734 self.negator = Negator()
732 ip.ast_transformers.append(self.negator)
735 ip.ast_transformers.append(self.negator)
733
736
734 def tearDown(self):
737 def tearDown(self):
735 ip.ast_transformers.remove(self.negator)
738 ip.ast_transformers.remove(self.negator)
736
739
737 def test_non_int_const(self):
740 def test_non_int_const(self):
738 with tt.AssertPrints("hello"):
741 with tt.AssertPrints("hello"):
739 ip.run_cell('print("hello")')
742 ip.run_cell('print("hello")')
740
743
741 def test_run_cell(self):
744 def test_run_cell(self):
742 with tt.AssertPrints("-34"):
745 with tt.AssertPrints("-34"):
743 ip.run_cell("print(12 + 22)")
746 ip.run_cell("print(12 + 22)")
744
747
745 # A named reference to a number shouldn't be transformed.
748 # A named reference to a number shouldn't be transformed.
746 ip.user_ns["n"] = 55
749 ip.user_ns["n"] = 55
747 with tt.AssertNotPrints("-55"):
750 with tt.AssertNotPrints("-55"):
748 ip.run_cell("print(n)")
751 ip.run_cell("print(n)")
749
752
750 def test_timeit(self):
753 def test_timeit(self):
751 called = set()
754 called = set()
752 def f(x):
755 def f(x):
753 called.add(x)
756 called.add(x)
754 ip.push({'f':f})
757 ip.push({'f':f})
755
758
756 with tt.AssertPrints("std. dev. of"):
759 with tt.AssertPrints("std. dev. of"):
757 ip.run_line_magic("timeit", "-n1 f(1)")
760 ip.run_line_magic("timeit", "-n1 f(1)")
758 self.assertEqual(called, {-1})
761 self.assertEqual(called, {-1})
759 called.clear()
762 called.clear()
760
763
761 with tt.AssertPrints("std. dev. of"):
764 with tt.AssertPrints("std. dev. of"):
762 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
765 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
763 self.assertEqual(called, {-2, -3})
766 self.assertEqual(called, {-2, -3})
764
767
765 def test_time(self):
768 def test_time(self):
766 called = []
769 called = []
767 def f(x):
770 def f(x):
768 called.append(x)
771 called.append(x)
769 ip.push({'f':f})
772 ip.push({'f':f})
770
773
771 # Test with an expression
774 # Test with an expression
772 with tt.AssertPrints("Wall time: "):
775 with tt.AssertPrints("Wall time: "):
773 ip.run_line_magic("time", "f(5+9)")
776 ip.run_line_magic("time", "f(5+9)")
774 self.assertEqual(called, [-14])
777 self.assertEqual(called, [-14])
775 called[:] = []
778 called[:] = []
776
779
777 # Test with a statement (different code path)
780 # Test with a statement (different code path)
778 with tt.AssertPrints("Wall time: "):
781 with tt.AssertPrints("Wall time: "):
779 ip.run_line_magic("time", "a = f(-3 + -2)")
782 ip.run_line_magic("time", "a = f(-3 + -2)")
780 self.assertEqual(called, [5])
783 self.assertEqual(called, [5])
781
784
782 def test_macro(self):
785 def test_macro(self):
783 ip.push({'a':10})
786 ip.push({'a':10})
784 # The AST transformation makes this do a+=-1
787 # The AST transformation makes this do a+=-1
785 ip.define_macro("amacro", "a+=1\nprint(a)")
788 ip.define_macro("amacro", "a+=1\nprint(a)")
786
789
787 with tt.AssertPrints("9"):
790 with tt.AssertPrints("9"):
788 ip.run_cell("amacro")
791 ip.run_cell("amacro")
789 with tt.AssertPrints("8"):
792 with tt.AssertPrints("8"):
790 ip.run_cell("amacro")
793 ip.run_cell("amacro")
791
794
792 class TestMiscTransform(unittest.TestCase):
795 class TestMiscTransform(unittest.TestCase):
793
796
794
797
795 def test_transform_only_once(self):
798 def test_transform_only_once(self):
796 cleanup = 0
799 cleanup = 0
797 line_t = 0
800 line_t = 0
798 def count_cleanup(lines):
801 def count_cleanup(lines):
799 nonlocal cleanup
802 nonlocal cleanup
800 cleanup += 1
803 cleanup += 1
801 return lines
804 return lines
802
805
803 def count_line_t(lines):
806 def count_line_t(lines):
804 nonlocal line_t
807 nonlocal line_t
805 line_t += 1
808 line_t += 1
806 return lines
809 return lines
807
810
808 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
811 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
809 ip.input_transformer_manager.line_transforms.append(count_line_t)
812 ip.input_transformer_manager.line_transforms.append(count_line_t)
810
813
811 ip.run_cell('1')
814 ip.run_cell('1')
812
815
813 assert cleanup == 1
816 assert cleanup == 1
814 assert line_t == 1
817 assert line_t == 1
815
818
816 class IntegerWrapper(ast.NodeTransformer):
819 class IntegerWrapper(ast.NodeTransformer):
817 """Wraps all integers in a call to Integer()"""
820 """Wraps all integers in a call to Integer()"""
818
821
819 # for Python 3.7 and earlier
822 # for Python 3.7 and earlier
820
823
821 # for Python 3.7 and earlier
824 # for Python 3.7 and earlier
822 def visit_Num(self, node):
825 def visit_Num(self, node):
823 if isinstance(node.n, int):
826 if isinstance(node.n, int):
824 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
827 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
825 args=[node], keywords=[])
828 args=[node], keywords=[])
826 return node
829 return node
827
830
828 # For Python 3.8+
831 # For Python 3.8+
829 def visit_Constant(self, node):
832 def visit_Constant(self, node):
830 if isinstance(node.value, int):
833 if isinstance(node.value, int):
831 return self.visit_Num(node)
834 return self.visit_Num(node)
832 return node
835 return node
833
836
834
837
835 class TestAstTransform2(unittest.TestCase):
838 class TestAstTransform2(unittest.TestCase):
836 def setUp(self):
839 def setUp(self):
837 self.intwrapper = IntegerWrapper()
840 self.intwrapper = IntegerWrapper()
838 ip.ast_transformers.append(self.intwrapper)
841 ip.ast_transformers.append(self.intwrapper)
839
842
840 self.calls = []
843 self.calls = []
841 def Integer(*args):
844 def Integer(*args):
842 self.calls.append(args)
845 self.calls.append(args)
843 return args
846 return args
844 ip.push({"Integer": Integer})
847 ip.push({"Integer": Integer})
845
848
846 def tearDown(self):
849 def tearDown(self):
847 ip.ast_transformers.remove(self.intwrapper)
850 ip.ast_transformers.remove(self.intwrapper)
848 del ip.user_ns['Integer']
851 del ip.user_ns['Integer']
849
852
850 def test_run_cell(self):
853 def test_run_cell(self):
851 ip.run_cell("n = 2")
854 ip.run_cell("n = 2")
852 self.assertEqual(self.calls, [(2,)])
855 self.assertEqual(self.calls, [(2,)])
853
856
854 # This shouldn't throw an error
857 # This shouldn't throw an error
855 ip.run_cell("o = 2.0")
858 ip.run_cell("o = 2.0")
856 self.assertEqual(ip.user_ns['o'], 2.0)
859 self.assertEqual(ip.user_ns['o'], 2.0)
857
860
858 def test_run_cell_non_int(self):
861 def test_run_cell_non_int(self):
859 ip.run_cell("n = 'a'")
862 ip.run_cell("n = 'a'")
860 assert self.calls == []
863 assert self.calls == []
861
864
862 def test_timeit(self):
865 def test_timeit(self):
863 called = set()
866 called = set()
864 def f(x):
867 def f(x):
865 called.add(x)
868 called.add(x)
866 ip.push({'f':f})
869 ip.push({'f':f})
867
870
868 with tt.AssertPrints("std. dev. of"):
871 with tt.AssertPrints("std. dev. of"):
869 ip.run_line_magic("timeit", "-n1 f(1)")
872 ip.run_line_magic("timeit", "-n1 f(1)")
870 self.assertEqual(called, {(1,)})
873 self.assertEqual(called, {(1,)})
871 called.clear()
874 called.clear()
872
875
873 with tt.AssertPrints("std. dev. of"):
876 with tt.AssertPrints("std. dev. of"):
874 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
877 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
875 self.assertEqual(called, {(2,), (3,)})
878 self.assertEqual(called, {(2,), (3,)})
876
879
877 class ErrorTransformer(ast.NodeTransformer):
880 class ErrorTransformer(ast.NodeTransformer):
878 """Throws an error when it sees a number."""
881 """Throws an error when it sees a number."""
879
882
880 def visit_Constant(self, node):
883 def visit_Constant(self, node):
881 if isinstance(node.value, int):
884 if isinstance(node.value, int):
882 raise ValueError("test")
885 raise ValueError("test")
883 return node
886 return node
884
887
885
888
886 class TestAstTransformError(unittest.TestCase):
889 class TestAstTransformError(unittest.TestCase):
887 def test_unregistering(self):
890 def test_unregistering(self):
888 err_transformer = ErrorTransformer()
891 err_transformer = ErrorTransformer()
889 ip.ast_transformers.append(err_transformer)
892 ip.ast_transformers.append(err_transformer)
890
893
891 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
894 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
892 ip.run_cell("1 + 2")
895 ip.run_cell("1 + 2")
893
896
894 # This should have been removed.
897 # This should have been removed.
895 self.assertNotIn(err_transformer, ip.ast_transformers)
898 self.assertNotIn(err_transformer, ip.ast_transformers)
896
899
897
900
898 class StringRejector(ast.NodeTransformer):
901 class StringRejector(ast.NodeTransformer):
899 """Throws an InputRejected when it sees a string literal.
902 """Throws an InputRejected when it sees a string literal.
900
903
901 Used to verify that NodeTransformers can signal that a piece of code should
904 Used to verify that NodeTransformers can signal that a piece of code should
902 not be executed by throwing an InputRejected.
905 not be executed by throwing an InputRejected.
903 """
906 """
904
907
905 def visit_Constant(self, node):
908 def visit_Constant(self, node):
906 if isinstance(node.value, str):
909 if isinstance(node.value, str):
907 raise InputRejected("test")
910 raise InputRejected("test")
908 return node
911 return node
909
912
910
913
911 class TestAstTransformInputRejection(unittest.TestCase):
914 class TestAstTransformInputRejection(unittest.TestCase):
912
915
913 def setUp(self):
916 def setUp(self):
914 self.transformer = StringRejector()
917 self.transformer = StringRejector()
915 ip.ast_transformers.append(self.transformer)
918 ip.ast_transformers.append(self.transformer)
916
919
917 def tearDown(self):
920 def tearDown(self):
918 ip.ast_transformers.remove(self.transformer)
921 ip.ast_transformers.remove(self.transformer)
919
922
920 def test_input_rejection(self):
923 def test_input_rejection(self):
921 """Check that NodeTransformers can reject input."""
924 """Check that NodeTransformers can reject input."""
922
925
923 expect_exception_tb = tt.AssertPrints("InputRejected: test")
926 expect_exception_tb = tt.AssertPrints("InputRejected: test")
924 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
927 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
925
928
926 # Run the same check twice to verify that the transformer is not
929 # Run the same check twice to verify that the transformer is not
927 # disabled after raising.
930 # disabled after raising.
928 with expect_exception_tb, expect_no_cell_output:
931 with expect_exception_tb, expect_no_cell_output:
929 ip.run_cell("'unsafe'")
932 ip.run_cell("'unsafe'")
930
933
931 with expect_exception_tb, expect_no_cell_output:
934 with expect_exception_tb, expect_no_cell_output:
932 res = ip.run_cell("'unsafe'")
935 res = ip.run_cell("'unsafe'")
933
936
934 self.assertIsInstance(res.error_before_exec, InputRejected)
937 self.assertIsInstance(res.error_before_exec, InputRejected)
935
938
936 def test__IPYTHON__():
939 def test__IPYTHON__():
937 # This shouldn't raise a NameError, that's all
940 # This shouldn't raise a NameError, that's all
938 __IPYTHON__
941 __IPYTHON__
939
942
940
943
941 class DummyRepr(object):
944 class DummyRepr(object):
942 def __repr__(self):
945 def __repr__(self):
943 return "DummyRepr"
946 return "DummyRepr"
944
947
945 def _repr_html_(self):
948 def _repr_html_(self):
946 return "<b>dummy</b>"
949 return "<b>dummy</b>"
947
950
948 def _repr_javascript_(self):
951 def _repr_javascript_(self):
949 return "console.log('hi');", {'key': 'value'}
952 return "console.log('hi');", {'key': 'value'}
950
953
951
954
952 def test_user_variables():
955 def test_user_variables():
953 # enable all formatters
956 # enable all formatters
954 ip.display_formatter.active_types = ip.display_formatter.format_types
957 ip.display_formatter.active_types = ip.display_formatter.format_types
955
958
956 ip.user_ns['dummy'] = d = DummyRepr()
959 ip.user_ns['dummy'] = d = DummyRepr()
957 keys = {'dummy', 'doesnotexist'}
960 keys = {'dummy', 'doesnotexist'}
958 r = ip.user_expressions({ key:key for key in keys})
961 r = ip.user_expressions({ key:key for key in keys})
959
962
960 assert keys == set(r.keys())
963 assert keys == set(r.keys())
961 dummy = r["dummy"]
964 dummy = r["dummy"]
962 assert {"status", "data", "metadata"} == set(dummy.keys())
965 assert {"status", "data", "metadata"} == set(dummy.keys())
963 assert dummy["status"] == "ok"
966 assert dummy["status"] == "ok"
964 data = dummy["data"]
967 data = dummy["data"]
965 metadata = dummy["metadata"]
968 metadata = dummy["metadata"]
966 assert data.get("text/html") == d._repr_html_()
969 assert data.get("text/html") == d._repr_html_()
967 js, jsmd = d._repr_javascript_()
970 js, jsmd = d._repr_javascript_()
968 assert data.get("application/javascript") == js
971 assert data.get("application/javascript") == js
969 assert metadata.get("application/javascript") == jsmd
972 assert metadata.get("application/javascript") == jsmd
970
973
971 dne = r["doesnotexist"]
974 dne = r["doesnotexist"]
972 assert dne["status"] == "error"
975 assert dne["status"] == "error"
973 assert dne["ename"] == "NameError"
976 assert dne["ename"] == "NameError"
974
977
975 # back to text only
978 # back to text only
976 ip.display_formatter.active_types = ['text/plain']
979 ip.display_formatter.active_types = ['text/plain']
977
980
978 def test_user_expression():
981 def test_user_expression():
979 # enable all formatters
982 # enable all formatters
980 ip.display_formatter.active_types = ip.display_formatter.format_types
983 ip.display_formatter.active_types = ip.display_formatter.format_types
981 query = {
984 query = {
982 'a' : '1 + 2',
985 'a' : '1 + 2',
983 'b' : '1/0',
986 'b' : '1/0',
984 }
987 }
985 r = ip.user_expressions(query)
988 r = ip.user_expressions(query)
986 import pprint
989 import pprint
987 pprint.pprint(r)
990 pprint.pprint(r)
988 assert set(r.keys()) == set(query.keys())
991 assert set(r.keys()) == set(query.keys())
989 a = r["a"]
992 a = r["a"]
990 assert {"status", "data", "metadata"} == set(a.keys())
993 assert {"status", "data", "metadata"} == set(a.keys())
991 assert a["status"] == "ok"
994 assert a["status"] == "ok"
992 data = a["data"]
995 data = a["data"]
993 metadata = a["metadata"]
996 metadata = a["metadata"]
994 assert data.get("text/plain") == "3"
997 assert data.get("text/plain") == "3"
995
998
996 b = r["b"]
999 b = r["b"]
997 assert b["status"] == "error"
1000 assert b["status"] == "error"
998 assert b["ename"] == "ZeroDivisionError"
1001 assert b["ename"] == "ZeroDivisionError"
999
1002
1000 # back to text only
1003 # back to text only
1001 ip.display_formatter.active_types = ['text/plain']
1004 ip.display_formatter.active_types = ['text/plain']
1002
1005
1003
1006
1004 class TestSyntaxErrorTransformer(unittest.TestCase):
1007 class TestSyntaxErrorTransformer(unittest.TestCase):
1005 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1008 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1006
1009
1007 @staticmethod
1010 @staticmethod
1008 def transformer(lines):
1011 def transformer(lines):
1009 for line in lines:
1012 for line in lines:
1010 pos = line.find('syntaxerror')
1013 pos = line.find('syntaxerror')
1011 if pos >= 0:
1014 if pos >= 0:
1012 e = SyntaxError('input contains "syntaxerror"')
1015 e = SyntaxError('input contains "syntaxerror"')
1013 e.text = line
1016 e.text = line
1014 e.offset = pos + 1
1017 e.offset = pos + 1
1015 raise e
1018 raise e
1016 return lines
1019 return lines
1017
1020
1018 def setUp(self):
1021 def setUp(self):
1019 ip.input_transformers_post.append(self.transformer)
1022 ip.input_transformers_post.append(self.transformer)
1020
1023
1021 def tearDown(self):
1024 def tearDown(self):
1022 ip.input_transformers_post.remove(self.transformer)
1025 ip.input_transformers_post.remove(self.transformer)
1023
1026
1024 def test_syntaxerror_input_transformer(self):
1027 def test_syntaxerror_input_transformer(self):
1025 with tt.AssertPrints('1234'):
1028 with tt.AssertPrints('1234'):
1026 ip.run_cell('1234')
1029 ip.run_cell('1234')
1027 with tt.AssertPrints('SyntaxError: invalid syntax'):
1030 with tt.AssertPrints('SyntaxError: invalid syntax'):
1028 ip.run_cell('1 2 3') # plain python syntax error
1031 ip.run_cell('1 2 3') # plain python syntax error
1029 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1032 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1030 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1033 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1031 with tt.AssertPrints('3456'):
1034 with tt.AssertPrints('3456'):
1032 ip.run_cell('3456')
1035 ip.run_cell('3456')
1033
1036
1034
1037
1035 class TestWarningSuppression(unittest.TestCase):
1038 class TestWarningSuppression(unittest.TestCase):
1036 def test_warning_suppression(self):
1039 def test_warning_suppression(self):
1037 ip.run_cell("import warnings")
1040 ip.run_cell("import warnings")
1038 try:
1041 try:
1039 with self.assertWarnsRegex(UserWarning, "asdf"):
1042 with self.assertWarnsRegex(UserWarning, "asdf"):
1040 ip.run_cell("warnings.warn('asdf')")
1043 ip.run_cell("warnings.warn('asdf')")
1041 # Here's the real test -- if we run that again, we should get the
1044 # Here's the real test -- if we run that again, we should get the
1042 # warning again. Traditionally, each warning was only issued once per
1045 # warning again. Traditionally, each warning was only issued once per
1043 # IPython session (approximately), even if the user typed in new and
1046 # IPython session (approximately), even if the user typed in new and
1044 # different code that should have also triggered the warning, leading
1047 # different code that should have also triggered the warning, leading
1045 # to much confusion.
1048 # to much confusion.
1046 with self.assertWarnsRegex(UserWarning, "asdf"):
1049 with self.assertWarnsRegex(UserWarning, "asdf"):
1047 ip.run_cell("warnings.warn('asdf')")
1050 ip.run_cell("warnings.warn('asdf')")
1048 finally:
1051 finally:
1049 ip.run_cell("del warnings")
1052 ip.run_cell("del warnings")
1050
1053
1051
1054
1052 def test_deprecation_warning(self):
1055 def test_deprecation_warning(self):
1053 ip.run_cell("""
1056 ip.run_cell("""
1054 import warnings
1057 import warnings
1055 def wrn():
1058 def wrn():
1056 warnings.warn(
1059 warnings.warn(
1057 "I AM A WARNING",
1060 "I AM A WARNING",
1058 DeprecationWarning
1061 DeprecationWarning
1059 )
1062 )
1060 """)
1063 """)
1061 try:
1064 try:
1062 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1065 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1063 ip.run_cell("wrn()")
1066 ip.run_cell("wrn()")
1064 finally:
1067 finally:
1065 ip.run_cell("del warnings")
1068 ip.run_cell("del warnings")
1066 ip.run_cell("del wrn")
1069 ip.run_cell("del wrn")
1067
1070
1068
1071
1069 class TestImportNoDeprecate(tt.TempFileMixin):
1072 class TestImportNoDeprecate(tt.TempFileMixin):
1070
1073
1071 def setUp(self):
1074 def setUp(self):
1072 """Make a valid python temp file."""
1075 """Make a valid python temp file."""
1073 self.mktmp("""
1076 self.mktmp("""
1074 import warnings
1077 import warnings
1075 def wrn():
1078 def wrn():
1076 warnings.warn(
1079 warnings.warn(
1077 "I AM A WARNING",
1080 "I AM A WARNING",
1078 DeprecationWarning
1081 DeprecationWarning
1079 )
1082 )
1080 """)
1083 """)
1081 super().setUp()
1084 super().setUp()
1082
1085
1083 def test_no_dep(self):
1086 def test_no_dep(self):
1084 """
1087 """
1085 No deprecation warning should be raised from imported functions
1088 No deprecation warning should be raised from imported functions
1086 """
1089 """
1087 ip.run_cell("from {} import wrn".format(self.fname))
1090 ip.run_cell("from {} import wrn".format(self.fname))
1088
1091
1089 with tt.AssertNotPrints("I AM A WARNING"):
1092 with tt.AssertNotPrints("I AM A WARNING"):
1090 ip.run_cell("wrn()")
1093 ip.run_cell("wrn()")
1091 ip.run_cell("del wrn")
1094 ip.run_cell("del wrn")
1092
1095
1093
1096
1094 def test_custom_exc_count():
1097 def test_custom_exc_count():
1095 hook = mock.Mock(return_value=None)
1098 hook = mock.Mock(return_value=None)
1096 ip.set_custom_exc((SyntaxError,), hook)
1099 ip.set_custom_exc((SyntaxError,), hook)
1097 before = ip.execution_count
1100 before = ip.execution_count
1098 ip.run_cell("def foo()", store_history=True)
1101 ip.run_cell("def foo()", store_history=True)
1099 # restore default excepthook
1102 # restore default excepthook
1100 ip.set_custom_exc((), None)
1103 ip.set_custom_exc((), None)
1101 assert hook.call_count == 1
1104 assert hook.call_count == 1
1102 assert ip.execution_count == before + 1
1105 assert ip.execution_count == before + 1
1103
1106
1104
1107
1105 def test_run_cell_async():
1108 def test_run_cell_async():
1106 ip.run_cell("import asyncio")
1109 ip.run_cell("import asyncio")
1107 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1110 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1108 assert asyncio.iscoroutine(coro)
1111 assert asyncio.iscoroutine(coro)
1109 loop = asyncio.new_event_loop()
1112 loop = asyncio.new_event_loop()
1110 result = loop.run_until_complete(coro)
1113 result = loop.run_until_complete(coro)
1111 assert isinstance(result, interactiveshell.ExecutionResult)
1114 assert isinstance(result, interactiveshell.ExecutionResult)
1112 assert result.result == 5
1115 assert result.result == 5
1113
1116
1114
1117
1115 def test_run_cell_await():
1118 def test_run_cell_await():
1116 ip.run_cell("import asyncio")
1119 ip.run_cell("import asyncio")
1117 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1120 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1118 assert ip.user_ns["_"] == 10
1121 assert ip.user_ns["_"] == 10
1119
1122
1120
1123
1121 def test_run_cell_asyncio_run():
1124 def test_run_cell_asyncio_run():
1122 ip.run_cell("import asyncio")
1125 ip.run_cell("import asyncio")
1123 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1126 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1124 assert ip.user_ns["_"] == 1
1127 assert ip.user_ns["_"] == 1
1125 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1128 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1126 assert ip.user_ns["_"] == 2
1129 assert ip.user_ns["_"] == 2
1127 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1130 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1128 assert ip.user_ns["_"] == 3
1131 assert ip.user_ns["_"] == 3
1129
1132
1130
1133
1131 def test_should_run_async():
1134 def test_should_run_async():
1132 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1135 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1133 assert ip.should_run_async("await x", transformed_cell="await x")
1136 assert ip.should_run_async("await x", transformed_cell="await x")
1134 assert ip.should_run_async(
1137 assert ip.should_run_async(
1135 "import asyncio; await asyncio.sleep(1)",
1138 "import asyncio; await asyncio.sleep(1)",
1136 transformed_cell="import asyncio; await asyncio.sleep(1)",
1139 transformed_cell="import asyncio; await asyncio.sleep(1)",
1137 )
1140 )
1138
1141
1139
1142
1140 def test_set_custom_completer():
1143 def test_set_custom_completer():
1141 num_completers = len(ip.Completer.matchers)
1144 num_completers = len(ip.Completer.matchers)
1142
1145
1143 def foo(*args, **kwargs):
1146 def foo(*args, **kwargs):
1144 return "I'm a completer!"
1147 return "I'm a completer!"
1145
1148
1146 ip.set_custom_completer(foo, 0)
1149 ip.set_custom_completer(foo, 0)
1147
1150
1148 # check that we've really added a new completer
1151 # check that we've really added a new completer
1149 assert len(ip.Completer.matchers) == num_completers + 1
1152 assert len(ip.Completer.matchers) == num_completers + 1
1150
1153
1151 # check that the first completer is the function we defined
1154 # check that the first completer is the function we defined
1152 assert ip.Completer.matchers[0]() == "I'm a completer!"
1155 assert ip.Completer.matchers[0]() == "I'm a completer!"
1153
1156
1154 # clean up
1157 # clean up
1155 ip.Completer.custom_matchers.pop()
1158 ip.Completer.custom_matchers.pop()
1156
1159
1157
1160
1158 class TestShowTracebackAttack(unittest.TestCase):
1161 class TestShowTracebackAttack(unittest.TestCase):
1159 """Test that the interactive shell is resilient against the client attack of
1162 """Test that the interactive shell is resilient against the client attack of
1160 manipulating the showtracebacks method. These attacks shouldn't result in an
1163 manipulating the showtracebacks method. These attacks shouldn't result in an
1161 unhandled exception in the kernel."""
1164 unhandled exception in the kernel."""
1162
1165
1163 def setUp(self):
1166 def setUp(self):
1164 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1167 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1165
1168
1166 def tearDown(self):
1169 def tearDown(self):
1167 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1170 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1168
1171
1169 def test_set_show_tracebacks_none(self):
1172 def test_set_show_tracebacks_none(self):
1170 """Test the case of the client setting showtracebacks to None"""
1173 """Test the case of the client setting showtracebacks to None"""
1171
1174
1172 result = ip.run_cell(
1175 result = ip.run_cell(
1173 """
1176 """
1174 import IPython.core.interactiveshell
1177 import IPython.core.interactiveshell
1175 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1178 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1176
1179
1177 assert False, "This should not raise an exception"
1180 assert False, "This should not raise an exception"
1178 """
1181 """
1179 )
1182 )
1180 print(result)
1183 print(result)
1181
1184
1182 assert result.result is None
1185 assert result.result is None
1183 assert isinstance(result.error_in_exec, TypeError)
1186 assert isinstance(result.error_in_exec, TypeError)
1184 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1187 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1185
1188
1186 def test_set_show_tracebacks_noop(self):
1189 def test_set_show_tracebacks_noop(self):
1187 """Test the case of the client setting showtracebacks to a no op lambda"""
1190 """Test the case of the client setting showtracebacks to a no op lambda"""
1188
1191
1189 result = ip.run_cell(
1192 result = ip.run_cell(
1190 """
1193 """
1191 import IPython.core.interactiveshell
1194 import IPython.core.interactiveshell
1192 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1195 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1193
1196
1194 assert False, "This should not raise an exception"
1197 assert False, "This should not raise an exception"
1195 """
1198 """
1196 )
1199 )
1197 print(result)
1200 print(result)
1198
1201
1199 assert result.result is None
1202 assert result.result is None
1200 assert isinstance(result.error_in_exec, AssertionError)
1203 assert isinstance(result.error_in_exec, AssertionError)
1201 assert str(result.error_in_exec) == "This should not raise an exception"
1204 assert str(result.error_in_exec) == "This should not raise an exception"
1205
1206
1207 @skip_if_not_osx
1208 def test_enable_gui_osx():
1209 simple_prompt = ip.simple_prompt
1210 ip.simple_prompt = False
1211
1212 ip.enable_gui("osx")
1213 assert ip.active_eventloop == "osx"
1214 ip.enable_gui()
1215
1216 # The following line fails for IPython <= 8.25.0
1217 ip.enable_gui("macosx")
1218 assert ip.active_eventloop == "osx"
1219 ip.enable_gui()
1220
1221 ip.simple_prompt = simple_prompt
@@ -1,1016 +1,1021 b''
1 """IPython terminal interface using prompt_toolkit"""
1 """IPython terminal interface using prompt_toolkit"""
2
2
3 import os
3 import os
4 import sys
4 import sys
5 import inspect
5 import inspect
6 from warnings import warn
6 from warnings import warn
7 from typing import Union as UnionType, Optional
7 from typing import Union as UnionType, Optional
8
8
9 from IPython.core.async_helpers import get_asyncio_loop
9 from IPython.core.async_helpers import get_asyncio_loop
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
10 from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC
11 from IPython.utils.py3compat import input
11 from IPython.utils.py3compat import input
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
12 from IPython.utils.terminal import toggle_set_term_title, set_term_title, restore_term_title
13 from IPython.utils.process import abbrev_cwd
13 from IPython.utils.process import abbrev_cwd
14 from traitlets import (
14 from traitlets import (
15 Bool,
15 Bool,
16 Unicode,
16 Unicode,
17 Dict,
17 Dict,
18 Integer,
18 Integer,
19 List,
19 List,
20 observe,
20 observe,
21 Instance,
21 Instance,
22 Type,
22 Type,
23 default,
23 default,
24 Enum,
24 Enum,
25 Union,
25 Union,
26 Any,
26 Any,
27 validate,
27 validate,
28 Float,
28 Float,
29 )
29 )
30
30
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
31 from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
32 from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
33 from prompt_toolkit.filters import HasFocus, Condition, IsDone
34 from prompt_toolkit.formatted_text import PygmentsTokens
34 from prompt_toolkit.formatted_text import PygmentsTokens
35 from prompt_toolkit.history import History
35 from prompt_toolkit.history import History
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
36 from prompt_toolkit.layout.processors import ConditionalProcessor, HighlightMatchingBracketProcessor
37 from prompt_toolkit.output import ColorDepth
37 from prompt_toolkit.output import ColorDepth
38 from prompt_toolkit.patch_stdout import patch_stdout
38 from prompt_toolkit.patch_stdout import patch_stdout
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
39 from prompt_toolkit.shortcuts import PromptSession, CompleteStyle, print_formatted_text
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
40 from prompt_toolkit.styles import DynamicStyle, merge_styles
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
41 from prompt_toolkit.styles.pygments import style_from_pygments_cls, style_from_pygments_dict
42 from prompt_toolkit import __version__ as ptk_version
42 from prompt_toolkit import __version__ as ptk_version
43
43
44 from pygments.styles import get_style_by_name
44 from pygments.styles import get_style_by_name
45 from pygments.style import Style
45 from pygments.style import Style
46 from pygments.token import Token
46 from pygments.token import Token
47
47
48 from .debugger import TerminalPdb, Pdb
48 from .debugger import TerminalPdb, Pdb
49 from .magics import TerminalMagics
49 from .magics import TerminalMagics
50 from .pt_inputhooks import get_inputhook_name_and_func
50 from .pt_inputhooks import get_inputhook_name_and_func
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
51 from .prompts import Prompts, ClassicPrompts, RichPromptDisplayHook
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
52 from .ptutils import IPythonPTCompleter, IPythonPTLexer
53 from .shortcuts import (
53 from .shortcuts import (
54 KEY_BINDINGS,
54 KEY_BINDINGS,
55 create_ipython_shortcuts,
55 create_ipython_shortcuts,
56 create_identifier,
56 create_identifier,
57 RuntimeBinding,
57 RuntimeBinding,
58 add_binding,
58 add_binding,
59 )
59 )
60 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
60 from .shortcuts.filters import KEYBINDING_FILTERS, filter_from_string
61 from .shortcuts.auto_suggest import (
61 from .shortcuts.auto_suggest import (
62 NavigableAutoSuggestFromHistory,
62 NavigableAutoSuggestFromHistory,
63 AppendAutoSuggestionInAnyLine,
63 AppendAutoSuggestionInAnyLine,
64 )
64 )
65
65
66 PTK3 = ptk_version.startswith('3.')
66 PTK3 = ptk_version.startswith('3.')
67
67
68
68
69 class _NoStyle(Style):
69 class _NoStyle(Style):
70 pass
70 pass
71
71
72
72
73 _style_overrides_light_bg = {
73 _style_overrides_light_bg = {
74 Token.Prompt: '#ansibrightblue',
74 Token.Prompt: '#ansibrightblue',
75 Token.PromptNum: '#ansiblue bold',
75 Token.PromptNum: '#ansiblue bold',
76 Token.OutPrompt: '#ansibrightred',
76 Token.OutPrompt: '#ansibrightred',
77 Token.OutPromptNum: '#ansired bold',
77 Token.OutPromptNum: '#ansired bold',
78 }
78 }
79
79
80 _style_overrides_linux = {
80 _style_overrides_linux = {
81 Token.Prompt: '#ansibrightgreen',
81 Token.Prompt: '#ansibrightgreen',
82 Token.PromptNum: '#ansigreen bold',
82 Token.PromptNum: '#ansigreen bold',
83 Token.OutPrompt: '#ansibrightred',
83 Token.OutPrompt: '#ansibrightred',
84 Token.OutPromptNum: '#ansired bold',
84 Token.OutPromptNum: '#ansired bold',
85 }
85 }
86
86
87
87
88 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
88 def _backward_compat_continuation_prompt_tokens(method, width: int, *, lineno: int):
89 """
89 """
90 Sagemath use custom prompt and we broke them in 8.19.
90 Sagemath use custom prompt and we broke them in 8.19.
91 """
91 """
92 sig = inspect.signature(method)
92 sig = inspect.signature(method)
93 if "lineno" in inspect.signature(method).parameters or any(
93 if "lineno" in inspect.signature(method).parameters or any(
94 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
94 [p.kind == p.VAR_KEYWORD for p in sig.parameters.values()]
95 ):
95 ):
96 return method(width, lineno=lineno)
96 return method(width, lineno=lineno)
97 else:
97 else:
98 return method(width)
98 return method(width)
99
99
100
100
101 def get_default_editor():
101 def get_default_editor():
102 try:
102 try:
103 return os.environ['EDITOR']
103 return os.environ['EDITOR']
104 except KeyError:
104 except KeyError:
105 pass
105 pass
106 except UnicodeError:
106 except UnicodeError:
107 warn("$EDITOR environment variable is not pure ASCII. Using platform "
107 warn("$EDITOR environment variable is not pure ASCII. Using platform "
108 "default editor.")
108 "default editor.")
109
109
110 if os.name == 'posix':
110 if os.name == 'posix':
111 return 'vi' # the only one guaranteed to be there!
111 return 'vi' # the only one guaranteed to be there!
112 else:
112 else:
113 return "notepad" # same in Windows!
113 return "notepad" # same in Windows!
114
114
115
115
116 # conservatively check for tty
116 # conservatively check for tty
117 # overridden streams can result in things like:
117 # overridden streams can result in things like:
118 # - sys.stdin = None
118 # - sys.stdin = None
119 # - no isatty method
119 # - no isatty method
120 for _name in ('stdin', 'stdout', 'stderr'):
120 for _name in ('stdin', 'stdout', 'stderr'):
121 _stream = getattr(sys, _name)
121 _stream = getattr(sys, _name)
122 try:
122 try:
123 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
123 if not _stream or not hasattr(_stream, "isatty") or not _stream.isatty():
124 _is_tty = False
124 _is_tty = False
125 break
125 break
126 except ValueError:
126 except ValueError:
127 # stream is closed
127 # stream is closed
128 _is_tty = False
128 _is_tty = False
129 break
129 break
130 else:
130 else:
131 _is_tty = True
131 _is_tty = True
132
132
133
133
134 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
134 _use_simple_prompt = ('IPY_TEST_SIMPLE_PROMPT' in os.environ) or (not _is_tty)
135
135
136 def black_reformat_handler(text_before_cursor):
136 def black_reformat_handler(text_before_cursor):
137 """
137 """
138 We do not need to protect against error,
138 We do not need to protect against error,
139 this is taken care at a higher level where any reformat error is ignored.
139 this is taken care at a higher level where any reformat error is ignored.
140 Indeed we may call reformatting on incomplete code.
140 Indeed we may call reformatting on incomplete code.
141 """
141 """
142 import black
142 import black
143
143
144 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
144 formatted_text = black.format_str(text_before_cursor, mode=black.FileMode())
145 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
145 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
146 formatted_text = formatted_text[:-1]
146 formatted_text = formatted_text[:-1]
147 return formatted_text
147 return formatted_text
148
148
149
149
150 def yapf_reformat_handler(text_before_cursor):
150 def yapf_reformat_handler(text_before_cursor):
151 from yapf.yapflib import file_resources
151 from yapf.yapflib import file_resources
152 from yapf.yapflib import yapf_api
152 from yapf.yapflib import yapf_api
153
153
154 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
154 style_config = file_resources.GetDefaultStyleForDir(os.getcwd())
155 formatted_text, was_formatted = yapf_api.FormatCode(
155 formatted_text, was_formatted = yapf_api.FormatCode(
156 text_before_cursor, style_config=style_config
156 text_before_cursor, style_config=style_config
157 )
157 )
158 if was_formatted:
158 if was_formatted:
159 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
159 if not text_before_cursor.endswith("\n") and formatted_text.endswith("\n"):
160 formatted_text = formatted_text[:-1]
160 formatted_text = formatted_text[:-1]
161 return formatted_text
161 return formatted_text
162 else:
162 else:
163 return text_before_cursor
163 return text_before_cursor
164
164
165
165
166 class PtkHistoryAdapter(History):
166 class PtkHistoryAdapter(History):
167 """
167 """
168 Prompt toolkit has it's own way of handling history, Where it assumes it can
168 Prompt toolkit has it's own way of handling history, Where it assumes it can
169 Push/pull from history.
169 Push/pull from history.
170
170
171 """
171 """
172
172
173 def __init__(self, shell):
173 def __init__(self, shell):
174 super().__init__()
174 super().__init__()
175 self.shell = shell
175 self.shell = shell
176 self._refresh()
176 self._refresh()
177
177
178 def append_string(self, string):
178 def append_string(self, string):
179 # we rely on sql for that.
179 # we rely on sql for that.
180 self._loaded = False
180 self._loaded = False
181 self._refresh()
181 self._refresh()
182
182
183 def _refresh(self):
183 def _refresh(self):
184 if not self._loaded:
184 if not self._loaded:
185 self._loaded_strings = list(self.load_history_strings())
185 self._loaded_strings = list(self.load_history_strings())
186
186
187 def load_history_strings(self):
187 def load_history_strings(self):
188 last_cell = ""
188 last_cell = ""
189 res = []
189 res = []
190 for __, ___, cell in self.shell.history_manager.get_tail(
190 for __, ___, cell in self.shell.history_manager.get_tail(
191 self.shell.history_load_length, include_latest=True
191 self.shell.history_load_length, include_latest=True
192 ):
192 ):
193 # Ignore blank lines and consecutive duplicates
193 # Ignore blank lines and consecutive duplicates
194 cell = cell.rstrip()
194 cell = cell.rstrip()
195 if cell and (cell != last_cell):
195 if cell and (cell != last_cell):
196 res.append(cell)
196 res.append(cell)
197 last_cell = cell
197 last_cell = cell
198 yield from res[::-1]
198 yield from res[::-1]
199
199
200 def store_string(self, string: str) -> None:
200 def store_string(self, string: str) -> None:
201 pass
201 pass
202
202
203 class TerminalInteractiveShell(InteractiveShell):
203 class TerminalInteractiveShell(InteractiveShell):
204 mime_renderers = Dict().tag(config=True)
204 mime_renderers = Dict().tag(config=True)
205
205
206 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
206 space_for_menu = Integer(6, help='Number of line at the bottom of the screen '
207 'to reserve for the tab completion menu, '
207 'to reserve for the tab completion menu, '
208 'search history, ...etc, the height of '
208 'search history, ...etc, the height of '
209 'these menus will at most this value. '
209 'these menus will at most this value. '
210 'Increase it is you prefer long and skinny '
210 'Increase it is you prefer long and skinny '
211 'menus, decrease for short and wide.'
211 'menus, decrease for short and wide.'
212 ).tag(config=True)
212 ).tag(config=True)
213
213
214 pt_app: UnionType[PromptSession, None] = None
214 pt_app: UnionType[PromptSession, None] = None
215 auto_suggest: UnionType[
215 auto_suggest: UnionType[
216 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
216 AutoSuggestFromHistory, NavigableAutoSuggestFromHistory, None
217 ] = None
217 ] = None
218 debugger_history = None
218 debugger_history = None
219
219
220 debugger_history_file = Unicode(
220 debugger_history_file = Unicode(
221 "~/.pdbhistory", help="File in which to store and read history"
221 "~/.pdbhistory", help="File in which to store and read history"
222 ).tag(config=True)
222 ).tag(config=True)
223
223
224 simple_prompt = Bool(_use_simple_prompt,
224 simple_prompt = Bool(_use_simple_prompt,
225 help="""Use `raw_input` for the REPL, without completion and prompt colors.
225 help="""Use `raw_input` for the REPL, without completion and prompt colors.
226
226
227 Useful when controlling IPython as a subprocess, and piping
227 Useful when controlling IPython as a subprocess, and piping
228 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
228 STDIN/OUT/ERR. Known usage are: IPython's own testing machinery,
229 and emacs' inferior-python subprocess (assuming you have set
229 and emacs' inferior-python subprocess (assuming you have set
230 `python-shell-interpreter` to "ipython") available through the
230 `python-shell-interpreter` to "ipython") available through the
231 built-in `M-x run-python` and third party packages such as elpy.
231 built-in `M-x run-python` and third party packages such as elpy.
232
232
233 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
233 This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT`
234 environment variable is set, or the current terminal is not a tty.
234 environment variable is set, or the current terminal is not a tty.
235 Thus the Default value reported in --help-all, or config will often
235 Thus the Default value reported in --help-all, or config will often
236 be incorrectly reported.
236 be incorrectly reported.
237 """,
237 """,
238 ).tag(config=True)
238 ).tag(config=True)
239
239
240 @property
240 @property
241 def debugger_cls(self):
241 def debugger_cls(self):
242 return Pdb if self.simple_prompt else TerminalPdb
242 return Pdb if self.simple_prompt else TerminalPdb
243
243
244 confirm_exit = Bool(True,
244 confirm_exit = Bool(True,
245 help="""
245 help="""
246 Set to confirm when you try to exit IPython with an EOF (Control-D
246 Set to confirm when you try to exit IPython with an EOF (Control-D
247 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
247 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
248 you can force a direct exit without any confirmation.""",
248 you can force a direct exit without any confirmation.""",
249 ).tag(config=True)
249 ).tag(config=True)
250
250
251 editing_mode = Unicode('emacs',
251 editing_mode = Unicode('emacs',
252 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
252 help="Shortcut style to use at the prompt. 'vi' or 'emacs'.",
253 ).tag(config=True)
253 ).tag(config=True)
254
254
255 emacs_bindings_in_vi_insert_mode = Bool(
255 emacs_bindings_in_vi_insert_mode = Bool(
256 True,
256 True,
257 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
257 help="Add shortcuts from 'emacs' insert mode to 'vi' insert mode.",
258 ).tag(config=True)
258 ).tag(config=True)
259
259
260 modal_cursor = Bool(
260 modal_cursor = Bool(
261 True,
261 True,
262 help="""
262 help="""
263 Cursor shape changes depending on vi mode: beam in vi insert mode,
263 Cursor shape changes depending on vi mode: beam in vi insert mode,
264 block in nav mode, underscore in replace mode.""",
264 block in nav mode, underscore in replace mode.""",
265 ).tag(config=True)
265 ).tag(config=True)
266
266
267 ttimeoutlen = Float(
267 ttimeoutlen = Float(
268 0.01,
268 0.01,
269 help="""The time in milliseconds that is waited for a key code
269 help="""The time in milliseconds that is waited for a key code
270 to complete.""",
270 to complete.""",
271 ).tag(config=True)
271 ).tag(config=True)
272
272
273 timeoutlen = Float(
273 timeoutlen = Float(
274 0.5,
274 0.5,
275 help="""The time in milliseconds that is waited for a mapped key
275 help="""The time in milliseconds that is waited for a mapped key
276 sequence to complete.""",
276 sequence to complete.""",
277 ).tag(config=True)
277 ).tag(config=True)
278
278
279 autoformatter = Unicode(
279 autoformatter = Unicode(
280 None,
280 None,
281 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
281 help="Autoformatter to reformat Terminal code. Can be `'black'`, `'yapf'` or `None`",
282 allow_none=True
282 allow_none=True
283 ).tag(config=True)
283 ).tag(config=True)
284
284
285 auto_match = Bool(
285 auto_match = Bool(
286 False,
286 False,
287 help="""
287 help="""
288 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
288 Automatically add/delete closing bracket or quote when opening bracket or quote is entered/deleted.
289 Brackets: (), [], {}
289 Brackets: (), [], {}
290 Quotes: '', \"\"
290 Quotes: '', \"\"
291 """,
291 """,
292 ).tag(config=True)
292 ).tag(config=True)
293
293
294 mouse_support = Bool(False,
294 mouse_support = Bool(False,
295 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
295 help="Enable mouse support in the prompt\n(Note: prevents selecting text with the mouse)"
296 ).tag(config=True)
296 ).tag(config=True)
297
297
298 # We don't load the list of styles for the help string, because loading
298 # We don't load the list of styles for the help string, because loading
299 # Pygments plugins takes time and can cause unexpected errors.
299 # Pygments plugins takes time and can cause unexpected errors.
300 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
300 highlighting_style = Union([Unicode('legacy'), Type(klass=Style)],
301 help="""The name or class of a Pygments style to use for syntax
301 help="""The name or class of a Pygments style to use for syntax
302 highlighting. To see available styles, run `pygmentize -L styles`."""
302 highlighting. To see available styles, run `pygmentize -L styles`."""
303 ).tag(config=True)
303 ).tag(config=True)
304
304
305 @validate('editing_mode')
305 @validate('editing_mode')
306 def _validate_editing_mode(self, proposal):
306 def _validate_editing_mode(self, proposal):
307 if proposal['value'].lower() == 'vim':
307 if proposal['value'].lower() == 'vim':
308 proposal['value']= 'vi'
308 proposal['value']= 'vi'
309 elif proposal['value'].lower() == 'default':
309 elif proposal['value'].lower() == 'default':
310 proposal['value']= 'emacs'
310 proposal['value']= 'emacs'
311
311
312 if hasattr(EditingMode, proposal['value'].upper()):
312 if hasattr(EditingMode, proposal['value'].upper()):
313 return proposal['value'].lower()
313 return proposal['value'].lower()
314
314
315 return self.editing_mode
315 return self.editing_mode
316
316
317 @observe('editing_mode')
317 @observe('editing_mode')
318 def _editing_mode(self, change):
318 def _editing_mode(self, change):
319 if self.pt_app:
319 if self.pt_app:
320 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
320 self.pt_app.editing_mode = getattr(EditingMode, change.new.upper())
321
321
322 def _set_formatter(self, formatter):
322 def _set_formatter(self, formatter):
323 if formatter is None:
323 if formatter is None:
324 self.reformat_handler = lambda x:x
324 self.reformat_handler = lambda x:x
325 elif formatter == 'black':
325 elif formatter == 'black':
326 self.reformat_handler = black_reformat_handler
326 self.reformat_handler = black_reformat_handler
327 elif formatter == "yapf":
327 elif formatter == "yapf":
328 self.reformat_handler = yapf_reformat_handler
328 self.reformat_handler = yapf_reformat_handler
329 else:
329 else:
330 raise ValueError
330 raise ValueError
331
331
332 @observe("autoformatter")
332 @observe("autoformatter")
333 def _autoformatter_changed(self, change):
333 def _autoformatter_changed(self, change):
334 formatter = change.new
334 formatter = change.new
335 self._set_formatter(formatter)
335 self._set_formatter(formatter)
336
336
337 @observe('highlighting_style')
337 @observe('highlighting_style')
338 @observe('colors')
338 @observe('colors')
339 def _highlighting_style_changed(self, change):
339 def _highlighting_style_changed(self, change):
340 self.refresh_style()
340 self.refresh_style()
341
341
342 def refresh_style(self):
342 def refresh_style(self):
343 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
343 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
344
344
345 highlighting_style_overrides = Dict(
345 highlighting_style_overrides = Dict(
346 help="Override highlighting format for specific tokens"
346 help="Override highlighting format for specific tokens"
347 ).tag(config=True)
347 ).tag(config=True)
348
348
349 true_color = Bool(False,
349 true_color = Bool(False,
350 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
350 help="""Use 24bit colors instead of 256 colors in prompt highlighting.
351 If your terminal supports true color, the following command should
351 If your terminal supports true color, the following command should
352 print ``TRUECOLOR`` in orange::
352 print ``TRUECOLOR`` in orange::
353
353
354 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
354 printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"
355 """,
355 """,
356 ).tag(config=True)
356 ).tag(config=True)
357
357
358 editor = Unicode(get_default_editor(),
358 editor = Unicode(get_default_editor(),
359 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
359 help="Set the editor used by IPython (default to $EDITOR/vi/notepad)."
360 ).tag(config=True)
360 ).tag(config=True)
361
361
362 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
362 prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True)
363
363
364 prompts = Instance(Prompts)
364 prompts = Instance(Prompts)
365
365
366 @default('prompts')
366 @default('prompts')
367 def _prompts_default(self):
367 def _prompts_default(self):
368 return self.prompts_class(self)
368 return self.prompts_class(self)
369
369
370 # @observe('prompts')
370 # @observe('prompts')
371 # def _(self, change):
371 # def _(self, change):
372 # self._update_layout()
372 # self._update_layout()
373
373
374 @default('displayhook_class')
374 @default('displayhook_class')
375 def _displayhook_class_default(self):
375 def _displayhook_class_default(self):
376 return RichPromptDisplayHook
376 return RichPromptDisplayHook
377
377
378 term_title = Bool(True,
378 term_title = Bool(True,
379 help="Automatically set the terminal title"
379 help="Automatically set the terminal title"
380 ).tag(config=True)
380 ).tag(config=True)
381
381
382 term_title_format = Unicode("IPython: {cwd}",
382 term_title_format = Unicode("IPython: {cwd}",
383 help="Customize the terminal title format. This is a python format string. " +
383 help="Customize the terminal title format. This is a python format string. " +
384 "Available substitutions are: {cwd}."
384 "Available substitutions are: {cwd}."
385 ).tag(config=True)
385 ).tag(config=True)
386
386
387 display_completions = Enum(('column', 'multicolumn','readlinelike'),
387 display_completions = Enum(('column', 'multicolumn','readlinelike'),
388 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
388 help= ( "Options for displaying tab completions, 'column', 'multicolumn', and "
389 "'readlinelike'. These options are for `prompt_toolkit`, see "
389 "'readlinelike'. These options are for `prompt_toolkit`, see "
390 "`prompt_toolkit` documentation for more information."
390 "`prompt_toolkit` documentation for more information."
391 ),
391 ),
392 default_value='multicolumn').tag(config=True)
392 default_value='multicolumn').tag(config=True)
393
393
394 highlight_matching_brackets = Bool(True,
394 highlight_matching_brackets = Bool(True,
395 help="Highlight matching brackets.",
395 help="Highlight matching brackets.",
396 ).tag(config=True)
396 ).tag(config=True)
397
397
398 extra_open_editor_shortcuts = Bool(False,
398 extra_open_editor_shortcuts = Bool(False,
399 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
399 help="Enable vi (v) or Emacs (C-X C-E) shortcuts to open an external editor. "
400 "This is in addition to the F2 binding, which is always enabled."
400 "This is in addition to the F2 binding, which is always enabled."
401 ).tag(config=True)
401 ).tag(config=True)
402
402
403 handle_return = Any(None,
403 handle_return = Any(None,
404 help="Provide an alternative handler to be called when the user presses "
404 help="Provide an alternative handler to be called when the user presses "
405 "Return. This is an advanced option intended for debugging, which "
405 "Return. This is an advanced option intended for debugging, which "
406 "may be changed or removed in later releases."
406 "may be changed or removed in later releases."
407 ).tag(config=True)
407 ).tag(config=True)
408
408
409 enable_history_search = Bool(True,
409 enable_history_search = Bool(True,
410 help="Allows to enable/disable the prompt toolkit history search"
410 help="Allows to enable/disable the prompt toolkit history search"
411 ).tag(config=True)
411 ).tag(config=True)
412
412
413 autosuggestions_provider = Unicode(
413 autosuggestions_provider = Unicode(
414 "NavigableAutoSuggestFromHistory",
414 "NavigableAutoSuggestFromHistory",
415 help="Specifies from which source automatic suggestions are provided. "
415 help="Specifies from which source automatic suggestions are provided. "
416 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
416 "Can be set to ``'NavigableAutoSuggestFromHistory'`` (:kbd:`up` and "
417 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
417 ":kbd:`down` swap suggestions), ``'AutoSuggestFromHistory'``, "
418 " or ``None`` to disable automatic suggestions. "
418 " or ``None`` to disable automatic suggestions. "
419 "Default is `'NavigableAutoSuggestFromHistory`'.",
419 "Default is `'NavigableAutoSuggestFromHistory`'.",
420 allow_none=True,
420 allow_none=True,
421 ).tag(config=True)
421 ).tag(config=True)
422
422
423 def _set_autosuggestions(self, provider):
423 def _set_autosuggestions(self, provider):
424 # disconnect old handler
424 # disconnect old handler
425 if self.auto_suggest and isinstance(
425 if self.auto_suggest and isinstance(
426 self.auto_suggest, NavigableAutoSuggestFromHistory
426 self.auto_suggest, NavigableAutoSuggestFromHistory
427 ):
427 ):
428 self.auto_suggest.disconnect()
428 self.auto_suggest.disconnect()
429 if provider is None:
429 if provider is None:
430 self.auto_suggest = None
430 self.auto_suggest = None
431 elif provider == "AutoSuggestFromHistory":
431 elif provider == "AutoSuggestFromHistory":
432 self.auto_suggest = AutoSuggestFromHistory()
432 self.auto_suggest = AutoSuggestFromHistory()
433 elif provider == "NavigableAutoSuggestFromHistory":
433 elif provider == "NavigableAutoSuggestFromHistory":
434 self.auto_suggest = NavigableAutoSuggestFromHistory()
434 self.auto_suggest = NavigableAutoSuggestFromHistory()
435 else:
435 else:
436 raise ValueError("No valid provider.")
436 raise ValueError("No valid provider.")
437 if self.pt_app:
437 if self.pt_app:
438 self.pt_app.auto_suggest = self.auto_suggest
438 self.pt_app.auto_suggest = self.auto_suggest
439
439
440 @observe("autosuggestions_provider")
440 @observe("autosuggestions_provider")
441 def _autosuggestions_provider_changed(self, change):
441 def _autosuggestions_provider_changed(self, change):
442 provider = change.new
442 provider = change.new
443 self._set_autosuggestions(provider)
443 self._set_autosuggestions(provider)
444
444
445 shortcuts = List(
445 shortcuts = List(
446 trait=Dict(
446 trait=Dict(
447 key_trait=Enum(
447 key_trait=Enum(
448 [
448 [
449 "command",
449 "command",
450 "match_keys",
450 "match_keys",
451 "match_filter",
451 "match_filter",
452 "new_keys",
452 "new_keys",
453 "new_filter",
453 "new_filter",
454 "create",
454 "create",
455 ]
455 ]
456 ),
456 ),
457 per_key_traits={
457 per_key_traits={
458 "command": Unicode(),
458 "command": Unicode(),
459 "match_keys": List(Unicode()),
459 "match_keys": List(Unicode()),
460 "match_filter": Unicode(),
460 "match_filter": Unicode(),
461 "new_keys": List(Unicode()),
461 "new_keys": List(Unicode()),
462 "new_filter": Unicode(),
462 "new_filter": Unicode(),
463 "create": Bool(False),
463 "create": Bool(False),
464 },
464 },
465 ),
465 ),
466 help="""Add, disable or modifying shortcuts.
466 help="""Add, disable or modifying shortcuts.
467
467
468 Each entry on the list should be a dictionary with ``command`` key
468 Each entry on the list should be a dictionary with ``command`` key
469 identifying the target function executed by the shortcut and at least
469 identifying the target function executed by the shortcut and at least
470 one of the following:
470 one of the following:
471
471
472 - ``match_keys``: list of keys used to match an existing shortcut,
472 - ``match_keys``: list of keys used to match an existing shortcut,
473 - ``match_filter``: shortcut filter used to match an existing shortcut,
473 - ``match_filter``: shortcut filter used to match an existing shortcut,
474 - ``new_keys``: list of keys to set,
474 - ``new_keys``: list of keys to set,
475 - ``new_filter``: a new shortcut filter to set
475 - ``new_filter``: a new shortcut filter to set
476
476
477 The filters have to be composed of pre-defined verbs and joined by one
477 The filters have to be composed of pre-defined verbs and joined by one
478 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
478 of the following conjunctions: ``&`` (and), ``|`` (or), ``~`` (not).
479 The pre-defined verbs are:
479 The pre-defined verbs are:
480
480
481 {}
481 {}
482
482
483
483
484 To disable a shortcut set ``new_keys`` to an empty list.
484 To disable a shortcut set ``new_keys`` to an empty list.
485 To add a shortcut add key ``create`` with value ``True``.
485 To add a shortcut add key ``create`` with value ``True``.
486
486
487 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
487 When modifying/disabling shortcuts, ``match_keys``/``match_filter`` can
488 be omitted if the provided specification uniquely identifies a shortcut
488 be omitted if the provided specification uniquely identifies a shortcut
489 to be modified/disabled. When modifying a shortcut ``new_filter`` or
489 to be modified/disabled. When modifying a shortcut ``new_filter`` or
490 ``new_keys`` can be omitted which will result in reuse of the existing
490 ``new_keys`` can be omitted which will result in reuse of the existing
491 filter/keys.
491 filter/keys.
492
492
493 Only shortcuts defined in IPython (and not default prompt-toolkit
493 Only shortcuts defined in IPython (and not default prompt-toolkit
494 shortcuts) can be modified or disabled. The full list of shortcuts,
494 shortcuts) can be modified or disabled. The full list of shortcuts,
495 command identifiers and filters is available under
495 command identifiers and filters is available under
496 :ref:`terminal-shortcuts-list`.
496 :ref:`terminal-shortcuts-list`.
497 """.format(
497 """.format(
498 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
498 "\n ".join([f"- `{k}`" for k in KEYBINDING_FILTERS])
499 ),
499 ),
500 ).tag(config=True)
500 ).tag(config=True)
501
501
502 @observe("shortcuts")
502 @observe("shortcuts")
503 def _shortcuts_changed(self, change):
503 def _shortcuts_changed(self, change):
504 if self.pt_app:
504 if self.pt_app:
505 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
505 self.pt_app.key_bindings = self._merge_shortcuts(user_shortcuts=change.new)
506
506
507 def _merge_shortcuts(self, user_shortcuts):
507 def _merge_shortcuts(self, user_shortcuts):
508 # rebuild the bindings list from scratch
508 # rebuild the bindings list from scratch
509 key_bindings = create_ipython_shortcuts(self)
509 key_bindings = create_ipython_shortcuts(self)
510
510
511 # for now we only allow adding shortcuts for commands which are already
511 # for now we only allow adding shortcuts for commands which are already
512 # registered; this is a security precaution.
512 # registered; this is a security precaution.
513 known_commands = {
513 known_commands = {
514 create_identifier(binding.command): binding.command
514 create_identifier(binding.command): binding.command
515 for binding in KEY_BINDINGS
515 for binding in KEY_BINDINGS
516 }
516 }
517 shortcuts_to_skip = []
517 shortcuts_to_skip = []
518 shortcuts_to_add = []
518 shortcuts_to_add = []
519
519
520 for shortcut in user_shortcuts:
520 for shortcut in user_shortcuts:
521 command_id = shortcut["command"]
521 command_id = shortcut["command"]
522 if command_id not in known_commands:
522 if command_id not in known_commands:
523 allowed_commands = "\n - ".join(known_commands)
523 allowed_commands = "\n - ".join(known_commands)
524 raise ValueError(
524 raise ValueError(
525 f"{command_id} is not a known shortcut command."
525 f"{command_id} is not a known shortcut command."
526 f" Allowed commands are: \n - {allowed_commands}"
526 f" Allowed commands are: \n - {allowed_commands}"
527 )
527 )
528 old_keys = shortcut.get("match_keys", None)
528 old_keys = shortcut.get("match_keys", None)
529 old_filter = (
529 old_filter = (
530 filter_from_string(shortcut["match_filter"])
530 filter_from_string(shortcut["match_filter"])
531 if "match_filter" in shortcut
531 if "match_filter" in shortcut
532 else None
532 else None
533 )
533 )
534 matching = [
534 matching = [
535 binding
535 binding
536 for binding in KEY_BINDINGS
536 for binding in KEY_BINDINGS
537 if (
537 if (
538 (old_filter is None or binding.filter == old_filter)
538 (old_filter is None or binding.filter == old_filter)
539 and (old_keys is None or [k for k in binding.keys] == old_keys)
539 and (old_keys is None or [k for k in binding.keys] == old_keys)
540 and create_identifier(binding.command) == command_id
540 and create_identifier(binding.command) == command_id
541 )
541 )
542 ]
542 ]
543
543
544 new_keys = shortcut.get("new_keys", None)
544 new_keys = shortcut.get("new_keys", None)
545 new_filter = shortcut.get("new_filter", None)
545 new_filter = shortcut.get("new_filter", None)
546
546
547 command = known_commands[command_id]
547 command = known_commands[command_id]
548
548
549 creating_new = shortcut.get("create", False)
549 creating_new = shortcut.get("create", False)
550 modifying_existing = not creating_new and (
550 modifying_existing = not creating_new and (
551 new_keys is not None or new_filter
551 new_keys is not None or new_filter
552 )
552 )
553
553
554 if creating_new and new_keys == []:
554 if creating_new and new_keys == []:
555 raise ValueError("Cannot add a shortcut without keys")
555 raise ValueError("Cannot add a shortcut without keys")
556
556
557 if modifying_existing:
557 if modifying_existing:
558 specification = {
558 specification = {
559 key: shortcut[key]
559 key: shortcut[key]
560 for key in ["command", "filter"]
560 for key in ["command", "filter"]
561 if key in shortcut
561 if key in shortcut
562 }
562 }
563 if len(matching) == 0:
563 if len(matching) == 0:
564 raise ValueError(
564 raise ValueError(
565 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
565 f"No shortcuts matching {specification} found in {KEY_BINDINGS}"
566 )
566 )
567 elif len(matching) > 1:
567 elif len(matching) > 1:
568 raise ValueError(
568 raise ValueError(
569 f"Multiple shortcuts matching {specification} found,"
569 f"Multiple shortcuts matching {specification} found,"
570 f" please add keys/filter to select one of: {matching}"
570 f" please add keys/filter to select one of: {matching}"
571 )
571 )
572
572
573 matched = matching[0]
573 matched = matching[0]
574 old_filter = matched.filter
574 old_filter = matched.filter
575 old_keys = list(matched.keys)
575 old_keys = list(matched.keys)
576 shortcuts_to_skip.append(
576 shortcuts_to_skip.append(
577 RuntimeBinding(
577 RuntimeBinding(
578 command,
578 command,
579 keys=old_keys,
579 keys=old_keys,
580 filter=old_filter,
580 filter=old_filter,
581 )
581 )
582 )
582 )
583
583
584 if new_keys != []:
584 if new_keys != []:
585 shortcuts_to_add.append(
585 shortcuts_to_add.append(
586 RuntimeBinding(
586 RuntimeBinding(
587 command,
587 command,
588 keys=new_keys or old_keys,
588 keys=new_keys or old_keys,
589 filter=filter_from_string(new_filter)
589 filter=filter_from_string(new_filter)
590 if new_filter is not None
590 if new_filter is not None
591 else (
591 else (
592 old_filter
592 old_filter
593 if old_filter is not None
593 if old_filter is not None
594 else filter_from_string("always")
594 else filter_from_string("always")
595 ),
595 ),
596 )
596 )
597 )
597 )
598
598
599 # rebuild the bindings list from scratch
599 # rebuild the bindings list from scratch
600 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
600 key_bindings = create_ipython_shortcuts(self, skip=shortcuts_to_skip)
601 for binding in shortcuts_to_add:
601 for binding in shortcuts_to_add:
602 add_binding(key_bindings, binding)
602 add_binding(key_bindings, binding)
603
603
604 return key_bindings
604 return key_bindings
605
605
606 prompt_includes_vi_mode = Bool(True,
606 prompt_includes_vi_mode = Bool(True,
607 help="Display the current vi mode (when using vi editing mode)."
607 help="Display the current vi mode (when using vi editing mode)."
608 ).tag(config=True)
608 ).tag(config=True)
609
609
610 prompt_line_number_format = Unicode(
610 prompt_line_number_format = Unicode(
611 "",
611 "",
612 help="The format for line numbering, will be passed `line` (int, 1 based)"
612 help="The format for line numbering, will be passed `line` (int, 1 based)"
613 " the current line number and `rel_line` the relative line number."
613 " the current line number and `rel_line` the relative line number."
614 " for example to display both you can use the following template string :"
614 " for example to display both you can use the following template string :"
615 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
615 " c.TerminalInteractiveShell.prompt_line_number_format='{line: 4d}/{rel_line:+03d} | '"
616 " This will display the current line number, with leading space and a width of at least 4"
616 " This will display the current line number, with leading space and a width of at least 4"
617 " character, as well as the relative line number 0 padded and always with a + or - sign."
617 " character, as well as the relative line number 0 padded and always with a + or - sign."
618 " Note that when using Emacs mode the prompt of the first line may not update.",
618 " Note that when using Emacs mode the prompt of the first line may not update.",
619 ).tag(config=True)
619 ).tag(config=True)
620
620
621 @observe('term_title')
621 @observe('term_title')
622 def init_term_title(self, change=None):
622 def init_term_title(self, change=None):
623 # Enable or disable the terminal title.
623 # Enable or disable the terminal title.
624 if self.term_title and _is_tty:
624 if self.term_title and _is_tty:
625 toggle_set_term_title(True)
625 toggle_set_term_title(True)
626 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
626 set_term_title(self.term_title_format.format(cwd=abbrev_cwd()))
627 else:
627 else:
628 toggle_set_term_title(False)
628 toggle_set_term_title(False)
629
629
630 def restore_term_title(self):
630 def restore_term_title(self):
631 if self.term_title and _is_tty:
631 if self.term_title and _is_tty:
632 restore_term_title()
632 restore_term_title()
633
633
634 def init_display_formatter(self):
634 def init_display_formatter(self):
635 super(TerminalInteractiveShell, self).init_display_formatter()
635 super(TerminalInteractiveShell, self).init_display_formatter()
636 # terminal only supports plain text
636 # terminal only supports plain text
637 self.display_formatter.active_types = ["text/plain"]
637 self.display_formatter.active_types = ["text/plain"]
638
638
639 def init_prompt_toolkit_cli(self):
639 def init_prompt_toolkit_cli(self):
640 if self.simple_prompt:
640 if self.simple_prompt:
641 # Fall back to plain non-interactive output for tests.
641 # Fall back to plain non-interactive output for tests.
642 # This is very limited.
642 # This is very limited.
643 def prompt():
643 def prompt():
644 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
644 prompt_text = "".join(x[1] for x in self.prompts.in_prompt_tokens())
645 lines = [input(prompt_text)]
645 lines = [input(prompt_text)]
646 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
646 prompt_continuation = "".join(x[1] for x in self.prompts.continuation_prompt_tokens())
647 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
647 while self.check_complete('\n'.join(lines))[0] == 'incomplete':
648 lines.append( input(prompt_continuation) )
648 lines.append( input(prompt_continuation) )
649 return '\n'.join(lines)
649 return '\n'.join(lines)
650 self.prompt_for_code = prompt
650 self.prompt_for_code = prompt
651 return
651 return
652
652
653 # Set up keyboard shortcuts
653 # Set up keyboard shortcuts
654 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
654 key_bindings = self._merge_shortcuts(user_shortcuts=self.shortcuts)
655
655
656 # Pre-populate history from IPython's history database
656 # Pre-populate history from IPython's history database
657 history = PtkHistoryAdapter(self)
657 history = PtkHistoryAdapter(self)
658
658
659 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
659 self._style = self._make_style_from_name_or_cls(self.highlighting_style)
660 self.style = DynamicStyle(lambda: self._style)
660 self.style = DynamicStyle(lambda: self._style)
661
661
662 editing_mode = getattr(EditingMode, self.editing_mode.upper())
662 editing_mode = getattr(EditingMode, self.editing_mode.upper())
663
663
664 self._use_asyncio_inputhook = False
664 self._use_asyncio_inputhook = False
665 self.pt_app = PromptSession(
665 self.pt_app = PromptSession(
666 auto_suggest=self.auto_suggest,
666 auto_suggest=self.auto_suggest,
667 editing_mode=editing_mode,
667 editing_mode=editing_mode,
668 key_bindings=key_bindings,
668 key_bindings=key_bindings,
669 history=history,
669 history=history,
670 completer=IPythonPTCompleter(shell=self),
670 completer=IPythonPTCompleter(shell=self),
671 enable_history_search=self.enable_history_search,
671 enable_history_search=self.enable_history_search,
672 style=self.style,
672 style=self.style,
673 include_default_pygments_style=False,
673 include_default_pygments_style=False,
674 mouse_support=self.mouse_support,
674 mouse_support=self.mouse_support,
675 enable_open_in_editor=self.extra_open_editor_shortcuts,
675 enable_open_in_editor=self.extra_open_editor_shortcuts,
676 color_depth=self.color_depth,
676 color_depth=self.color_depth,
677 tempfile_suffix=".py",
677 tempfile_suffix=".py",
678 **self._extra_prompt_options(),
678 **self._extra_prompt_options(),
679 )
679 )
680 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
680 if isinstance(self.auto_suggest, NavigableAutoSuggestFromHistory):
681 self.auto_suggest.connect(self.pt_app)
681 self.auto_suggest.connect(self.pt_app)
682
682
683 def _make_style_from_name_or_cls(self, name_or_cls):
683 def _make_style_from_name_or_cls(self, name_or_cls):
684 """
684 """
685 Small wrapper that make an IPython compatible style from a style name
685 Small wrapper that make an IPython compatible style from a style name
686
686
687 We need that to add style for prompt ... etc.
687 We need that to add style for prompt ... etc.
688 """
688 """
689 style_overrides = {}
689 style_overrides = {}
690 if name_or_cls == 'legacy':
690 if name_or_cls == 'legacy':
691 legacy = self.colors.lower()
691 legacy = self.colors.lower()
692 if legacy == 'linux':
692 if legacy == 'linux':
693 style_cls = get_style_by_name('monokai')
693 style_cls = get_style_by_name('monokai')
694 style_overrides = _style_overrides_linux
694 style_overrides = _style_overrides_linux
695 elif legacy == 'lightbg':
695 elif legacy == 'lightbg':
696 style_overrides = _style_overrides_light_bg
696 style_overrides = _style_overrides_light_bg
697 style_cls = get_style_by_name('pastie')
697 style_cls = get_style_by_name('pastie')
698 elif legacy == 'neutral':
698 elif legacy == 'neutral':
699 # The default theme needs to be visible on both a dark background
699 # The default theme needs to be visible on both a dark background
700 # and a light background, because we can't tell what the terminal
700 # and a light background, because we can't tell what the terminal
701 # looks like. These tweaks to the default theme help with that.
701 # looks like. These tweaks to the default theme help with that.
702 style_cls = get_style_by_name('default')
702 style_cls = get_style_by_name('default')
703 style_overrides.update({
703 style_overrides.update({
704 Token.Number: '#ansigreen',
704 Token.Number: '#ansigreen',
705 Token.Operator: 'noinherit',
705 Token.Operator: 'noinherit',
706 Token.String: '#ansiyellow',
706 Token.String: '#ansiyellow',
707 Token.Name.Function: '#ansiblue',
707 Token.Name.Function: '#ansiblue',
708 Token.Name.Class: 'bold #ansiblue',
708 Token.Name.Class: 'bold #ansiblue',
709 Token.Name.Namespace: 'bold #ansiblue',
709 Token.Name.Namespace: 'bold #ansiblue',
710 Token.Name.Variable.Magic: '#ansiblue',
710 Token.Name.Variable.Magic: '#ansiblue',
711 Token.Prompt: '#ansigreen',
711 Token.Prompt: '#ansigreen',
712 Token.PromptNum: '#ansibrightgreen bold',
712 Token.PromptNum: '#ansibrightgreen bold',
713 Token.OutPrompt: '#ansired',
713 Token.OutPrompt: '#ansired',
714 Token.OutPromptNum: '#ansibrightred bold',
714 Token.OutPromptNum: '#ansibrightred bold',
715 })
715 })
716
716
717 # Hack: Due to limited color support on the Windows console
717 # Hack: Due to limited color support on the Windows console
718 # the prompt colors will be wrong without this
718 # the prompt colors will be wrong without this
719 if os.name == 'nt':
719 if os.name == 'nt':
720 style_overrides.update({
720 style_overrides.update({
721 Token.Prompt: '#ansidarkgreen',
721 Token.Prompt: '#ansidarkgreen',
722 Token.PromptNum: '#ansigreen bold',
722 Token.PromptNum: '#ansigreen bold',
723 Token.OutPrompt: '#ansidarkred',
723 Token.OutPrompt: '#ansidarkred',
724 Token.OutPromptNum: '#ansired bold',
724 Token.OutPromptNum: '#ansired bold',
725 })
725 })
726 elif legacy =='nocolor':
726 elif legacy =='nocolor':
727 style_cls=_NoStyle
727 style_cls=_NoStyle
728 style_overrides = {}
728 style_overrides = {}
729 else :
729 else :
730 raise ValueError('Got unknown colors: ', legacy)
730 raise ValueError('Got unknown colors: ', legacy)
731 else :
731 else :
732 if isinstance(name_or_cls, str):
732 if isinstance(name_or_cls, str):
733 style_cls = get_style_by_name(name_or_cls)
733 style_cls = get_style_by_name(name_or_cls)
734 else:
734 else:
735 style_cls = name_or_cls
735 style_cls = name_or_cls
736 style_overrides = {
736 style_overrides = {
737 Token.Prompt: '#ansigreen',
737 Token.Prompt: '#ansigreen',
738 Token.PromptNum: '#ansibrightgreen bold',
738 Token.PromptNum: '#ansibrightgreen bold',
739 Token.OutPrompt: '#ansired',
739 Token.OutPrompt: '#ansired',
740 Token.OutPromptNum: '#ansibrightred bold',
740 Token.OutPromptNum: '#ansibrightred bold',
741 }
741 }
742 style_overrides.update(self.highlighting_style_overrides)
742 style_overrides.update(self.highlighting_style_overrides)
743 style = merge_styles([
743 style = merge_styles([
744 style_from_pygments_cls(style_cls),
744 style_from_pygments_cls(style_cls),
745 style_from_pygments_dict(style_overrides),
745 style_from_pygments_dict(style_overrides),
746 ])
746 ])
747
747
748 return style
748 return style
749
749
750 @property
750 @property
751 def pt_complete_style(self):
751 def pt_complete_style(self):
752 return {
752 return {
753 'multicolumn': CompleteStyle.MULTI_COLUMN,
753 'multicolumn': CompleteStyle.MULTI_COLUMN,
754 'column': CompleteStyle.COLUMN,
754 'column': CompleteStyle.COLUMN,
755 'readlinelike': CompleteStyle.READLINE_LIKE,
755 'readlinelike': CompleteStyle.READLINE_LIKE,
756 }[self.display_completions]
756 }[self.display_completions]
757
757
758 @property
758 @property
759 def color_depth(self):
759 def color_depth(self):
760 return (ColorDepth.TRUE_COLOR if self.true_color else None)
760 return (ColorDepth.TRUE_COLOR if self.true_color else None)
761
761
762 def _extra_prompt_options(self):
762 def _extra_prompt_options(self):
763 """
763 """
764 Return the current layout option for the current Terminal InteractiveShell
764 Return the current layout option for the current Terminal InteractiveShell
765 """
765 """
766 def get_message():
766 def get_message():
767 return PygmentsTokens(self.prompts.in_prompt_tokens())
767 return PygmentsTokens(self.prompts.in_prompt_tokens())
768
768
769 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
769 if self.editing_mode == "emacs" and self.prompt_line_number_format == "":
770 # with emacs mode the prompt is (usually) static, so we call only
770 # with emacs mode the prompt is (usually) static, so we call only
771 # the function once. With VI mode it can toggle between [ins] and
771 # the function once. With VI mode it can toggle between [ins] and
772 # [nor] so we can't precompute.
772 # [nor] so we can't precompute.
773 # here I'm going to favor the default keybinding which almost
773 # here I'm going to favor the default keybinding which almost
774 # everybody uses to decrease CPU usage.
774 # everybody uses to decrease CPU usage.
775 # if we have issues with users with custom Prompts we can see how to
775 # if we have issues with users with custom Prompts we can see how to
776 # work around this.
776 # work around this.
777 get_message = get_message()
777 get_message = get_message()
778
778
779 options = {
779 options = {
780 "complete_in_thread": False,
780 "complete_in_thread": False,
781 "lexer": IPythonPTLexer(),
781 "lexer": IPythonPTLexer(),
782 "reserve_space_for_menu": self.space_for_menu,
782 "reserve_space_for_menu": self.space_for_menu,
783 "message": get_message,
783 "message": get_message,
784 "prompt_continuation": (
784 "prompt_continuation": (
785 lambda width, lineno, is_soft_wrap: PygmentsTokens(
785 lambda width, lineno, is_soft_wrap: PygmentsTokens(
786 _backward_compat_continuation_prompt_tokens(
786 _backward_compat_continuation_prompt_tokens(
787 self.prompts.continuation_prompt_tokens, width, lineno=lineno
787 self.prompts.continuation_prompt_tokens, width, lineno=lineno
788 )
788 )
789 )
789 )
790 ),
790 ),
791 "multiline": True,
791 "multiline": True,
792 "complete_style": self.pt_complete_style,
792 "complete_style": self.pt_complete_style,
793 "input_processors": [
793 "input_processors": [
794 # Highlight matching brackets, but only when this setting is
794 # Highlight matching brackets, but only when this setting is
795 # enabled, and only when the DEFAULT_BUFFER has the focus.
795 # enabled, and only when the DEFAULT_BUFFER has the focus.
796 ConditionalProcessor(
796 ConditionalProcessor(
797 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
797 processor=HighlightMatchingBracketProcessor(chars="[](){}"),
798 filter=HasFocus(DEFAULT_BUFFER)
798 filter=HasFocus(DEFAULT_BUFFER)
799 & ~IsDone()
799 & ~IsDone()
800 & Condition(lambda: self.highlight_matching_brackets),
800 & Condition(lambda: self.highlight_matching_brackets),
801 ),
801 ),
802 # Show auto-suggestion in lines other than the last line.
802 # Show auto-suggestion in lines other than the last line.
803 ConditionalProcessor(
803 ConditionalProcessor(
804 processor=AppendAutoSuggestionInAnyLine(),
804 processor=AppendAutoSuggestionInAnyLine(),
805 filter=HasFocus(DEFAULT_BUFFER)
805 filter=HasFocus(DEFAULT_BUFFER)
806 & ~IsDone()
806 & ~IsDone()
807 & Condition(
807 & Condition(
808 lambda: isinstance(
808 lambda: isinstance(
809 self.auto_suggest, NavigableAutoSuggestFromHistory
809 self.auto_suggest, NavigableAutoSuggestFromHistory
810 )
810 )
811 ),
811 ),
812 ),
812 ),
813 ],
813 ],
814 }
814 }
815 if not PTK3:
815 if not PTK3:
816 options['inputhook'] = self.inputhook
816 options['inputhook'] = self.inputhook
817
817
818 return options
818 return options
819
819
820 def prompt_for_code(self):
820 def prompt_for_code(self):
821 if self.rl_next_input:
821 if self.rl_next_input:
822 default = self.rl_next_input
822 default = self.rl_next_input
823 self.rl_next_input = None
823 self.rl_next_input = None
824 else:
824 else:
825 default = ''
825 default = ''
826
826
827 # In order to make sure that asyncio code written in the
827 # In order to make sure that asyncio code written in the
828 # interactive shell doesn't interfere with the prompt, we run the
828 # interactive shell doesn't interfere with the prompt, we run the
829 # prompt in a different event loop.
829 # prompt in a different event loop.
830 # If we don't do this, people could spawn coroutine with a
830 # If we don't do this, people could spawn coroutine with a
831 # while/true inside which will freeze the prompt.
831 # while/true inside which will freeze the prompt.
832
832
833 with patch_stdout(raw=True):
833 with patch_stdout(raw=True):
834 if self._use_asyncio_inputhook:
834 if self._use_asyncio_inputhook:
835 # When we integrate the asyncio event loop, run the UI in the
835 # When we integrate the asyncio event loop, run the UI in the
836 # same event loop as the rest of the code. don't use an actual
836 # same event loop as the rest of the code. don't use an actual
837 # input hook. (Asyncio is not made for nesting event loops.)
837 # input hook. (Asyncio is not made for nesting event loops.)
838 asyncio_loop = get_asyncio_loop()
838 asyncio_loop = get_asyncio_loop()
839 text = asyncio_loop.run_until_complete(
839 text = asyncio_loop.run_until_complete(
840 self.pt_app.prompt_async(
840 self.pt_app.prompt_async(
841 default=default, **self._extra_prompt_options()
841 default=default, **self._extra_prompt_options()
842 )
842 )
843 )
843 )
844 else:
844 else:
845 text = self.pt_app.prompt(
845 text = self.pt_app.prompt(
846 default=default,
846 default=default,
847 inputhook=self._inputhook,
847 inputhook=self._inputhook,
848 **self._extra_prompt_options(),
848 **self._extra_prompt_options(),
849 )
849 )
850
850
851 return text
851 return text
852
852
853 def enable_win_unicode_console(self):
853 def enable_win_unicode_console(self):
854 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
854 # Since IPython 7.10 doesn't support python < 3.6 and PEP 528, Python uses the unicode APIs for the Windows
855 # console by default, so WUC shouldn't be needed.
855 # console by default, so WUC shouldn't be needed.
856 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
856 warn("`enable_win_unicode_console` is deprecated since IPython 7.10, does not do anything and will be removed in the future",
857 DeprecationWarning,
857 DeprecationWarning,
858 stacklevel=2)
858 stacklevel=2)
859
859
860 def init_io(self):
860 def init_io(self):
861 if sys.platform not in {'win32', 'cli'}:
861 if sys.platform not in {'win32', 'cli'}:
862 return
862 return
863
863
864 import colorama
864 import colorama
865 colorama.init()
865 colorama.init()
866
866
867 def init_magics(self):
867 def init_magics(self):
868 super(TerminalInteractiveShell, self).init_magics()
868 super(TerminalInteractiveShell, self).init_magics()
869 self.register_magics(TerminalMagics)
869 self.register_magics(TerminalMagics)
870
870
871 def init_alias(self):
871 def init_alias(self):
872 # The parent class defines aliases that can be safely used with any
872 # The parent class defines aliases that can be safely used with any
873 # frontend.
873 # frontend.
874 super(TerminalInteractiveShell, self).init_alias()
874 super(TerminalInteractiveShell, self).init_alias()
875
875
876 # Now define aliases that only make sense on the terminal, because they
876 # Now define aliases that only make sense on the terminal, because they
877 # need direct access to the console in a way that we can't emulate in
877 # need direct access to the console in a way that we can't emulate in
878 # GUI or web frontend
878 # GUI or web frontend
879 if os.name == 'posix':
879 if os.name == 'posix':
880 for cmd in ('clear', 'more', 'less', 'man'):
880 for cmd in ('clear', 'more', 'less', 'man'):
881 self.alias_manager.soft_define_alias(cmd, cmd)
881 self.alias_manager.soft_define_alias(cmd, cmd)
882
882
883 def __init__(self, *args, **kwargs) -> None:
883 def __init__(self, *args, **kwargs) -> None:
884 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
884 super(TerminalInteractiveShell, self).__init__(*args, **kwargs)
885 self._set_autosuggestions(self.autosuggestions_provider)
885 self._set_autosuggestions(self.autosuggestions_provider)
886 self.init_prompt_toolkit_cli()
886 self.init_prompt_toolkit_cli()
887 self.init_term_title()
887 self.init_term_title()
888 self.keep_running = True
888 self.keep_running = True
889 self._set_formatter(self.autoformatter)
889 self._set_formatter(self.autoformatter)
890
890
891 def ask_exit(self):
891 def ask_exit(self):
892 self.keep_running = False
892 self.keep_running = False
893
893
894 rl_next_input = None
894 rl_next_input = None
895
895
896 def interact(self):
896 def interact(self):
897 self.keep_running = True
897 self.keep_running = True
898 while self.keep_running:
898 while self.keep_running:
899 print(self.separate_in, end='')
899 print(self.separate_in, end='')
900
900
901 try:
901 try:
902 code = self.prompt_for_code()
902 code = self.prompt_for_code()
903 except EOFError:
903 except EOFError:
904 if (not self.confirm_exit) \
904 if (not self.confirm_exit) \
905 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
905 or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'):
906 self.ask_exit()
906 self.ask_exit()
907
907
908 else:
908 else:
909 if code:
909 if code:
910 self.run_cell(code, store_history=True)
910 self.run_cell(code, store_history=True)
911
911
912 def mainloop(self):
912 def mainloop(self):
913 # An extra layer of protection in case someone mashing Ctrl-C breaks
913 # An extra layer of protection in case someone mashing Ctrl-C breaks
914 # out of our internal code.
914 # out of our internal code.
915 while True:
915 while True:
916 try:
916 try:
917 self.interact()
917 self.interact()
918 break
918 break
919 except KeyboardInterrupt as e:
919 except KeyboardInterrupt as e:
920 print("\n%s escaped interact()\n" % type(e).__name__)
920 print("\n%s escaped interact()\n" % type(e).__name__)
921 finally:
921 finally:
922 # An interrupt during the eventloop will mess up the
922 # An interrupt during the eventloop will mess up the
923 # internal state of the prompt_toolkit library.
923 # internal state of the prompt_toolkit library.
924 # Stopping the eventloop fixes this, see
924 # Stopping the eventloop fixes this, see
925 # https://github.com/ipython/ipython/pull/9867
925 # https://github.com/ipython/ipython/pull/9867
926 if hasattr(self, '_eventloop'):
926 if hasattr(self, '_eventloop'):
927 self._eventloop.stop()
927 self._eventloop.stop()
928
928
929 self.restore_term_title()
929 self.restore_term_title()
930
930
931 # try to call some at-exit operation optimistically as some things can't
931 # try to call some at-exit operation optimistically as some things can't
932 # be done during interpreter shutdown. this is technically inaccurate as
932 # be done during interpreter shutdown. this is technically inaccurate as
933 # this make mainlool not re-callable, but that should be a rare if not
933 # this make mainlool not re-callable, but that should be a rare if not
934 # in existent use case.
934 # in existent use case.
935
935
936 self._atexit_once()
936 self._atexit_once()
937
937
938 _inputhook = None
938 _inputhook = None
939 def inputhook(self, context):
939 def inputhook(self, context):
940 if self._inputhook is not None:
940 if self._inputhook is not None:
941 self._inputhook(context)
941 self._inputhook(context)
942
942
943 active_eventloop: Optional[str] = None
943 active_eventloop: Optional[str] = None
944
944
945 def enable_gui(self, gui: Optional[str] = None) -> None:
945 def enable_gui(self, gui: Optional[str] = None) -> None:
946 if gui:
947 from ..core.pylabtools import _convert_gui_from_matplotlib
948
949 gui = _convert_gui_from_matplotlib(gui)
950
946 if self.simple_prompt is True and gui is not None:
951 if self.simple_prompt is True and gui is not None:
947 print(
952 print(
948 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
953 f'Cannot install event loop hook for "{gui}" when running with `--simple-prompt`.'
949 )
954 )
950 print(
955 print(
951 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
956 "NOTE: Tk is supported natively; use Tk apps and Tk backends with `--simple-prompt`."
952 )
957 )
953 return
958 return
954
959
955 if self._inputhook is None and gui is None:
960 if self._inputhook is None and gui is None:
956 print("No event loop hook running.")
961 print("No event loop hook running.")
957 return
962 return
958
963
959 if self._inputhook is not None and gui is not None:
964 if self._inputhook is not None and gui is not None:
960 newev, newinhook = get_inputhook_name_and_func(gui)
965 newev, newinhook = get_inputhook_name_and_func(gui)
961 if self._inputhook == newinhook:
966 if self._inputhook == newinhook:
962 # same inputhook, do nothing
967 # same inputhook, do nothing
963 self.log.info(
968 self.log.info(
964 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
969 f"Shell is already running the {self.active_eventloop} eventloop. Doing nothing"
965 )
970 )
966 return
971 return
967 self.log.warning(
972 self.log.warning(
968 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
973 f"Shell is already running a different gui event loop for {self.active_eventloop}. "
969 "Call with no arguments to disable the current loop."
974 "Call with no arguments to disable the current loop."
970 )
975 )
971 return
976 return
972 if self._inputhook is not None and gui is None:
977 if self._inputhook is not None and gui is None:
973 self.active_eventloop = self._inputhook = None
978 self.active_eventloop = self._inputhook = None
974
979
975 if gui and (gui not in {None, "webagg"}):
980 if gui and (gui not in {None, "webagg"}):
976 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
981 # This hook runs with each cycle of the `prompt_toolkit`'s event loop.
977 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
982 self.active_eventloop, self._inputhook = get_inputhook_name_and_func(gui)
978 else:
983 else:
979 self.active_eventloop = self._inputhook = None
984 self.active_eventloop = self._inputhook = None
980
985
981 self._use_asyncio_inputhook = gui == "asyncio"
986 self._use_asyncio_inputhook = gui == "asyncio"
982
987
983 # Run !system commands directly, not through pipes, so terminal programs
988 # Run !system commands directly, not through pipes, so terminal programs
984 # work correctly.
989 # work correctly.
985 system = InteractiveShell.system_raw
990 system = InteractiveShell.system_raw
986
991
987 def auto_rewrite_input(self, cmd):
992 def auto_rewrite_input(self, cmd):
988 """Overridden from the parent class to use fancy rewriting prompt"""
993 """Overridden from the parent class to use fancy rewriting prompt"""
989 if not self.show_rewritten_input:
994 if not self.show_rewritten_input:
990 return
995 return
991
996
992 tokens = self.prompts.rewrite_prompt_tokens()
997 tokens = self.prompts.rewrite_prompt_tokens()
993 if self.pt_app:
998 if self.pt_app:
994 print_formatted_text(PygmentsTokens(tokens), end='',
999 print_formatted_text(PygmentsTokens(tokens), end='',
995 style=self.pt_app.app.style)
1000 style=self.pt_app.app.style)
996 print(cmd)
1001 print(cmd)
997 else:
1002 else:
998 prompt = ''.join(s for t, s in tokens)
1003 prompt = ''.join(s for t, s in tokens)
999 print(prompt, cmd, sep='')
1004 print(prompt, cmd, sep='')
1000
1005
1001 _prompts_before = None
1006 _prompts_before = None
1002 def switch_doctest_mode(self, mode):
1007 def switch_doctest_mode(self, mode):
1003 """Switch prompts to classic for %doctest_mode"""
1008 """Switch prompts to classic for %doctest_mode"""
1004 if mode:
1009 if mode:
1005 self._prompts_before = self.prompts
1010 self._prompts_before = self.prompts
1006 self.prompts = ClassicPrompts(self)
1011 self.prompts = ClassicPrompts(self)
1007 elif self._prompts_before:
1012 elif self._prompts_before:
1008 self.prompts = self._prompts_before
1013 self.prompts = self._prompts_before
1009 self._prompts_before = None
1014 self._prompts_before = None
1010 # self._update_layout()
1015 # self._update_layout()
1011
1016
1012
1017
1013 InteractiveShellABC.register(TerminalInteractiveShell)
1018 InteractiveShellABC.register(TerminalInteractiveShell)
1014
1019
1015 if __name__ == '__main__':
1020 if __name__ == '__main__':
1016 TerminalInteractiveShell.instance().interact()
1021 TerminalInteractiveShell.instance().interact()
@@ -1,201 +1,204 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Decorators for labeling test objects.
2 """Decorators for labeling test objects.
3
3
4 Decorators that merely return a modified version of the original function
4 Decorators that merely return a modified version of the original function
5 object are straightforward. Decorators that return a new function object need
5 object are straightforward. Decorators that return a new function object need
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
6 to use nose.tools.make_decorator(original_function)(decorator) in returning the
7 decorator, in order to preserve metadata such as function name, setup and
7 decorator, in order to preserve metadata such as function name, setup and
8 teardown functions and so on - see nose.tools for more information.
8 teardown functions and so on - see nose.tools for more information.
9
9
10 This module provides a set of useful decorators meant to be ready to use in
10 This module provides a set of useful decorators meant to be ready to use in
11 your own tests. See the bottom of the file for the ready-made ones, and if you
11 your own tests. See the bottom of the file for the ready-made ones, and if you
12 find yourself writing a new one that may be of generic use, add it here.
12 find yourself writing a new one that may be of generic use, add it here.
13
13
14 Included decorators:
14 Included decorators:
15
15
16
16
17 Lightweight testing that remains unittest-compatible.
17 Lightweight testing that remains unittest-compatible.
18
18
19 - An @as_unittest decorator can be used to tag any normal parameter-less
19 - An @as_unittest decorator can be used to tag any normal parameter-less
20 function as a unittest TestCase. Then, both nose and normal unittest will
20 function as a unittest TestCase. Then, both nose and normal unittest will
21 recognize it as such. This will make it easier to migrate away from Nose if
21 recognize it as such. This will make it easier to migrate away from Nose if
22 we ever need/want to while maintaining very lightweight tests.
22 we ever need/want to while maintaining very lightweight tests.
23
23
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
24 NOTE: This file contains IPython-specific decorators. Using the machinery in
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
25 IPython.external.decorators, we import either numpy.testing.decorators if numpy is
26 available, OR use equivalent code in IPython.external._decorators, which
26 available, OR use equivalent code in IPython.external._decorators, which
27 we've copied verbatim from numpy.
27 we've copied verbatim from numpy.
28
28
29 """
29 """
30
30
31 # Copyright (c) IPython Development Team.
31 # Copyright (c) IPython Development Team.
32 # Distributed under the terms of the Modified BSD License.
32 # Distributed under the terms of the Modified BSD License.
33
33
34 import os
34 import os
35 import shutil
35 import shutil
36 import sys
36 import sys
37 import tempfile
37 import tempfile
38 import unittest
38 import unittest
39 from importlib import import_module
39 from importlib import import_module
40
40
41 from decorator import decorator
41 from decorator import decorator
42
42
43 # Expose the unittest-driven decorators
43 # Expose the unittest-driven decorators
44 from .ipunittest import ipdoctest, ipdocstring
44 from .ipunittest import ipdoctest, ipdocstring
45
45
46 #-----------------------------------------------------------------------------
46 #-----------------------------------------------------------------------------
47 # Classes and functions
47 # Classes and functions
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49
49
50 # Simple example of the basic idea
50 # Simple example of the basic idea
51 def as_unittest(func):
51 def as_unittest(func):
52 """Decorator to make a simple function into a normal test via unittest."""
52 """Decorator to make a simple function into a normal test via unittest."""
53 class Tester(unittest.TestCase):
53 class Tester(unittest.TestCase):
54 def test(self):
54 def test(self):
55 func()
55 func()
56
56
57 Tester.__name__ = func.__name__
57 Tester.__name__ = func.__name__
58
58
59 return Tester
59 return Tester
60
60
61 # Utility functions
61 # Utility functions
62
62
63
63
64 def skipif(skip_condition, msg=None):
64 def skipif(skip_condition, msg=None):
65 """Make function raise SkipTest exception if skip_condition is true
65 """Make function raise SkipTest exception if skip_condition is true
66
66
67 Parameters
67 Parameters
68 ----------
68 ----------
69
69
70 skip_condition : bool or callable
70 skip_condition : bool or callable
71 Flag to determine whether to skip test. If the condition is a
71 Flag to determine whether to skip test. If the condition is a
72 callable, it is used at runtime to dynamically make the decision. This
72 callable, it is used at runtime to dynamically make the decision. This
73 is useful for tests that may require costly imports, to delay the cost
73 is useful for tests that may require costly imports, to delay the cost
74 until the test suite is actually executed.
74 until the test suite is actually executed.
75 msg : string
75 msg : string
76 Message to give on raising a SkipTest exception.
76 Message to give on raising a SkipTest exception.
77
77
78 Returns
78 Returns
79 -------
79 -------
80 decorator : function
80 decorator : function
81 Decorator, which, when applied to a function, causes SkipTest
81 Decorator, which, when applied to a function, causes SkipTest
82 to be raised when the skip_condition was True, and the function
82 to be raised when the skip_condition was True, and the function
83 to be called normally otherwise.
83 to be called normally otherwise.
84 """
84 """
85 if msg is None:
85 if msg is None:
86 msg = "Test skipped due to test condition."
86 msg = "Test skipped due to test condition."
87
87
88 import pytest
88 import pytest
89
89
90 assert isinstance(skip_condition, bool)
90 assert isinstance(skip_condition, bool)
91 return pytest.mark.skipif(skip_condition, reason=msg)
91 return pytest.mark.skipif(skip_condition, reason=msg)
92
92
93
93
94 # A version with the condition set to true, common case just to attach a message
94 # A version with the condition set to true, common case just to attach a message
95 # to a skip decorator
95 # to a skip decorator
96 def skip(msg=None):
96 def skip(msg=None):
97 """Decorator factory - mark a test function for skipping from test suite.
97 """Decorator factory - mark a test function for skipping from test suite.
98
98
99 Parameters
99 Parameters
100 ----------
100 ----------
101 msg : string
101 msg : string
102 Optional message to be added.
102 Optional message to be added.
103
103
104 Returns
104 Returns
105 -------
105 -------
106 decorator : function
106 decorator : function
107 Decorator, which, when applied to a function, causes SkipTest
107 Decorator, which, when applied to a function, causes SkipTest
108 to be raised, with the optional message added.
108 to be raised, with the optional message added.
109 """
109 """
110 if msg and not isinstance(msg, str):
110 if msg and not isinstance(msg, str):
111 raise ValueError('invalid object passed to `@skip` decorator, did you '
111 raise ValueError('invalid object passed to `@skip` decorator, did you '
112 'meant `@skip()` with brackets ?')
112 'meant `@skip()` with brackets ?')
113 return skipif(True, msg)
113 return skipif(True, msg)
114
114
115
115
116 def onlyif(condition, msg):
116 def onlyif(condition, msg):
117 """The reverse from skipif, see skipif for details."""
117 """The reverse from skipif, see skipif for details."""
118
118
119 return skipif(not condition, msg)
119 return skipif(not condition, msg)
120
120
121 #-----------------------------------------------------------------------------
121 #-----------------------------------------------------------------------------
122 # Utility functions for decorators
122 # Utility functions for decorators
123 def module_not_available(module):
123 def module_not_available(module):
124 """Can module be imported? Returns true if module does NOT import.
124 """Can module be imported? Returns true if module does NOT import.
125
125
126 This is used to make a decorator to skip tests that require module to be
126 This is used to make a decorator to skip tests that require module to be
127 available, but delay the 'import numpy' to test execution time.
127 available, but delay the 'import numpy' to test execution time.
128 """
128 """
129 try:
129 try:
130 mod = import_module(module)
130 mod = import_module(module)
131 mod_not_avail = False
131 mod_not_avail = False
132 except ImportError:
132 except ImportError:
133 mod_not_avail = True
133 mod_not_avail = True
134
134
135 return mod_not_avail
135 return mod_not_avail
136
136
137
137
138 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
139 # Decorators for public use
139 # Decorators for public use
140
140
141 # Decorators to skip certain tests on specific platforms.
141 # Decorators to skip certain tests on specific platforms.
142 skip_win32 = skipif(sys.platform == 'win32',
142 skip_win32 = skipif(sys.platform == 'win32',
143 "This test does not run under Windows")
143 "This test does not run under Windows")
144 skip_linux = skipif(sys.platform.startswith('linux'),
144 skip_linux = skipif(sys.platform.startswith('linux'),
145 "This test does not run under Linux")
145 "This test does not run under Linux")
146 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
146 skip_osx = skipif(sys.platform == 'darwin',"This test does not run under OS X")
147
147
148
148
149 # Decorators to skip tests if not on specific platforms.
149 # Decorators to skip tests if not on specific platforms.
150 skip_if_not_win32 = skipif(sys.platform != 'win32',
150 skip_if_not_win32 = skipif(sys.platform != "win32", "This test only runs under Windows")
151 "This test only runs under Windows")
151 skip_if_not_linux = skipif(
152 skip_if_not_linux = skipif(not sys.platform.startswith('linux'),
152 not sys.platform.startswith("linux"), "This test only runs under Linux"
153 "This test only runs under Linux")
153 )
154 skip_if_not_osx = skipif(
155 not sys.platform.startswith("darwin"), "This test only runs under macOS"
156 )
154
157
155 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
158 _x11_skip_cond = (sys.platform not in ('darwin', 'win32') and
156 os.environ.get('DISPLAY', '') == '')
159 os.environ.get('DISPLAY', '') == '')
157 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
160 _x11_skip_msg = "Skipped under *nix when X11/XOrg not available"
158
161
159 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
162 skip_if_no_x11 = skipif(_x11_skip_cond, _x11_skip_msg)
160
163
161 # Other skip decorators
164 # Other skip decorators
162
165
163 # generic skip without module
166 # generic skip without module
164 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
167 skip_without = lambda mod: skipif(module_not_available(mod), "This test requires %s" % mod)
165
168
166 skipif_not_numpy = skip_without('numpy')
169 skipif_not_numpy = skip_without('numpy')
167
170
168 skipif_not_matplotlib = skip_without('matplotlib')
171 skipif_not_matplotlib = skip_without('matplotlib')
169
172
170 # A null 'decorator', useful to make more readable code that needs to pick
173 # A null 'decorator', useful to make more readable code that needs to pick
171 # between different decorators based on OS or other conditions
174 # between different decorators based on OS or other conditions
172 null_deco = lambda f: f
175 null_deco = lambda f: f
173
176
174 # Some tests only run where we can use unicode paths. Note that we can't just
177 # Some tests only run where we can use unicode paths. Note that we can't just
175 # check os.path.supports_unicode_filenames, which is always False on Linux.
178 # check os.path.supports_unicode_filenames, which is always False on Linux.
176 try:
179 try:
177 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
180 f = tempfile.NamedTemporaryFile(prefix=u"tmp€")
178 except UnicodeEncodeError:
181 except UnicodeEncodeError:
179 unicode_paths = False
182 unicode_paths = False
180 else:
183 else:
181 unicode_paths = True
184 unicode_paths = True
182 f.close()
185 f.close()
183
186
184 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
187 onlyif_unicode_paths = onlyif(unicode_paths, ("This test is only applicable "
185 "where we can use unicode in filenames."))
188 "where we can use unicode in filenames."))
186
189
187
190
188 def onlyif_cmds_exist(*commands):
191 def onlyif_cmds_exist(*commands):
189 """
192 """
190 Decorator to skip test when at least one of `commands` is not found.
193 Decorator to skip test when at least one of `commands` is not found.
191 """
194 """
192 assert (
195 assert (
193 os.environ.get("IPTEST_WORKING_DIR", None) is None
196 os.environ.get("IPTEST_WORKING_DIR", None) is None
194 ), "iptest deprecated since IPython 8.0"
197 ), "iptest deprecated since IPython 8.0"
195 for cmd in commands:
198 for cmd in commands:
196 reason = f"This test runs only if command '{cmd}' is installed"
199 reason = f"This test runs only if command '{cmd}' is installed"
197 if not shutil.which(cmd):
200 if not shutil.which(cmd):
198 import pytest
201 import pytest
199
202
200 return pytest.mark.skip(reason=reason)
203 return pytest.mark.skip(reason=reason)
201 return null_deco
204 return null_deco
General Comments 0
You need to be logged in to leave comments. Login now