Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/build/pkgs/ipython/patches/0001-Allow-InputTransformers-to-raise-SyntaxErrors.patch
8815 views
1
From 23df1eb24e9d217788dd89f19b4f1c49d8ce9adf Mon Sep 17 00:00:00 2001
2
From: Volker Braun <[email protected]>
3
Date: Wed, 21 Aug 2013 22:03:39 +0100
4
Subject: [PATCH] Allow InputTransformers to raise SyntaxErrors.
5
6
---
7
IPython/core/inputsplitter.py | 33 ++-
8
IPython/core/inputtransformer.py | 3 +
9
IPython/core/interactiveshell.py | 308 +++++++++++----------
10
IPython/core/tests/test_inputsplitter.py | 23 +-
11
IPython/core/tests/test_interactiveshell.py | 160 +++++++----
12
IPython/qt/console/frontend_widget.py | 5 +-
13
IPython/sphinxext/ipython_directive.py | 2 +-
14
IPython/terminal/console/interactiveshell.py | 13 +-
15
IPython/terminal/interactiveshell.py | 13 +-
16
IPython/terminal/tests/test_interactivshell.py | 92 ++++++
17
IPython/utils/io.py | 5 +
18
docs/source/config/inputtransforms.rst | 8 +
19
.../pr/incompat-inputsplitter-source-raw-reset.rst | 6 +
20
.../whatsnew/pr/inputtransformer-syntaxerrors.rst | 4 +
21
14 files changed, 435 insertions(+), 240 deletions(-)
22
create mode 100644 docs/source/whatsnew/pr/incompat-inputsplitter-source-raw-reset.rst
23
create mode 100644 docs/source/whatsnew/pr/inputtransformer-syntaxerrors.rst
24
25
diff --git a/IPython/core/inputsplitter.py b/IPython/core/inputsplitter.py
26
index 97c199d..6885490 100644
27
--- a/IPython/core/inputsplitter.py
28
+++ b/IPython/core/inputsplitter.py
29
@@ -535,8 +535,14 @@ def reset(self):
30
self.source_raw = ''
31
self.transformer_accumulating = False
32
self.within_python_line = False
33
+
34
for t in self.transforms:
35
- t.reset()
36
+ try:
37
+ t.reset()
38
+ except SyntaxError:
39
+ # Nothing that calls reset() expects to handle transformer
40
+ # errors
41
+ pass
42
43
def flush_transformers(self):
44
def _flush(transform, out):
45
@@ -553,18 +559,19 @@ def _flush(transform, out):
46
if out is not None:
47
self._store(out)
48
49
- def source_raw_reset(self):
50
- """Return input and raw source and perform a full reset.
51
+ def raw_reset(self):
52
+ """Return raw input only and perform a full reset.
53
"""
54
- self.flush_transformers()
55
- out = self.source
56
- out_r = self.source_raw
57
+ out = self.source_raw
58
self.reset()
59
- return out, out_r
60
+ return out
61
62
def source_reset(self):
63
- self.flush_transformers()
64
- return super(IPythonInputSplitter, self).source_reset()
65
+ try:
66
+ self.flush_transformers()
67
+ return self.source
68
+ finally:
69
+ self.reset()
70
71
def push_accepts_more(self):
72
if self.transformer_accumulating:
73
@@ -576,8 +583,12 @@ def transform_cell(self, cell):
74
"""Process and translate a cell of input.
75
"""
76
self.reset()
77
- self.push(cell)
78
- return self.source_reset()
79
+ try:
80
+ self.push(cell)
81
+ self.flush_transformers()
82
+ return self.source
83
+ finally:
84
+ self.reset()
85
86
def push(self, lines):
87
"""Push one or more lines of IPython input.
88
diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py
89
index 83edf48..eef71a4 100644
90
--- a/IPython/core/inputtransformer.py
91
+++ b/IPython/core/inputtransformer.py
92
@@ -43,6 +43,9 @@ def push(self, line):
93
input or None if the transformer is waiting for more input.
94
95
Must be overridden by subclasses.
96
+
97
+ Implementations may raise ``SyntaxError`` if the input is invalid. No
98
+ other exceptions may be raised.
99
"""
100
pass
101
102
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
103
index 2f96ac6..daa9a3c 100644
104
--- a/IPython/core/interactiveshell.py
105
+++ b/IPython/core/interactiveshell.py
106
@@ -2600,12 +2600,46 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr
107
"""
108
if (not raw_cell) or raw_cell.isspace():
109
return
110
-
111
+
112
if silent:
113
store_history = False
114
115
- self.input_transformer_manager.push(raw_cell)
116
- cell = self.input_transformer_manager.source_reset()
117
+ # If any of our input transformation (input_transformer_manager or
118
+ # prefilter_manager) raises an exception, we store it in this variable
119
+ # so that we can display the error after logging the input and storing
120
+ # it in the history.
121
+ preprocessing_exc_tuple = None
122
+ try:
123
+ # Static input transformations
124
+ cell = self.input_transformer_manager.transform_cell(raw_cell)
125
+ except SyntaxError:
126
+ preprocessing_exc_tuple = sys.exc_info()
127
+ cell = raw_cell # cell has to exist so it can be stored/logged
128
+ else:
129
+ if len(cell.splitlines()) == 1:
130
+ # Dynamic transformations - only applied for single line commands
131
+ with self.builtin_trap:
132
+ try:
133
+ # use prefilter_lines to handle trailing newlines
134
+ # restore trailing newline for ast.parse
135
+ cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
136
+ except Exception:
137
+ # don't allow prefilter errors to crash IPython
138
+ preprocessing_exc_tuple = sys.exc_info()
139
+
140
+ # Store raw and processed history
141
+ if store_history:
142
+ self.history_manager.store_inputs(self.execution_count,
143
+ cell, raw_cell)
144
+ if not silent:
145
+ self.logger.log(cell, raw_cell)
146
+
147
+ # Display the exception if input processing failed.
148
+ if preprocessing_exc_tuple is not None:
149
+ self.showtraceback(preprocessing_exc_tuple)
150
+ if store_history:
151
+ self.execution_count += 1
152
+ return
153
154
# Our own compiler remembers the __future__ environment. If we want to
155
# run code with a separate __future__ environment, use the default
156
@@ -2613,73 +2647,53 @@ def run_cell(self, raw_cell, store_history=False, silent=False, shell_futures=Tr
157
compiler = self.compile if shell_futures else CachingCompiler()
158
159
with self.builtin_trap:
160
- prefilter_failed = False
161
- if len(cell.splitlines()) == 1:
162
- try:
163
- # use prefilter_lines to handle trailing newlines
164
- # restore trailing newline for ast.parse
165
- cell = self.prefilter_manager.prefilter_lines(cell) + '\n'
166
- except AliasError as e:
167
- error(e)
168
- prefilter_failed = True
169
- except Exception:
170
- # don't allow prefilter errors to crash IPython
171
- self.showtraceback()
172
- prefilter_failed = True
173
-
174
- # Store raw and processed history
175
- if store_history:
176
- self.history_manager.store_inputs(self.execution_count,
177
- cell, raw_cell)
178
- if not silent:
179
- self.logger.log(cell, raw_cell)
180
-
181
- if not prefilter_failed:
182
- # don't run if prefilter failed
183
- cell_name = self.compile.cache(cell, self.execution_count)
184
+ cell_name = self.compile.cache(cell, self.execution_count)
185
186
- with self.display_trap:
187
+ with self.display_trap:
188
+ # Compile to bytecode
189
+ try:
190
+ code_ast = compiler.ast_parse(cell, filename=cell_name)
191
+ except IndentationError:
192
+ self.showindentationerror()
193
+ if store_history:
194
+ self.execution_count += 1
195
+ return None
196
+ except (OverflowError, SyntaxError, ValueError, TypeError,
197
+ MemoryError):
198
+ self.showsyntaxerror()
199
+ if store_history:
200
+ self.execution_count += 1
201
+ return None
202
+
203
+ # Apply AST transformations
204
+ code_ast = self.transform_ast(code_ast)
205
+
206
+ # Execute the user code
207
+ interactivity = "none" if silent else self.ast_node_interactivity
208
+ self.run_ast_nodes(code_ast.body, cell_name,
209
+ interactivity=interactivity, compiler=compiler)
210
+
211
+ # Execute any registered post-execution functions.
212
+ # unless we are silent
213
+ post_exec = [] if silent else self._post_execute.iteritems()
214
+
215
+ for func, status in post_exec:
216
+ if self.disable_failing_post_execute and not status:
217
+ continue
218
try:
219
- code_ast = compiler.ast_parse(cell, filename=cell_name)
220
- except IndentationError:
221
- self.showindentationerror()
222
- if store_history:
223
- self.execution_count += 1
224
- return None
225
- except (OverflowError, SyntaxError, ValueError, TypeError,
226
- MemoryError):
227
- self.showsyntaxerror()
228
- if store_history:
229
- self.execution_count += 1
230
- return None
231
-
232
- code_ast = self.transform_ast(code_ast)
233
-
234
- interactivity = "none" if silent else self.ast_node_interactivity
235
- self.run_ast_nodes(code_ast.body, cell_name,
236
- interactivity=interactivity, compiler=compiler)
237
-
238
- # Execute any registered post-execution functions.
239
- # unless we are silent
240
- post_exec = [] if silent else self._post_execute.iteritems()
241
-
242
- for func, status in post_exec:
243
- if self.disable_failing_post_execute and not status:
244
- continue
245
- try:
246
- func()
247
- except KeyboardInterrupt:
248
- print("\nKeyboardInterrupt", file=io.stderr)
249
- except Exception:
250
- # register as failing:
251
- self._post_execute[func] = False
252
- self.showtraceback()
253
- print('\n'.join([
254
- "post-execution function %r produced an error." % func,
255
- "If this problem persists, you can disable failing post-exec functions with:",
256
- "",
257
- " get_ipython().disable_failing_post_execute = True"
258
- ]), file=io.stderr)
259
+ func()
260
+ except KeyboardInterrupt:
261
+ print("\nKeyboardInterrupt", file=io.stderr)
262
+ except Exception:
263
+ # register as failing:
264
+ self._post_execute[func] = False
265
+ self.showtraceback()
266
+ print('\n'.join([
267
+ "post-execution function %r produced an error." % func,
268
+ "If this problem persists, you can disable failing post-exec functions with:",
269
+ "",
270
+ " get_ipython().disable_failing_post_execute = True"
271
+ ]), file=io.stderr)
272
273
if store_history:
274
# Write output to the database. Does nothing unless
275
diff --git a/IPython/core/tests/test_inputsplitter.py b/IPython/core/tests/test_inputsplitter.py
276
index 9508979..196384d 100644
277
--- a/IPython/core/tests/test_inputsplitter.py
278
+++ b/IPython/core/tests/test_inputsplitter.py
279
@@ -410,7 +410,8 @@ def test_syntax(self):
280
continue
281
282
isp.push(raw+'\n')
283
- out, out_raw = isp.source_raw_reset()
284
+ out_raw = isp.source_raw
285
+ out = isp.source_reset()
286
self.assertEqual(out.rstrip(), out_t,
287
tt.pair_fail_msg.format("inputsplitter",raw, out_t, out))
288
self.assertEqual(out_raw.rstrip(), raw.rstrip())
289
@@ -424,12 +425,13 @@ def test_syntax_multiline(self):
290
for lraw, out_t_part in line_pairs:
291
if out_t_part is not None:
292
out_t_parts.append(out_t_part)
293
-
294
+
295
if lraw is not None:
296
isp.push(lraw)
297
raw_parts.append(lraw)
298
299
- out, out_raw = isp.source_raw_reset()
300
+ out_raw = isp.source_raw
301
+ out = isp.source_reset()
302
out_t = '\n'.join(out_t_parts).rstrip()
303
raw = '\n'.join(raw_parts).rstrip()
304
self.assertEqual(out.rstrip(), out_t)
305
@@ -496,7 +498,8 @@ def test_cellmagic_preempt(self):
306
# Here we just return input so we can use it in a test suite, but a
307
# real interpreter would instead send it for execution somewhere.
308
#src = isp.source; raise EOFError # dbg
309
- src, raw = isp.source_raw_reset()
310
+ raw = isp.source_raw
311
+ src = isp.source_reset()
312
print 'Input source was:\n', src
313
print 'Raw source was:\n', raw
314
except EOFError:
315
@@ -543,12 +546,10 @@ class CellMagicsCommon(object):
316
317
def test_whole_cell(self):
318
src = "%%cellm line\nbody\n"
319
- sp = self.sp
320
- sp.push(src)
321
- out = sp.source_reset()
322
+ out = self.sp.transform_cell(src)
323
ref = u"get_ipython().run_cell_magic({u}'cellm', {u}'line', {u}'body')\n"
324
nt.assert_equal(out, py3compat.u_format(ref))
325
-
326
+
327
def test_cellmagic_help(self):
328
self.sp.push('%%cellm?')
329
nt.assert_false(self.sp.push_accepts_more())
330
diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py
331
index 5176bd1..9e8a000 100644
332
--- a/IPython/core/tests/test_interactiveshell.py
333
+++ b/IPython/core/tests/test_interactiveshell.py
334
@@ -33,6 +33,7 @@
335
import nose.tools as nt
336
337
# Our own
338
+from IPython.core.inputtransformer import InputTransformer
339
from IPython.testing.decorators import skipif, onlyif_unicode_paths
340
from IPython.testing import tools as tt
341
from IPython.utils import io
342
@@ -637,16 +638,53 @@ def test_user_expression():
343
data = a['data']
344
metadata = a['metadata']
345
nt.assert_equal(data.get('text/plain'), '3')
346
-
347
+
348
b = r['b']
349
nt.assert_equal(b['status'], 'error')
350
nt.assert_equal(b['ename'], 'ZeroDivisionError')
351
-
352
+
353
# back to text only
354
ip.display_formatter.active_types = ['text/plain']
355
-
356
357
358
359
360
361
+class TestSyntaxErrorTransformer(unittest.TestCase):
362
+ """Check that SyntaxError raised by an input transformer is handled by run_cell()"""
363
+
364
+ class SyntaxErrorTransformer(InputTransformer):
365
+
366
+ def push(self, line):
367
+ pos = line.find('syntaxerror')
368
+ if pos >= 0:
369
+ e = SyntaxError('input contains "syntaxerror"')
370
+ e.text = line
371
+ e.offset = pos + 1
372
+ raise e
373
+ return line
374
+
375
+ def reset(self):
376
+ pass
377
+
378
+ def setUp(self):
379
+ self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer()
380
+ ip.input_splitter.python_line_transforms.append(self.transformer)
381
+ ip.input_transformer_manager.python_line_transforms.append(self.transformer)
382
+
383
+ def tearDown(self):
384
+ ip.input_splitter.python_line_transforms.remove(self.transformer)
385
+ ip.input_transformer_manager.python_line_transforms.remove(self.transformer)
386
+
387
+ def test_syntaxerror_input_transformer(self):
388
+ with tt.AssertPrints('1234'):
389
+ ip.run_cell('1234')
390
+ with tt.AssertPrints('SyntaxError: invalid syntax'):
391
+ ip.run_cell('1 2 3') # plain python syntax error
392
+ with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'):
393
+ ip.run_cell('2345 # syntaxerror') # input transformer syntax error
394
+ with tt.AssertPrints('3456'):
395
+ ip.run_cell('3456')
396
+
397
+
398
+
399
diff --git a/IPython/qt/console/frontend_widget.py b/IPython/qt/console/frontend_widget.py
400
index 524cf76..0dc598b 100644
401
--- a/IPython/qt/console/frontend_widget.py
402
+++ b/IPython/qt/console/frontend_widget.py
403
@@ -205,7 +205,10 @@ def _is_complete(self, source, interactive):
404
'interactive' is True; otherwise, it is False.
405
"""
406
self._input_splitter.reset()
407
- complete = self._input_splitter.push(source)
408
+ try:
409
+ complete = self._input_splitter.push(source)
410
+ except SyntaxError:
411
+ return True
412
if interactive:
413
complete = not self._input_splitter.push_accepts_more()
414
return complete
415
diff --git a/IPython/sphinxext/ipython_directive.py b/IPython/sphinxext/ipython_directive.py
416
index c253448..7105603 100644
417
--- a/IPython/sphinxext/ipython_directive.py
418
+++ b/IPython/sphinxext/ipython_directive.py
419
@@ -252,7 +252,7 @@ def process_input_line(self, line, store_history=True):
420
splitter.push(line)
421
more = splitter.push_accepts_more()
422
if not more:
423
- source_raw = splitter.source_raw_reset()[1]
424
+ source_raw = splitter.raw_reset()
425
self.IP.run_cell(source_raw, store_history=store_history)
426
finally:
427
sys.stdout = stdout
428
diff --git a/IPython/terminal/console/interactiveshell.py b/IPython/terminal/console/interactiveshell.py
429
index bd135cf..286dc6d 100644
430
--- a/IPython/terminal/console/interactiveshell.py
431
+++ b/IPython/terminal/console/interactiveshell.py
432
@@ -456,7 +456,7 @@ def interact(self, display_banner=None):
433
#double-guard against keyboardinterrupts during kbdint handling
434
try:
435
self.write('\nKeyboardInterrupt\n')
436
- source_raw = self.input_splitter.source_raw_reset()[1]
437
+ source_raw = self.input_splitter.raw_reset()
438
hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
439
more = False
440
except KeyboardInterrupt:
441
@@ -478,13 +478,18 @@ def interact(self, display_banner=None):
442
# asynchronously by signal handlers, for example.
443
self.showtraceback()
444
else:
445
- self.input_splitter.push(line)
446
- more = self.input_splitter.push_accepts_more()
447
+ try:
448
+ self.input_splitter.push(line)
449
+ more = self.input_splitter.push_accepts_more()
450
+ except SyntaxError:
451
+ # Run the code directly - run_cell takes care of displaying
452
+ # the exception.
453
+ more = False
454
if (self.SyntaxTB.last_syntax_error and
455
self.autoedit_syntax):
456
self.edit_syntax_error()
457
if not more:
458
- source_raw = self.input_splitter.source_raw_reset()[1]
459
+ source_raw = self.input_splitter.raw_reset()
460
hlen_b4_cell = self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
461
self.run_cell(source_raw)
462
463
diff --git a/IPython/terminal/interactiveshell.py b/IPython/terminal/interactiveshell.py
464
index 32a6c17..cd00e68 100644
465
--- a/IPython/terminal/interactiveshell.py
466
+++ b/IPython/terminal/interactiveshell.py
467
@@ -522,7 +522,7 @@ def interact(self, display_banner=None):
468
#double-guard against keyboardinterrupts during kbdint handling
469
try:
470
self.write('\nKeyboardInterrupt\n')
471
- source_raw = self.input_splitter.source_raw_reset()[1]
472
+ source_raw = self.input_splitter.raw_reset()
473
hlen_b4_cell = \
474
self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
475
more = False
476
@@ -545,13 +545,18 @@ def interact(self, display_banner=None):
477
# asynchronously by signal handlers, for example.
478
self.showtraceback()
479
else:
480
- self.input_splitter.push(line)
481
- more = self.input_splitter.push_accepts_more()
482
+ try:
483
+ self.input_splitter.push(line)
484
+ more = self.input_splitter.push_accepts_more()
485
+ except SyntaxError:
486
+ # Run the code directly - run_cell takes care of displaying
487
+ # the exception.
488
+ more = False
489
if (self.SyntaxTB.last_syntax_error and
490
self.autoedit_syntax):
491
self.edit_syntax_error()
492
if not more:
493
- source_raw = self.input_splitter.source_raw_reset()[1]
494
+ source_raw = self.input_splitter.raw_reset()
495
self.run_cell(source_raw, store_history=True)
496
hlen_b4_cell = \
497
self._replace_rlhist_multiline(source_raw, hlen_b4_cell)
498
diff --git a/IPython/terminal/tests/test_interactivshell.py b/IPython/terminal/tests/test_interactivshell.py
499
index 6ab4acb..9437aff 100644
500
--- a/IPython/terminal/tests/test_interactivshell.py
501
+++ b/IPython/terminal/tests/test_interactivshell.py
502
@@ -17,12 +17,68 @@
503
#-----------------------------------------------------------------------------
504
# stdlib
505
import sys
506
+import types
507
import unittest
508
509
+from IPython.core.inputtransformer import InputTransformer
510
from IPython.testing.decorators import skipif
511
from IPython.utils import py3compat
512
from IPython.testing import tools as tt
513
514
+# Decorator for interaction loop tests -----------------------------------------
515
+
516
+class mock_input_helper(object):
517
+ """Machinery for tests of the main interact loop.
518
+
519
+ Used by the mock_input decorator.
520
+ """
521
+ def __init__(self, testgen):
522
+ self.testgen = testgen
523
+ self.exception = None
524
+ self.ip = get_ipython()
525
+
526
+ def __enter__(self):
527
+ self.orig_raw_input = self.ip.raw_input
528
+ self.ip.raw_input = self.fake_input
529
+ return self
530
+
531
+ def __exit__(self, etype, value, tb):
532
+ self.ip.raw_input = self.orig_raw_input
533
+
534
+ def fake_input(self, prompt):
535
+ try:
536
+ return next(self.testgen)
537
+ except StopIteration:
538
+ self.ip.exit_now = True
539
+ return u''
540
+ except:
541
+ self.exception = sys.exc_info()
542
+ self.ip.exit_now = True
543
+ return u''
544
+
545
+def mock_input(testfunc):
546
+ """Decorator for tests of the main interact loop.
547
+
548
+ Write the test as a generator, yield-ing the input strings, which IPython
549
+ will see as if they were typed in at the prompt.
550
+ """
551
+ def test_method(self):
552
+ testgen = testfunc(self)
553
+ with mock_input_helper(testgen) as mih:
554
+ mih.ip.interact(display_banner=False)
555
+
556
+ if mih.exception is not None:
557
+ # Re-raise captured exception
558
+ etype, value, tb = mih.exception
559
+ import traceback
560
+ traceback.print_tb(tb, file=sys.stdout)
561
+ del tb # Avoid reference loop
562
+ raise value
563
+
564
+ return test_method
565
+
566
+# Test classes -----------------------------------------------------------------
567
+
568
class InteractiveShellTestCase(unittest.TestCase):
569
def rl_hist_entries(self, rl, n):
570
"""Get last n readline history entries as a list"""
571
@@ -171,6 +227,42 @@ def test_replace_multiline_hist_replaces_empty_line(self):
572
expected = [ py3compat.unicode_to_str(e, enc) for e in expected ]
573
self.assertEqual(hist, expected)
574
575
+ @mock_input
576
+ def test_inputtransformer_syntaxerror(self):
577
+ ip = get_ipython()
578
+ transformer = SyntaxErrorTransformer()
579
+ ip.input_splitter.python_line_transforms.append(transformer)
580
+ ip.input_transformer_manager.python_line_transforms.append(transformer)
581
+
582
+ try:
583
+ #raise Exception
584
+ with tt.AssertPrints('4', suppress=False):
585
+ yield u'print(2*2)'
586
+
587
+ with tt.AssertPrints('SyntaxError: input contains', suppress=False):
588
+ yield u'print(2345) # syntaxerror'
589
+
590
+ with tt.AssertPrints('16', suppress=False):
591
+ yield u'print(4*4)'
592
+
593
+ finally:
594
+ ip.input_splitter.python_line_transforms.remove(transformer)
595
+ ip.input_transformer_manager.python_line_transforms.remove(transformer)
596
+
597
+
598
+class SyntaxErrorTransformer(InputTransformer):
599
+ def push(self, line):
600
+ pos = line.find('syntaxerror')
601
+ if pos >= 0:
602
+ e = SyntaxError('input contains "syntaxerror"')
603
+ e.text = line
604
+ e.offset = pos + 1
605
+ raise e
606
+ return line
607
+
608
+ def reset(self):
609
+ pass
610
+
611
class TerminalMagicsTestCase(unittest.TestCase):
612
def test_paste_magics_message(self):
613
"""Test that an IndentationError while using paste magics doesn't
614
diff --git a/IPython/utils/io.py b/IPython/utils/io.py
615
index 5cd2228..c86d2ae 100644
616
--- a/IPython/utils/io.py
617
+++ b/IPython/utils/io.py
618
@@ -43,6 +43,11 @@ def clone(meth):
619
for meth in filter(clone, dir(stream)):
620
setattr(self, meth, getattr(stream, meth))
621
622
+ def __repr__(self):
623
+ cls = self.__class__
624
+ tpl = '{mod}.{cls}({args})'
625
+ return tpl.format(mod=cls.__module__, cls=cls.__name__, args=self.stream)
626
+
627
def write(self,data):
628
try:
629
self._swrite(data)
630
diff --git a/docs/source/config/inputtransforms.rst b/docs/source/config/inputtransforms.rst
631
index a28c13d..1f9347f 100644
632
--- a/docs/source/config/inputtransforms.rst
633
+++ b/docs/source/config/inputtransforms.rst
634
@@ -43,6 +43,14 @@ to tell when a block of input is complete, and
635
to transform complete cells. If you add a transformer, you should make sure that
636
it gets added to both.
637
638
+These transformers may raise :exc:`SyntaxError` if the input code is invalid, but
639
+in most cases it is clearer to pass unrecognised code through unmodified and let
640
+Python's own parser decide whether it is valid.
641
+
642
+.. versionchanged:: 2.0
643
+
644
+ Added the option to raise :exc:`SyntaxError`.
645
+
646
Stateless transformations
647
-------------------------
648
649
diff --git a/docs/source/whatsnew/pr/incompat-inputsplitter-source-raw-reset.rst b/docs/source/whatsnew/pr/incompat-inputsplitter-source-raw-reset.rst
650
new file mode 100644
651
index 0000000..7e9056f
652
--- /dev/null
653
+++ b/docs/source/whatsnew/pr/incompat-inputsplitter-source-raw-reset.rst
654
@@ -0,0 +1,6 @@
655
+* :class:`IPython.core.inputsplitter.IPythonInputSplitter` no longer has a method
656
+ ``source_raw_reset()``, but gains :meth:`~IPython.core.inputsplitter.IPythonInputSplitter.raw_reset`
657
+ instead. Use of ``source_raw_reset`` can be replaced with::
658
+
659
+ raw = isp.source_raw
660
+ transformed = isp.source_reset()
661
diff --git a/docs/source/whatsnew/pr/inputtransformer-syntaxerrors.rst b/docs/source/whatsnew/pr/inputtransformer-syntaxerrors.rst
662
new file mode 100644
663
index 0000000..74d3594
664
--- /dev/null
665
+++ b/docs/source/whatsnew/pr/inputtransformer-syntaxerrors.rst
666
@@ -0,0 +1,4 @@
667
+* Input transformers (see :doc:`/config/inputtransforms`) may now raise
668
+ :exc:`SyntaxError` if they determine that input is invalid. The input
669
+ transformation machinery in IPython will handle displaying the exception to
670
+ the user and resetting state.
671
--
672
1.8.5.3
673
674
675