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