##// END OF EJS Templates
deprecated ast attribute (#14575)
M Bussonnier -
r28957:0615526f merge
parent child Browse files
Show More
@@ -1,1221 +1,1221
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 30 skipif,
31 31 skip_win32,
32 32 onlyif_unicode_paths,
33 33 onlyif_cmds_exist,
34 34 skip_if_not_osx,
35 35 )
36 36 from IPython.testing import tools as tt
37 37 from IPython.utils.process import find_cmd
38 38
39 39 #-----------------------------------------------------------------------------
40 40 # Globals
41 41 #-----------------------------------------------------------------------------
42 42 # This is used by every single test, no point repeating it ad nauseam
43 43
44 44 #-----------------------------------------------------------------------------
45 45 # Tests
46 46 #-----------------------------------------------------------------------------
47 47
48 48 class DerivedInterrupt(KeyboardInterrupt):
49 49 pass
50 50
51 51 class InteractiveShellTestCase(unittest.TestCase):
52 52 def test_naked_string_cells(self):
53 53 """Test that cells with only naked strings are fully executed"""
54 54 # First, single-line inputs
55 55 ip.run_cell('"a"\n')
56 56 self.assertEqual(ip.user_ns['_'], 'a')
57 57 # And also multi-line cells
58 58 ip.run_cell('"""a\nb"""\n')
59 59 self.assertEqual(ip.user_ns['_'], 'a\nb')
60 60
61 61 def test_run_empty_cell(self):
62 62 """Just make sure we don't get a horrible error with a blank
63 63 cell of input. Yes, I did overlook that."""
64 64 old_xc = ip.execution_count
65 65 res = ip.run_cell('')
66 66 self.assertEqual(ip.execution_count, old_xc)
67 67 self.assertEqual(res.execution_count, None)
68 68
69 69 def test_run_cell_multiline(self):
70 70 """Multi-block, multi-line cells must execute correctly.
71 71 """
72 72 src = '\n'.join(["x=1",
73 73 "y=2",
74 74 "if 1:",
75 75 " x += 1",
76 76 " y += 1",])
77 77 res = ip.run_cell(src)
78 78 self.assertEqual(ip.user_ns['x'], 2)
79 79 self.assertEqual(ip.user_ns['y'], 3)
80 80 self.assertEqual(res.success, True)
81 81 self.assertEqual(res.result, None)
82 82
83 83 def test_multiline_string_cells(self):
84 84 "Code sprinkled with multiline strings should execute (GH-306)"
85 85 ip.run_cell('tmp=0')
86 86 self.assertEqual(ip.user_ns['tmp'], 0)
87 87 res = ip.run_cell('tmp=1;"""a\nb"""\n')
88 88 self.assertEqual(ip.user_ns['tmp'], 1)
89 89 self.assertEqual(res.success, True)
90 90 self.assertEqual(res.result, "a\nb")
91 91
92 92 def test_dont_cache_with_semicolon(self):
93 93 "Ending a line with semicolon should not cache the returned object (GH-307)"
94 94 oldlen = len(ip.user_ns['Out'])
95 95 for cell in ['1;', '1;1;']:
96 96 res = ip.run_cell(cell, store_history=True)
97 97 newlen = len(ip.user_ns['Out'])
98 98 self.assertEqual(oldlen, newlen)
99 99 self.assertIsNone(res.result)
100 100 i = 0
101 101 #also test the default caching behavior
102 102 for cell in ['1', '1;1']:
103 103 ip.run_cell(cell, store_history=True)
104 104 newlen = len(ip.user_ns['Out'])
105 105 i += 1
106 106 self.assertEqual(oldlen+i, newlen)
107 107
108 108 def test_syntax_error(self):
109 109 res = ip.run_cell("raise = 3")
110 110 self.assertIsInstance(res.error_before_exec, SyntaxError)
111 111
112 112 def test_open_standard_input_stream(self):
113 113 res = ip.run_cell("open(0)")
114 114 self.assertIsInstance(res.error_in_exec, ValueError)
115 115
116 116 def test_open_standard_output_stream(self):
117 117 res = ip.run_cell("open(1)")
118 118 self.assertIsInstance(res.error_in_exec, ValueError)
119 119
120 120 def test_open_standard_error_stream(self):
121 121 res = ip.run_cell("open(2)")
122 122 self.assertIsInstance(res.error_in_exec, ValueError)
123 123
124 124 def test_In_variable(self):
125 125 "Verify that In variable grows with user input (GH-284)"
126 126 oldlen = len(ip.user_ns['In'])
127 127 ip.run_cell('1;', store_history=True)
128 128 newlen = len(ip.user_ns['In'])
129 129 self.assertEqual(oldlen+1, newlen)
130 130 self.assertEqual(ip.user_ns['In'][-1],'1;')
131 131
132 132 def test_magic_names_in_string(self):
133 133 ip.run_cell('a = """\n%exit\n"""')
134 134 self.assertEqual(ip.user_ns['a'], '\n%exit\n')
135 135
136 136 def test_trailing_newline(self):
137 137 """test that running !(command) does not raise a SyntaxError"""
138 138 ip.run_cell('!(true)\n', False)
139 139 ip.run_cell('!(true)\n\n\n', False)
140 140
141 141 def test_gh_597(self):
142 142 """Pretty-printing lists of objects with non-ascii reprs may cause
143 143 problems."""
144 144 class Spam(object):
145 145 def __repr__(self):
146 146 return "\xe9"*50
147 147 import IPython.core.formatters
148 148 f = IPython.core.formatters.PlainTextFormatter()
149 149 f([Spam(), Spam()])
150 150
151 151 def test_future_flags(self):
152 152 """Check that future flags are used for parsing code (gh-777)"""
153 153 ip.run_cell('from __future__ import barry_as_FLUFL')
154 154 try:
155 155 ip.run_cell('prfunc_return_val = 1 <> 2')
156 156 assert 'prfunc_return_val' in ip.user_ns
157 157 finally:
158 158 # Reset compiler flags so we don't mess up other tests.
159 159 ip.compile.reset_compiler_flags()
160 160
161 161 def test_can_pickle(self):
162 162 "Can we pickle objects defined interactively (GH-29)"
163 163 ip = get_ipython()
164 164 ip.reset()
165 165 ip.run_cell(("class Mylist(list):\n"
166 166 " def __init__(self,x=[]):\n"
167 167 " list.__init__(self,x)"))
168 168 ip.run_cell("w=Mylist([1,2,3])")
169 169
170 170 from pickle import dumps
171 171
172 172 # We need to swap in our main module - this is only necessary
173 173 # inside the test framework, because IPython puts the interactive module
174 174 # in place (but the test framework undoes this).
175 175 _main = sys.modules['__main__']
176 176 sys.modules['__main__'] = ip.user_module
177 177 try:
178 178 res = dumps(ip.user_ns["w"])
179 179 finally:
180 180 sys.modules['__main__'] = _main
181 181 self.assertTrue(isinstance(res, bytes))
182 182
183 183 def test_global_ns(self):
184 184 "Code in functions must be able to access variables outside them."
185 185 ip = get_ipython()
186 186 ip.run_cell("a = 10")
187 187 ip.run_cell(("def f(x):\n"
188 188 " return x + a"))
189 189 ip.run_cell("b = f(12)")
190 190 self.assertEqual(ip.user_ns["b"], 22)
191 191
192 192 def test_bad_custom_tb(self):
193 193 """Check that InteractiveShell is protected from bad custom exception handlers"""
194 194 ip.set_custom_exc((IOError,), lambda etype,value,tb: 1/0)
195 195 self.assertEqual(ip.custom_exceptions, (IOError,))
196 196 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
197 197 ip.run_cell(u'raise IOError("foo")')
198 198 self.assertEqual(ip.custom_exceptions, ())
199 199
200 200 def test_bad_custom_tb_return(self):
201 201 """Check that InteractiveShell is protected from bad return types in custom exception handlers"""
202 202 ip.set_custom_exc((NameError,),lambda etype,value,tb, tb_offset=None: 1)
203 203 self.assertEqual(ip.custom_exceptions, (NameError,))
204 204 with tt.AssertPrints("Custom TB Handler failed", channel='stderr'):
205 205 ip.run_cell(u'a=abracadabra')
206 206 self.assertEqual(ip.custom_exceptions, ())
207 207
208 208 def test_drop_by_id(self):
209 209 myvars = {"a":object(), "b":object(), "c": object()}
210 210 ip.push(myvars, interactive=False)
211 211 for name in myvars:
212 212 assert name in ip.user_ns, name
213 213 assert name in ip.user_ns_hidden, name
214 214 ip.user_ns['b'] = 12
215 215 ip.drop_by_id(myvars)
216 216 for name in ["a", "c"]:
217 217 assert name not in ip.user_ns, name
218 218 assert name not in ip.user_ns_hidden, name
219 219 assert ip.user_ns['b'] == 12
220 220 ip.reset()
221 221
222 222 def test_var_expand(self):
223 223 ip.user_ns['f'] = u'Ca\xf1o'
224 224 self.assertEqual(ip.var_expand(u'echo $f'), u'echo Ca\xf1o')
225 225 self.assertEqual(ip.var_expand(u'echo {f}'), u'echo Ca\xf1o')
226 226 self.assertEqual(ip.var_expand(u'echo {f[:-1]}'), u'echo Ca\xf1')
227 227 self.assertEqual(ip.var_expand(u'echo {1*2}'), u'echo 2')
228 228
229 229 self.assertEqual(ip.var_expand(u"grep x | awk '{print $1}'"), u"grep x | awk '{print $1}'")
230 230
231 231 ip.user_ns['f'] = b'Ca\xc3\xb1o'
232 232 # This should not raise any exception:
233 233 ip.var_expand(u'echo $f')
234 234
235 235 def test_var_expand_local(self):
236 236 """Test local variable expansion in !system and %magic calls"""
237 237 # !system
238 238 ip.run_cell(
239 239 "def test():\n"
240 240 ' lvar = "ttt"\n'
241 241 " ret = !echo {lvar}\n"
242 242 " return ret[0]\n"
243 243 )
244 244 res = ip.user_ns["test"]()
245 245 self.assertIn("ttt", res)
246 246
247 247 # %magic
248 248 ip.run_cell(
249 249 "def makemacro():\n"
250 250 ' macroname = "macro_var_expand_locals"\n'
251 251 " %macro {macroname} codestr\n"
252 252 )
253 253 ip.user_ns["codestr"] = "str(12)"
254 254 ip.run_cell("makemacro()")
255 255 self.assertIn("macro_var_expand_locals", ip.user_ns)
256 256
257 257 def test_var_expand_self(self):
258 258 """Test variable expansion with the name 'self', which was failing.
259 259
260 260 See https://github.com/ipython/ipython/issues/1878#issuecomment-7698218
261 261 """
262 262 ip.run_cell(
263 263 "class cTest:\n"
264 264 ' classvar="see me"\n'
265 265 " def test(self):\n"
266 266 " res = !echo Variable: {self.classvar}\n"
267 267 " return res[0]\n"
268 268 )
269 269 self.assertIn("see me", ip.user_ns["cTest"]().test())
270 270
271 271 def test_bad_var_expand(self):
272 272 """var_expand on invalid formats shouldn't raise"""
273 273 # SyntaxError
274 274 self.assertEqual(ip.var_expand(u"{'a':5}"), u"{'a':5}")
275 275 # NameError
276 276 self.assertEqual(ip.var_expand(u"{asdf}"), u"{asdf}")
277 277 # ZeroDivisionError
278 278 self.assertEqual(ip.var_expand(u"{1/0}"), u"{1/0}")
279 279
280 280 def test_silent_postexec(self):
281 281 """run_cell(silent=True) doesn't invoke pre/post_run_cell callbacks"""
282 282 pre_explicit = mock.Mock()
283 283 pre_always = mock.Mock()
284 284 post_explicit = mock.Mock()
285 285 post_always = mock.Mock()
286 286 all_mocks = [pre_explicit, pre_always, post_explicit, post_always]
287 287
288 288 ip.events.register('pre_run_cell', pre_explicit)
289 289 ip.events.register('pre_execute', pre_always)
290 290 ip.events.register('post_run_cell', post_explicit)
291 291 ip.events.register('post_execute', post_always)
292 292
293 293 try:
294 294 ip.run_cell("1", silent=True)
295 295 assert pre_always.called
296 296 assert not pre_explicit.called
297 297 assert post_always.called
298 298 assert not post_explicit.called
299 299 # double-check that non-silent exec did what we expected
300 300 # silent to avoid
301 301 ip.run_cell("1")
302 302 assert pre_explicit.called
303 303 assert post_explicit.called
304 304 info, = pre_explicit.call_args[0]
305 305 result, = post_explicit.call_args[0]
306 306 self.assertEqual(info, result.info)
307 307 # check that post hooks are always called
308 308 [m.reset_mock() for m in all_mocks]
309 309 ip.run_cell("syntax error")
310 310 assert pre_always.called
311 311 assert pre_explicit.called
312 312 assert post_always.called
313 313 assert post_explicit.called
314 314 info, = pre_explicit.call_args[0]
315 315 result, = post_explicit.call_args[0]
316 316 self.assertEqual(info, result.info)
317 317 finally:
318 318 # remove post-exec
319 319 ip.events.unregister('pre_run_cell', pre_explicit)
320 320 ip.events.unregister('pre_execute', pre_always)
321 321 ip.events.unregister('post_run_cell', post_explicit)
322 322 ip.events.unregister('post_execute', post_always)
323 323
324 324 def test_silent_noadvance(self):
325 325 """run_cell(silent=True) doesn't advance execution_count"""
326 326 ec = ip.execution_count
327 327 # silent should force store_history=False
328 328 ip.run_cell("1", store_history=True, silent=True)
329 329
330 330 self.assertEqual(ec, ip.execution_count)
331 331 # double-check that non-silent exec did what we expected
332 332 # silent to avoid
333 333 ip.run_cell("1", store_history=True)
334 334 self.assertEqual(ec+1, ip.execution_count)
335 335
336 336 def test_silent_nodisplayhook(self):
337 337 """run_cell(silent=True) doesn't trigger displayhook"""
338 338 d = dict(called=False)
339 339
340 340 trap = ip.display_trap
341 341 save_hook = trap.hook
342 342
343 343 def failing_hook(*args, **kwargs):
344 344 d['called'] = True
345 345
346 346 try:
347 347 trap.hook = failing_hook
348 348 res = ip.run_cell("1", silent=True)
349 349 self.assertFalse(d['called'])
350 350 self.assertIsNone(res.result)
351 351 # double-check that non-silent exec did what we expected
352 352 # silent to avoid
353 353 ip.run_cell("1")
354 354 self.assertTrue(d['called'])
355 355 finally:
356 356 trap.hook = save_hook
357 357
358 358 def test_ofind_line_magic(self):
359 359 from IPython.core.magic import register_line_magic
360 360
361 361 @register_line_magic
362 362 def lmagic(line):
363 363 "A line magic"
364 364
365 365 # Get info on line magic
366 366 lfind = ip._ofind("lmagic")
367 367 info = OInfo(
368 368 found=True,
369 369 isalias=False,
370 370 ismagic=True,
371 371 namespace="IPython internal",
372 372 obj=lmagic,
373 373 parent=None,
374 374 )
375 375 self.assertEqual(lfind, info)
376 376
377 377 def test_ofind_cell_magic(self):
378 378 from IPython.core.magic import register_cell_magic
379 379
380 380 @register_cell_magic
381 381 def cmagic(line, cell):
382 382 "A cell magic"
383 383
384 384 # Get info on cell magic
385 385 find = ip._ofind("cmagic")
386 386 info = OInfo(
387 387 found=True,
388 388 isalias=False,
389 389 ismagic=True,
390 390 namespace="IPython internal",
391 391 obj=cmagic,
392 392 parent=None,
393 393 )
394 394 self.assertEqual(find, info)
395 395
396 396 def test_ofind_property_with_error(self):
397 397 class A(object):
398 398 @property
399 399 def foo(self):
400 400 raise NotImplementedError() # pragma: no cover
401 401
402 402 a = A()
403 403
404 404 found = ip._ofind("a.foo", [("locals", locals())])
405 405 info = OInfo(
406 406 found=True,
407 407 isalias=False,
408 408 ismagic=False,
409 409 namespace="locals",
410 410 obj=A.foo,
411 411 parent=a,
412 412 )
413 413 self.assertEqual(found, info)
414 414
415 415 def test_ofind_multiple_attribute_lookups(self):
416 416 class A(object):
417 417 @property
418 418 def foo(self):
419 419 raise NotImplementedError() # pragma: no cover
420 420
421 421 a = A()
422 422 a.a = A()
423 423 a.a.a = A()
424 424
425 425 found = ip._ofind("a.a.a.foo", [("locals", locals())])
426 426 info = OInfo(
427 427 found=True,
428 428 isalias=False,
429 429 ismagic=False,
430 430 namespace="locals",
431 431 obj=A.foo,
432 432 parent=a.a.a,
433 433 )
434 434 self.assertEqual(found, info)
435 435
436 436 def test_ofind_slotted_attributes(self):
437 437 class A(object):
438 438 __slots__ = ['foo']
439 439 def __init__(self):
440 440 self.foo = 'bar'
441 441
442 442 a = A()
443 443 found = ip._ofind("a.foo", [("locals", locals())])
444 444 info = OInfo(
445 445 found=True,
446 446 isalias=False,
447 447 ismagic=False,
448 448 namespace="locals",
449 449 obj=a.foo,
450 450 parent=a,
451 451 )
452 452 self.assertEqual(found, info)
453 453
454 454 found = ip._ofind("a.bar", [("locals", locals())])
455 455 expected = OInfo(
456 456 found=False,
457 457 isalias=False,
458 458 ismagic=False,
459 459 namespace=None,
460 460 obj=None,
461 461 parent=a,
462 462 )
463 463 assert found == expected
464 464
465 465 def test_ofind_prefers_property_to_instance_level_attribute(self):
466 466 class A(object):
467 467 @property
468 468 def foo(self):
469 469 return 'bar'
470 470 a = A()
471 471 a.__dict__["foo"] = "baz"
472 472 self.assertEqual(a.foo, "bar")
473 473 found = ip._ofind("a.foo", [("locals", locals())])
474 474 self.assertIs(found.obj, A.foo)
475 475
476 476 def test_custom_syntaxerror_exception(self):
477 477 called = []
478 478 def my_handler(shell, etype, value, tb, tb_offset=None):
479 479 called.append(etype)
480 480 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
481 481
482 482 ip.set_custom_exc((SyntaxError,), my_handler)
483 483 try:
484 484 ip.run_cell("1f")
485 485 # Check that this was called, and only once.
486 486 self.assertEqual(called, [SyntaxError])
487 487 finally:
488 488 # Reset the custom exception hook
489 489 ip.set_custom_exc((), None)
490 490
491 491 def test_custom_exception(self):
492 492 called = []
493 493 def my_handler(shell, etype, value, tb, tb_offset=None):
494 494 called.append(etype)
495 495 shell.showtraceback((etype, value, tb), tb_offset=tb_offset)
496 496
497 497 ip.set_custom_exc((ValueError,), my_handler)
498 498 try:
499 499 res = ip.run_cell("raise ValueError('test')")
500 500 # Check that this was called, and only once.
501 501 self.assertEqual(called, [ValueError])
502 502 # Check that the error is on the result object
503 503 self.assertIsInstance(res.error_in_exec, ValueError)
504 504 finally:
505 505 # Reset the custom exception hook
506 506 ip.set_custom_exc((), None)
507 507
508 508 @mock.patch("builtins.print")
509 509 def test_showtraceback_with_surrogates(self, mocked_print):
510 510 values = []
511 511
512 512 def mock_print_func(value, sep=" ", end="\n", file=sys.stdout, flush=False):
513 513 values.append(value)
514 514 if value == chr(0xD8FF):
515 515 raise UnicodeEncodeError("utf-8", chr(0xD8FF), 0, 1, "")
516 516
517 517 # mock builtins.print
518 518 mocked_print.side_effect = mock_print_func
519 519
520 520 # ip._showtraceback() is replaced in globalipapp.py.
521 521 # Call original method to test.
522 522 interactiveshell.InteractiveShell._showtraceback(ip, None, None, chr(0xD8FF))
523 523
524 524 self.assertEqual(mocked_print.call_count, 2)
525 525 self.assertEqual(values, [chr(0xD8FF), "\\ud8ff"])
526 526
527 527 def test_mktempfile(self):
528 528 filename = ip.mktempfile()
529 529 # Check that we can open the file again on Windows
530 530 with open(filename, "w", encoding="utf-8") as f:
531 531 f.write("abc")
532 532
533 533 filename = ip.mktempfile(data="blah")
534 534 with open(filename, "r", encoding="utf-8") as f:
535 535 self.assertEqual(f.read(), "blah")
536 536
537 537 def test_new_main_mod(self):
538 538 # Smoketest to check that this accepts a unicode module name
539 539 name = u'jiefmw'
540 540 mod = ip.new_main_mod(u'%s.py' % name, name)
541 541 self.assertEqual(mod.__name__, name)
542 542
543 543 def test_get_exception_only(self):
544 544 try:
545 545 raise KeyboardInterrupt
546 546 except KeyboardInterrupt:
547 547 msg = ip.get_exception_only()
548 548 self.assertEqual(msg, 'KeyboardInterrupt\n')
549 549
550 550 try:
551 551 raise DerivedInterrupt("foo")
552 552 except KeyboardInterrupt:
553 553 msg = ip.get_exception_only()
554 554 self.assertEqual(msg, 'IPython.core.tests.test_interactiveshell.DerivedInterrupt: foo\n')
555 555
556 556 def test_inspect_text(self):
557 557 ip.run_cell('a = 5')
558 558 text = ip.object_inspect_text('a')
559 559 self.assertIsInstance(text, str)
560 560
561 561 def test_last_execution_result(self):
562 562 """ Check that last execution result gets set correctly (GH-10702) """
563 563 result = ip.run_cell('a = 5; a')
564 564 self.assertTrue(ip.last_execution_succeeded)
565 565 self.assertEqual(ip.last_execution_result.result, 5)
566 566
567 567 result = ip.run_cell('a = x_invalid_id_x')
568 568 self.assertFalse(ip.last_execution_succeeded)
569 569 self.assertFalse(ip.last_execution_result.success)
570 570 self.assertIsInstance(ip.last_execution_result.error_in_exec, NameError)
571 571
572 572 def test_reset_aliasing(self):
573 573 """ Check that standard posix aliases work after %reset. """
574 574 if os.name != 'posix':
575 575 return
576 576
577 577 ip.reset()
578 578 for cmd in ('clear', 'more', 'less', 'man'):
579 579 res = ip.run_cell('%' + cmd)
580 580 self.assertEqual(res.success, True)
581 581
582 582
583 583 @pytest.mark.skipif(
584 584 sys.implementation.name == "pypy"
585 585 and ((7, 3, 13) < sys.implementation.version < (7, 3, 16)),
586 586 reason="Unicode issues with scandir on PyPy, see https://github.com/pypy/pypy/issues/4860",
587 587 )
588 588 class TestSafeExecfileNonAsciiPath(unittest.TestCase):
589 589 @onlyif_unicode_paths
590 590 def setUp(self):
591 591 self.BASETESTDIR = tempfile.mkdtemp()
592 592 self.TESTDIR = join(self.BASETESTDIR, u"Γ₯Àâ")
593 593 os.mkdir(self.TESTDIR)
594 594 with open(
595 595 join(self.TESTDIR, "Γ₯Àâtestscript.py"), "w", encoding="utf-8"
596 596 ) as sfile:
597 597 sfile.write("pass\n")
598 598 self.oldpath = os.getcwd()
599 599 os.chdir(self.TESTDIR)
600 600 self.fname = u"Γ₯Àâtestscript.py"
601 601
602 602 def tearDown(self):
603 603 os.chdir(self.oldpath)
604 604 shutil.rmtree(self.BASETESTDIR)
605 605
606 606 @onlyif_unicode_paths
607 607 def test_1(self):
608 608 """Test safe_execfile with non-ascii path
609 609 """
610 610 ip.safe_execfile(self.fname, {}, raise_exceptions=True)
611 611
612 612 class ExitCodeChecks(tt.TempFileMixin):
613 613
614 614 def setUp(self):
615 615 self.system = ip.system_raw
616 616
617 617 def test_exit_code_ok(self):
618 618 self.system('exit 0')
619 619 self.assertEqual(ip.user_ns['_exit_code'], 0)
620 620
621 621 def test_exit_code_error(self):
622 622 self.system('exit 1')
623 623 self.assertEqual(ip.user_ns['_exit_code'], 1)
624 624
625 625 @skipif(not hasattr(signal, 'SIGALRM'))
626 626 def test_exit_code_signal(self):
627 627 self.mktmp("import signal, time\n"
628 628 "signal.setitimer(signal.ITIMER_REAL, 0.1)\n"
629 629 "time.sleep(1)\n")
630 630 self.system("%s %s" % (sys.executable, self.fname))
631 631 self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
632 632
633 633 @onlyif_cmds_exist("csh")
634 634 def test_exit_code_signal_csh(self): # pragma: no cover
635 635 SHELL = os.environ.get("SHELL", None)
636 636 os.environ["SHELL"] = find_cmd("csh")
637 637 try:
638 638 self.test_exit_code_signal()
639 639 finally:
640 640 if SHELL is not None:
641 641 os.environ['SHELL'] = SHELL
642 642 else:
643 643 del os.environ['SHELL']
644 644
645 645
646 646 class TestSystemRaw(ExitCodeChecks):
647 647
648 648 def setUp(self):
649 649 super().setUp()
650 650 self.system = ip.system_raw
651 651
652 652 @onlyif_unicode_paths
653 653 def test_1(self):
654 654 """Test system_raw with non-ascii cmd
655 655 """
656 656 cmd = u'''python -c "'Γ₯Àâ'" '''
657 657 ip.system_raw(cmd)
658 658
659 659 @mock.patch('subprocess.call', side_effect=KeyboardInterrupt)
660 660 @mock.patch('os.system', side_effect=KeyboardInterrupt)
661 661 def test_control_c(self, *mocks):
662 662 try:
663 663 self.system("sleep 1 # won't happen")
664 664 except KeyboardInterrupt: # pragma: no cove
665 665 self.fail(
666 666 "system call should intercept "
667 667 "keyboard interrupt from subprocess.call"
668 668 )
669 669 self.assertEqual(ip.user_ns["_exit_code"], -signal.SIGINT)
670 670
671 671
672 672 @pytest.mark.parametrize("magic_cmd", ["pip", "conda", "cd"])
673 673 def test_magic_warnings(magic_cmd):
674 674 if sys.platform == "win32":
675 675 to_mock = "os.system"
676 676 expected_arg, expected_kwargs = magic_cmd, dict()
677 677 else:
678 678 to_mock = "subprocess.call"
679 679 expected_arg, expected_kwargs = magic_cmd, dict(
680 680 shell=True, executable=os.environ.get("SHELL", None)
681 681 )
682 682
683 683 with mock.patch(to_mock, return_value=0) as mock_sub:
684 684 with pytest.warns(Warning, match=r"You executed the system command"):
685 685 ip.system_raw(magic_cmd)
686 686 mock_sub.assert_called_once_with(expected_arg, **expected_kwargs)
687 687
688 688
689 689 # TODO: Exit codes are currently ignored on Windows.
690 690 class TestSystemPipedExitCode(ExitCodeChecks):
691 691
692 692 def setUp(self):
693 693 super().setUp()
694 694 self.system = ip.system_piped
695 695
696 696 @skip_win32
697 697 def test_exit_code_ok(self):
698 698 ExitCodeChecks.test_exit_code_ok(self)
699 699
700 700 @skip_win32
701 701 def test_exit_code_error(self):
702 702 ExitCodeChecks.test_exit_code_error(self)
703 703
704 704 @skip_win32
705 705 def test_exit_code_signal(self):
706 706 ExitCodeChecks.test_exit_code_signal(self)
707 707
708 708 class TestModules(tt.TempFileMixin):
709 709 def test_extraneous_loads(self):
710 710 """Test we're not loading modules on startup that we shouldn't.
711 711 """
712 712 self.mktmp("import sys\n"
713 713 "print('numpy' in sys.modules)\n"
714 714 "print('ipyparallel' in sys.modules)\n"
715 715 "print('ipykernel' in sys.modules)\n"
716 716 )
717 717 out = "False\nFalse\nFalse\n"
718 718 tt.ipexec_validate(self.fname, out)
719 719
720 720 class Negator(ast.NodeTransformer):
721 721 """Negates all number literals in an AST."""
722 722
723 723 def visit_Num(self, node):
724 node.n = -node.n
724 node.value = -node.value
725 725 return node
726 726
727 727 def visit_Constant(self, node):
728 728 if isinstance(node.value, int):
729 729 return self.visit_Num(node)
730 730 return node
731 731
732 732 class TestAstTransform(unittest.TestCase):
733 733 def setUp(self):
734 734 self.negator = Negator()
735 735 ip.ast_transformers.append(self.negator)
736 736
737 737 def tearDown(self):
738 738 ip.ast_transformers.remove(self.negator)
739 739
740 740 def test_non_int_const(self):
741 741 with tt.AssertPrints("hello"):
742 742 ip.run_cell('print("hello")')
743 743
744 744 def test_run_cell(self):
745 745 with tt.AssertPrints("-34"):
746 746 ip.run_cell("print(12 + 22)")
747 747
748 748 # A named reference to a number shouldn't be transformed.
749 749 ip.user_ns["n"] = 55
750 750 with tt.AssertNotPrints("-55"):
751 751 ip.run_cell("print(n)")
752 752
753 753 def test_timeit(self):
754 754 called = set()
755 755 def f(x):
756 756 called.add(x)
757 757 ip.push({'f':f})
758 758
759 759 with tt.AssertPrints("std. dev. of"):
760 760 ip.run_line_magic("timeit", "-n1 f(1)")
761 761 self.assertEqual(called, {-1})
762 762 called.clear()
763 763
764 764 with tt.AssertPrints("std. dev. of"):
765 765 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
766 766 self.assertEqual(called, {-2, -3})
767 767
768 768 def test_time(self):
769 769 called = []
770 770 def f(x):
771 771 called.append(x)
772 772 ip.push({'f':f})
773 773
774 774 # Test with an expression
775 775 with tt.AssertPrints("Wall time: "):
776 776 ip.run_line_magic("time", "f(5+9)")
777 777 self.assertEqual(called, [-14])
778 778 called[:] = []
779 779
780 780 # Test with a statement (different code path)
781 781 with tt.AssertPrints("Wall time: "):
782 782 ip.run_line_magic("time", "a = f(-3 + -2)")
783 783 self.assertEqual(called, [5])
784 784
785 785 def test_macro(self):
786 786 ip.push({'a':10})
787 787 # The AST transformation makes this do a+=-1
788 788 ip.define_macro("amacro", "a+=1\nprint(a)")
789 789
790 790 with tt.AssertPrints("9"):
791 791 ip.run_cell("amacro")
792 792 with tt.AssertPrints("8"):
793 793 ip.run_cell("amacro")
794 794
795 795 class TestMiscTransform(unittest.TestCase):
796 796
797 797
798 798 def test_transform_only_once(self):
799 799 cleanup = 0
800 800 line_t = 0
801 801 def count_cleanup(lines):
802 802 nonlocal cleanup
803 803 cleanup += 1
804 804 return lines
805 805
806 806 def count_line_t(lines):
807 807 nonlocal line_t
808 808 line_t += 1
809 809 return lines
810 810
811 811 ip.input_transformer_manager.cleanup_transforms.append(count_cleanup)
812 812 ip.input_transformer_manager.line_transforms.append(count_line_t)
813 813
814 814 ip.run_cell('1')
815 815
816 816 assert cleanup == 1
817 817 assert line_t == 1
818 818
819 819 class IntegerWrapper(ast.NodeTransformer):
820 820 """Wraps all integers in a call to Integer()"""
821 821
822 822 # for Python 3.7 and earlier
823 823
824 824 # for Python 3.7 and earlier
825 825 def visit_Num(self, node):
826 826 if isinstance(node.n, int):
827 827 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
828 828 args=[node], keywords=[])
829 829 return node
830 830
831 831 # For Python 3.8+
832 832 def visit_Constant(self, node):
833 833 if isinstance(node.value, int):
834 834 return self.visit_Num(node)
835 835 return node
836 836
837 837
838 838 class TestAstTransform2(unittest.TestCase):
839 839 def setUp(self):
840 840 self.intwrapper = IntegerWrapper()
841 841 ip.ast_transformers.append(self.intwrapper)
842 842
843 843 self.calls = []
844 844 def Integer(*args):
845 845 self.calls.append(args)
846 846 return args
847 847 ip.push({"Integer": Integer})
848 848
849 849 def tearDown(self):
850 850 ip.ast_transformers.remove(self.intwrapper)
851 851 del ip.user_ns['Integer']
852 852
853 853 def test_run_cell(self):
854 854 ip.run_cell("n = 2")
855 855 self.assertEqual(self.calls, [(2,)])
856 856
857 857 # This shouldn't throw an error
858 858 ip.run_cell("o = 2.0")
859 859 self.assertEqual(ip.user_ns['o'], 2.0)
860 860
861 861 def test_run_cell_non_int(self):
862 862 ip.run_cell("n = 'a'")
863 863 assert self.calls == []
864 864
865 865 def test_timeit(self):
866 866 called = set()
867 867 def f(x):
868 868 called.add(x)
869 869 ip.push({'f':f})
870 870
871 871 with tt.AssertPrints("std. dev. of"):
872 872 ip.run_line_magic("timeit", "-n1 f(1)")
873 873 self.assertEqual(called, {(1,)})
874 874 called.clear()
875 875
876 876 with tt.AssertPrints("std. dev. of"):
877 877 ip.run_cell_magic("timeit", "-n1 f(2)", "f(3)")
878 878 self.assertEqual(called, {(2,), (3,)})
879 879
880 880 class ErrorTransformer(ast.NodeTransformer):
881 881 """Throws an error when it sees a number."""
882 882
883 883 def visit_Constant(self, node):
884 884 if isinstance(node.value, int):
885 885 raise ValueError("test")
886 886 return node
887 887
888 888
889 889 class TestAstTransformError(unittest.TestCase):
890 890 def test_unregistering(self):
891 891 err_transformer = ErrorTransformer()
892 892 ip.ast_transformers.append(err_transformer)
893 893
894 894 with self.assertWarnsRegex(UserWarning, "It will be unregistered"):
895 895 ip.run_cell("1 + 2")
896 896
897 897 # This should have been removed.
898 898 self.assertNotIn(err_transformer, ip.ast_transformers)
899 899
900 900
901 901 class StringRejector(ast.NodeTransformer):
902 902 """Throws an InputRejected when it sees a string literal.
903 903
904 904 Used to verify that NodeTransformers can signal that a piece of code should
905 905 not be executed by throwing an InputRejected.
906 906 """
907 907
908 908 def visit_Constant(self, node):
909 909 if isinstance(node.value, str):
910 910 raise InputRejected("test")
911 911 return node
912 912
913 913
914 914 class TestAstTransformInputRejection(unittest.TestCase):
915 915
916 916 def setUp(self):
917 917 self.transformer = StringRejector()
918 918 ip.ast_transformers.append(self.transformer)
919 919
920 920 def tearDown(self):
921 921 ip.ast_transformers.remove(self.transformer)
922 922
923 923 def test_input_rejection(self):
924 924 """Check that NodeTransformers can reject input."""
925 925
926 926 expect_exception_tb = tt.AssertPrints("InputRejected: test")
927 927 expect_no_cell_output = tt.AssertNotPrints("'unsafe'", suppress=False)
928 928
929 929 # Run the same check twice to verify that the transformer is not
930 930 # disabled after raising.
931 931 with expect_exception_tb, expect_no_cell_output:
932 932 ip.run_cell("'unsafe'")
933 933
934 934 with expect_exception_tb, expect_no_cell_output:
935 935 res = ip.run_cell("'unsafe'")
936 936
937 937 self.assertIsInstance(res.error_before_exec, InputRejected)
938 938
939 939 def test__IPYTHON__():
940 940 # This shouldn't raise a NameError, that's all
941 941 __IPYTHON__
942 942
943 943
944 944 class DummyRepr(object):
945 945 def __repr__(self):
946 946 return "DummyRepr"
947 947
948 948 def _repr_html_(self):
949 949 return "<b>dummy</b>"
950 950
951 951 def _repr_javascript_(self):
952 952 return "console.log('hi');", {'key': 'value'}
953 953
954 954
955 955 def test_user_variables():
956 956 # enable all formatters
957 957 ip.display_formatter.active_types = ip.display_formatter.format_types
958 958
959 959 ip.user_ns['dummy'] = d = DummyRepr()
960 960 keys = {'dummy', 'doesnotexist'}
961 961 r = ip.user_expressions({ key:key for key in keys})
962 962
963 963 assert keys == set(r.keys())
964 964 dummy = r["dummy"]
965 965 assert {"status", "data", "metadata"} == set(dummy.keys())
966 966 assert dummy["status"] == "ok"
967 967 data = dummy["data"]
968 968 metadata = dummy["metadata"]
969 969 assert data.get("text/html") == d._repr_html_()
970 970 js, jsmd = d._repr_javascript_()
971 971 assert data.get("application/javascript") == js
972 972 assert metadata.get("application/javascript") == jsmd
973 973
974 974 dne = r["doesnotexist"]
975 975 assert dne["status"] == "error"
976 976 assert dne["ename"] == "NameError"
977 977
978 978 # back to text only
979 979 ip.display_formatter.active_types = ['text/plain']
980 980
981 981 def test_user_expression():
982 982 # enable all formatters
983 983 ip.display_formatter.active_types = ip.display_formatter.format_types
984 984 query = {
985 985 'a' : '1 + 2',
986 986 'b' : '1/0',
987 987 }
988 988 r = ip.user_expressions(query)
989 989 import pprint
990 990 pprint.pprint(r)
991 991 assert set(r.keys()) == set(query.keys())
992 992 a = r["a"]
993 993 assert {"status", "data", "metadata"} == set(a.keys())
994 994 assert a["status"] == "ok"
995 995 data = a["data"]
996 996 metadata = a["metadata"]
997 997 assert data.get("text/plain") == "3"
998 998
999 999 b = r["b"]
1000 1000 assert b["status"] == "error"
1001 1001 assert b["ename"] == "ZeroDivisionError"
1002 1002
1003 1003 # back to text only
1004 1004 ip.display_formatter.active_types = ['text/plain']
1005 1005
1006 1006
1007 1007 class TestSyntaxErrorTransformer(unittest.TestCase):
1008 1008 """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
1009 1009
1010 1010 @staticmethod
1011 1011 def transformer(lines):
1012 1012 for line in lines:
1013 1013 pos = line.find('syntaxerror')
1014 1014 if pos >= 0:
1015 1015 e = SyntaxError('input contains "syntaxerror"')
1016 1016 e.text = line
1017 1017 e.offset = pos + 1
1018 1018 raise e
1019 1019 return lines
1020 1020
1021 1021 def setUp(self):
1022 1022 ip.input_transformers_post.append(self.transformer)
1023 1023
1024 1024 def tearDown(self):
1025 1025 ip.input_transformers_post.remove(self.transformer)
1026 1026
1027 1027 def test_syntaxerror_input_transformer(self):
1028 1028 with tt.AssertPrints('1234'):
1029 1029 ip.run_cell('1234')
1030 1030 with tt.AssertPrints('SyntaxError: invalid syntax'):
1031 1031 ip.run_cell('1 2 3') # plain python syntax error
1032 1032 with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
1033 1033 ip.run_cell('2345 # syntaxerror') # input transformer syntax error
1034 1034 with tt.AssertPrints('3456'):
1035 1035 ip.run_cell('3456')
1036 1036
1037 1037
1038 1038 class TestWarningSuppression(unittest.TestCase):
1039 1039 def test_warning_suppression(self):
1040 1040 ip.run_cell("import warnings")
1041 1041 try:
1042 1042 with self.assertWarnsRegex(UserWarning, "asdf"):
1043 1043 ip.run_cell("warnings.warn('asdf')")
1044 1044 # Here's the real test -- if we run that again, we should get the
1045 1045 # warning again. Traditionally, each warning was only issued once per
1046 1046 # IPython session (approximately), even if the user typed in new and
1047 1047 # different code that should have also triggered the warning, leading
1048 1048 # to much confusion.
1049 1049 with self.assertWarnsRegex(UserWarning, "asdf"):
1050 1050 ip.run_cell("warnings.warn('asdf')")
1051 1051 finally:
1052 1052 ip.run_cell("del warnings")
1053 1053
1054 1054
1055 1055 def test_deprecation_warning(self):
1056 1056 ip.run_cell("""
1057 1057 import warnings
1058 1058 def wrn():
1059 1059 warnings.warn(
1060 1060 "I AM A WARNING",
1061 1061 DeprecationWarning
1062 1062 )
1063 1063 """)
1064 1064 try:
1065 1065 with self.assertWarnsRegex(DeprecationWarning, "I AM A WARNING"):
1066 1066 ip.run_cell("wrn()")
1067 1067 finally:
1068 1068 ip.run_cell("del warnings")
1069 1069 ip.run_cell("del wrn")
1070 1070
1071 1071
1072 1072 class TestImportNoDeprecate(tt.TempFileMixin):
1073 1073
1074 1074 def setUp(self):
1075 1075 """Make a valid python temp file."""
1076 1076 self.mktmp("""
1077 1077 import warnings
1078 1078 def wrn():
1079 1079 warnings.warn(
1080 1080 "I AM A WARNING",
1081 1081 DeprecationWarning
1082 1082 )
1083 1083 """)
1084 1084 super().setUp()
1085 1085
1086 1086 def test_no_dep(self):
1087 1087 """
1088 1088 No deprecation warning should be raised from imported functions
1089 1089 """
1090 1090 ip.run_cell("from {} import wrn".format(self.fname))
1091 1091
1092 1092 with tt.AssertNotPrints("I AM A WARNING"):
1093 1093 ip.run_cell("wrn()")
1094 1094 ip.run_cell("del wrn")
1095 1095
1096 1096
1097 1097 def test_custom_exc_count():
1098 1098 hook = mock.Mock(return_value=None)
1099 1099 ip.set_custom_exc((SyntaxError,), hook)
1100 1100 before = ip.execution_count
1101 1101 ip.run_cell("def foo()", store_history=True)
1102 1102 # restore default excepthook
1103 1103 ip.set_custom_exc((), None)
1104 1104 assert hook.call_count == 1
1105 1105 assert ip.execution_count == before + 1
1106 1106
1107 1107
1108 1108 def test_run_cell_async():
1109 1109 ip.run_cell("import asyncio")
1110 1110 coro = ip.run_cell_async("await asyncio.sleep(0.01)\n5")
1111 1111 assert asyncio.iscoroutine(coro)
1112 1112 loop = asyncio.new_event_loop()
1113 1113 result = loop.run_until_complete(coro)
1114 1114 assert isinstance(result, interactiveshell.ExecutionResult)
1115 1115 assert result.result == 5
1116 1116
1117 1117
1118 1118 def test_run_cell_await():
1119 1119 ip.run_cell("import asyncio")
1120 1120 result = ip.run_cell("await asyncio.sleep(0.01); 10")
1121 1121 assert ip.user_ns["_"] == 10
1122 1122
1123 1123
1124 1124 def test_run_cell_asyncio_run():
1125 1125 ip.run_cell("import asyncio")
1126 1126 result = ip.run_cell("await asyncio.sleep(0.01); 1")
1127 1127 assert ip.user_ns["_"] == 1
1128 1128 result = ip.run_cell("asyncio.run(asyncio.sleep(0.01)); 2")
1129 1129 assert ip.user_ns["_"] == 2
1130 1130 result = ip.run_cell("await asyncio.sleep(0.01); 3")
1131 1131 assert ip.user_ns["_"] == 3
1132 1132
1133 1133
1134 1134 def test_should_run_async():
1135 1135 assert not ip.should_run_async("a = 5", transformed_cell="a = 5")
1136 1136 assert ip.should_run_async("await x", transformed_cell="await x")
1137 1137 assert ip.should_run_async(
1138 1138 "import asyncio; await asyncio.sleep(1)",
1139 1139 transformed_cell="import asyncio; await asyncio.sleep(1)",
1140 1140 )
1141 1141
1142 1142
1143 1143 def test_set_custom_completer():
1144 1144 num_completers = len(ip.Completer.matchers)
1145 1145
1146 1146 def foo(*args, **kwargs):
1147 1147 return "I'm a completer!"
1148 1148
1149 1149 ip.set_custom_completer(foo, 0)
1150 1150
1151 1151 # check that we've really added a new completer
1152 1152 assert len(ip.Completer.matchers) == num_completers + 1
1153 1153
1154 1154 # check that the first completer is the function we defined
1155 1155 assert ip.Completer.matchers[0]() == "I'm a completer!"
1156 1156
1157 1157 # clean up
1158 1158 ip.Completer.custom_matchers.pop()
1159 1159
1160 1160
1161 1161 class TestShowTracebackAttack(unittest.TestCase):
1162 1162 """Test that the interactive shell is resilient against the client attack of
1163 1163 manipulating the showtracebacks method. These attacks shouldn't result in an
1164 1164 unhandled exception in the kernel."""
1165 1165
1166 1166 def setUp(self):
1167 1167 self.orig_showtraceback = interactiveshell.InteractiveShell.showtraceback
1168 1168
1169 1169 def tearDown(self):
1170 1170 interactiveshell.InteractiveShell.showtraceback = self.orig_showtraceback
1171 1171
1172 1172 def test_set_show_tracebacks_none(self):
1173 1173 """Test the case of the client setting showtracebacks to None"""
1174 1174
1175 1175 result = ip.run_cell(
1176 1176 """
1177 1177 import IPython.core.interactiveshell
1178 1178 IPython.core.interactiveshell.InteractiveShell.showtraceback = None
1179 1179
1180 1180 assert False, "This should not raise an exception"
1181 1181 """
1182 1182 )
1183 1183 print(result)
1184 1184
1185 1185 assert result.result is None
1186 1186 assert isinstance(result.error_in_exec, TypeError)
1187 1187 assert str(result.error_in_exec) == "'NoneType' object is not callable"
1188 1188
1189 1189 def test_set_show_tracebacks_noop(self):
1190 1190 """Test the case of the client setting showtracebacks to a no op lambda"""
1191 1191
1192 1192 result = ip.run_cell(
1193 1193 """
1194 1194 import IPython.core.interactiveshell
1195 1195 IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
1196 1196
1197 1197 assert False, "This should not raise an exception"
1198 1198 """
1199 1199 )
1200 1200 print(result)
1201 1201
1202 1202 assert result.result is None
1203 1203 assert isinstance(result.error_in_exec, AssertionError)
1204 1204 assert str(result.error_in_exec) == "This should not raise an exception"
1205 1205
1206 1206
1207 1207 @skip_if_not_osx
1208 1208 def test_enable_gui_osx():
1209 1209 simple_prompt = ip.simple_prompt
1210 1210 ip.simple_prompt = False
1211 1211
1212 1212 ip.enable_gui("osx")
1213 1213 assert ip.active_eventloop == "osx"
1214 1214 ip.enable_gui()
1215 1215
1216 1216 # The following line fails for IPython <= 8.25.0
1217 1217 ip.enable_gui("macosx")
1218 1218 assert ip.active_eventloop == "osx"
1219 1219 ip.enable_gui()
1220 1220
1221 1221 ip.simple_prompt = simple_prompt
@@ -1,90 +1,90
1 1
2 2 ===========================
3 3 Custom input transformation
4 4 ===========================
5 5
6 6 IPython extends Python syntax to allow things like magic commands, and help with
7 7 the ``?`` syntax. There are several ways to customise how the user's input is
8 8 processed into Python code to be executed.
9 9
10 10 These hooks are mainly for other projects using IPython as the core of their
11 11 interactive interface. Using them carelessly can easily break IPython!
12 12
13 13 String based transformations
14 14 ============================
15 15
16 16 .. currentmodule:: IPython.core.inputtransforms
17 17
18 18 When the user enters code, it is first processed as a string. By the
19 19 end of this stage, it must be valid Python syntax.
20 20
21 21 .. versionchanged:: 7.0
22 22
23 23 The API for string and token-based transformations has been completely
24 24 redesigned. Any third party code extending input transformation will need to
25 25 be rewritten. The new API is, hopefully, simpler.
26 26
27 27 String based transformations are functions which accept a list of strings:
28 28 each string is a single line of the input cell, including its line ending.
29 29 The transformation function should return output in the same structure.
30 30
31 31 These transformations are in two groups, accessible as attributes of
32 32 the :class:`~IPython.core.interactiveshell.InteractiveShell` instance.
33 33 Each group is a list of transformation functions.
34 34
35 35 * ``input_transformers_cleanup`` run first on input, to do things like stripping
36 36 prompts and leading indents from copied code. It may not be possible at this
37 37 stage to parse the input as valid Python code.
38 38 * Then IPython runs its own transformations to handle its special syntax, like
39 39 ``%magics`` and ``!system`` commands. This part does not expose extension
40 40 points.
41 41 * ``input_transformers_post`` run as the last step, to do things like converting
42 42 float literals into decimal objects. These may attempt to parse the input as
43 43 Python code.
44 44
45 45 These transformers may raise :exc:`SyntaxError` if the input code is invalid, but
46 46 in most cases it is clearer to pass unrecognised code through unmodified and let
47 47 Python's own parser decide whether it is valid.
48 48
49 49 For example, imagine we want to obfuscate our code by reversing each line, so
50 50 we'd write ``)5(f =+ a`` instead of ``a += f(5)``. Here's how we could swap it
51 51 back the right way before IPython tries to run it::
52 52
53 53 def reverse_line_chars(lines):
54 54 new_lines = []
55 55 for line in lines:
56 56 chars = line[:-1] # the newline needs to stay at the end
57 57 new_lines.append(chars[::-1] + '\n')
58 58 return new_lines
59 59
60 60 To start using this::
61 61
62 62 ip = get_ipython()
63 63 ip.input_transformers_cleanup.append(reverse_line_chars)
64 64
65 65 .. versionadded:: 7.17
66 66
67 67 input_transformers can now have an attribute ``has_side_effects`` set to
68 68 `True`, which will prevent the transformers from being ran when IPython is
69 69 trying to guess whether the user input is complete.
70 70
71 71
72 72
73 73 AST transformations
74 74 ===================
75 75
76 76 After the code has been parsed as Python syntax, you can use Python's powerful
77 77 *Abstract Syntax Tree* tools to modify it. Subclass :class:`ast.NodeTransformer`,
78 78 and add an instance to ``shell.ast_transformers``.
79 79
80 80 This example wraps integer literals in an ``Integer`` class, which is useful for
81 81 mathematical frameworks that want to handle e.g. ``1/3`` as a precise fraction::
82 82
83 83
84 84 class IntegerWrapper(ast.NodeTransformer):
85 85 """Wraps all integers in a call to Integer()"""
86 86 def visit_Num(self, node):
87 if isinstance(node.n, int):
87 if isinstance(node.value, int):
88 88 return ast.Call(func=ast.Name(id='Integer', ctx=ast.Load()),
89 89 args=[node], keywords=[])
90 90 return node
General Comments 0
You need to be logged in to leave comments. Login now