##// END OF EJS Templates
run_cell returns an ExecutionResult instance...
Thomas Kluyver -
Show More
@@ -30,6 +30,8 b' class DisplayHook(Configurable):'
30 30 """
31 31
32 32 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
33 exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
34 allow_none=True)
33 35 cull_fraction = Float(0.2)
34 36
35 37 def __init__(self, shell=None, cache_size=1000, **kwargs):
@@ -198,6 +200,10 b' class DisplayHook(Configurable):'
198 200 self.shell.push(to_main, interactive=False)
199 201 self.shell.user_ns['_oh'][self.prompt_count] = result
200 202
203 def fill_exec_result(self, result):
204 if self.exec_result is not None:
205 self.exec_result.result = result
206
201 207 def log_output(self, format_dict):
202 208 """Log the output."""
203 209 if 'text/plain' not in format_dict:
@@ -225,6 +231,7 b' class DisplayHook(Configurable):'
225 231 self.write_output_prompt()
226 232 format_dict, md_dict = self.compute_format_data(result)
227 233 self.update_user_ns(result)
234 self.fill_exec_result(result)
228 235 if format_dict:
229 236 self.write_format_data(format_dict, md_dict)
230 237 self.log_output(format_dict)
@@ -192,9 +192,21 b' class DummyMod(object):'
192 192 a namespace must be assigned to the module's __dict__."""
193 193 pass
194 194
195 #-----------------------------------------------------------------------------
196 # Main IPython class
197 #-----------------------------------------------------------------------------
195
196 class ExecutionResult(object):
197 """The result of a call to :meth:`InteractiveShell.run_cell`
198
199 Stores information about what took place.
200 """
201 execution_count = None
202 error_before_exec = None
203 error_in_exec = None
204 result = None
205
206 @property
207 def success(self):
208 return (self.error_before_exec is None) and (self.error_in_exec is None)
209
198 210
199 211 class InteractiveShell(SingletonConfigurable):
200 212 """An enhanced, interactive shell for Python."""
@@ -2760,13 +2772,26 b' class InteractiveShell(SingletonConfigurable):'
2760 2772 shell. It will both be affected by previous __future__ imports, and
2761 2773 any __future__ imports in the code will affect the shell. If False,
2762 2774 __future__ imports are not shared in either direction.
2775
2776 Returns
2777 -------
2778 result : :class:`ExecutionResult`
2763 2779 """
2780 result = ExecutionResult()
2781
2764 2782 if (not raw_cell) or raw_cell.isspace():
2765 return
2783 return result
2766 2784
2767 2785 if silent:
2768 2786 store_history = False
2769 2787
2788 if store_history:
2789 result.execution_count = self.execution_count
2790
2791 def error_before_exec(value):
2792 result.error_before_exec = value
2793 return result
2794
2770 2795 self.events.trigger('pre_execute')
2771 2796 if not silent:
2772 2797 self.events.trigger('pre_run_cell')
@@ -2806,7 +2831,7 b' class InteractiveShell(SingletonConfigurable):'
2806 2831 self.showtraceback(preprocessing_exc_tuple)
2807 2832 if store_history:
2808 2833 self.execution_count += 1
2809 return
2834 return error_before_exec(preprocessing_exc_tuple[2])
2810 2835
2811 2836 # Our own compiler remembers the __future__ environment. If we want to
2812 2837 # run code with a separate __future__ environment, use the default
@@ -2820,32 +2845,40 b' class InteractiveShell(SingletonConfigurable):'
2820 2845 # Compile to bytecode
2821 2846 try:
2822 2847 code_ast = compiler.ast_parse(cell, filename=cell_name)
2823 except IndentationError:
2848 except IndentationError as e:
2824 2849 self.showindentationerror()
2825 2850 if store_history:
2826 2851 self.execution_count += 1
2827 return None
2852 return error_before_exec(e)
2828 2853 except (OverflowError, SyntaxError, ValueError, TypeError,
2829 MemoryError):
2854 MemoryError) as e:
2830 2855 self.showsyntaxerror()
2831 2856 if store_history:
2832 2857 self.execution_count += 1
2833 return None
2858 return error_before_exec(e)
2834 2859
2835 2860 # Apply AST transformations
2836 2861 try:
2837 2862 code_ast = self.transform_ast(code_ast)
2838 except InputRejected:
2863 except InputRejected as e:
2839 2864 self.showtraceback()
2840 2865 if store_history:
2841 2866 self.execution_count += 1
2842 return None
2867 return error_before_exec(e)
2868
2869 # Give the displayhook a reference to our ExecutionResult so it
2870 # can fill in the output value.
2871 self.displayhook.exec_result = result
2843 2872
2844 2873 # Execute the user code
2845 2874 interactivity = "none" if silent else self.ast_node_interactivity
2846 2875 self.run_ast_nodes(code_ast.body, cell_name,
2847 interactivity=interactivity, compiler=compiler)
2848
2876 interactivity=interactivity, compiler=compiler, result=result)
2877
2878 # Reset this so later displayed values do not modify the
2879 # ExecutionResult
2880 self.displayhook.exec_result = None
2881
2849 2882 self.events.trigger('post_execute')
2850 2883 if not silent:
2851 2884 self.events.trigger('post_run_cell')
@@ -2856,6 +2889,8 b' class InteractiveShell(SingletonConfigurable):'
2856 2889 self.history_manager.store_output(self.execution_count)
2857 2890 # Each cell is a *single* input, regardless of how many lines it has
2858 2891 self.execution_count += 1
2892
2893 return result
2859 2894
2860 2895 def transform_ast(self, node):
2861 2896 """Apply the AST transformations from self.ast_transformers
@@ -2890,7 +2925,7 b' class InteractiveShell(SingletonConfigurable):'
2890 2925
2891 2926
2892 2927 def run_ast_nodes(self, nodelist, cell_name, interactivity='last_expr',
2893 compiler=compile):
2928 compiler=compile, result=None):
2894 2929 """Run a sequence of AST nodes. The execution mode depends on the
2895 2930 interactivity parameter.
2896 2931
@@ -2910,6 +2945,13 b' class InteractiveShell(SingletonConfigurable):'
2910 2945 compiler : callable
2911 2946 A function with the same interface as the built-in compile(), to turn
2912 2947 the AST nodes into code objects. Default is the built-in compile().
2948 result : ExecutionResult, optional
2949 An object to store exceptions that occur during execution.
2950
2951 Returns
2952 -------
2953 True if an exception occurred while running code, False if it finished
2954 running.
2913 2955 """
2914 2956 if not nodelist:
2915 2957 return
@@ -2935,13 +2977,13 b' class InteractiveShell(SingletonConfigurable):'
2935 2977 for i, node in enumerate(to_run_exec):
2936 2978 mod = ast.Module([node])
2937 2979 code = compiler(mod, cell_name, "exec")
2938 if self.run_code(code):
2980 if self.run_code(code, result):
2939 2981 return True
2940 2982
2941 2983 for i, node in enumerate(to_run_interactive):
2942 2984 mod = ast.Interactive([node])
2943 2985 code = compiler(mod, cell_name, "single")
2944 if self.run_code(code):
2986 if self.run_code(code, result):
2945 2987 return True
2946 2988
2947 2989 # Flush softspace
@@ -2958,11 +3000,14 b' class InteractiveShell(SingletonConfigurable):'
2958 3000 # We do only one try/except outside the loop to minimize the impact
2959 3001 # on runtime, and also because if any node in the node list is
2960 3002 # broken, we should stop execution completely.
3003 if result:
3004 result.error_before_exec = sys.exc_info()[1]
2961 3005 self.showtraceback()
3006 return True
2962 3007
2963 3008 return False
2964 3009
2965 def run_code(self, code_obj):
3010 def run_code(self, code_obj, result=None):
2966 3011 """Execute a code object.
2967 3012
2968 3013 When an exception occurs, self.showtraceback() is called to display a
@@ -2972,6 +3017,8 b' class InteractiveShell(SingletonConfigurable):'
2972 3017 ----------
2973 3018 code_obj : code object
2974 3019 A compiled code object, to be executed
3020 result : ExecutionResult, optional
3021 An object to store exceptions that occur during execution.
2975 3022
2976 3023 Returns
2977 3024 -------
@@ -2982,6 +3029,11 b' class InteractiveShell(SingletonConfigurable):'
2982 3029 # directly, so that the IPython crash handler doesn't get triggered
2983 3030 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
2984 3031
3032 # Convenience function to set result.error_in_exec
3033 def set_result_exc(value=None):
3034 if result is not None:
3035 result.error_in_exec = value if (value is not None) else sys.exc_info()[1]
3036
2985 3037 # we save the original sys.excepthook in the instance, in case config
2986 3038 # code (such as magics) needs access to it.
2987 3039 self.sys_excepthook = old_excepthook
@@ -2994,13 +3046,16 b' class InteractiveShell(SingletonConfigurable):'
2994 3046 finally:
2995 3047 # Reset our crash handler in place
2996 3048 sys.excepthook = old_excepthook
2997 except SystemExit:
3049 except SystemExit as e:
3050 set_result_exc(e)
2998 3051 self.showtraceback(exception_only=True)
2999 3052 warn("To exit: use 'exit', 'quit', or Ctrl-D.", level=1)
3000 3053 except self.custom_exceptions:
3001 3054 etype, value, tb = sys.exc_info()
3055 set_result_exc(value)
3002 3056 self.CustomTB(etype, value, tb)
3003 3057 except:
3058 set_result_exc()
3004 3059 self.showtraceback()
3005 3060 else:
3006 3061 outflag = 0
@@ -64,35 +64,45 b' class InteractiveShellTestCase(unittest.TestCase):'
64 64 """Just make sure we don't get a horrible error with a blank
65 65 cell of input. Yes, I did overlook that."""
66 66 old_xc = ip.execution_count
67 ip.run_cell('')
67 res = ip.run_cell('')
68 68 self.assertEqual(ip.execution_count, old_xc)
69 self.assertEqual(res.execution_count, None)
69 70
70 71 def test_run_cell_multiline(self):
71 72 """Multi-block, multi-line cells must execute correctly.
72 73 """
74 print(ip.history_manager.input_hist_raw)
75 print(ip.history_manager.output_hist)
73 76 src = '\n'.join(["x=1",
74 77 "y=2",
75 78 "if 1:",
76 79 " x += 1",
77 80 " y += 1",])
78 ip.run_cell(src)
81 res = ip.run_cell(src)
79 82 self.assertEqual(ip.user_ns['x'], 2)
80 83 self.assertEqual(ip.user_ns['y'], 3)
84 self.assertEqual(res.success, True)
85 print(ip.history_manager.input_hist_raw)
86 print(ip.history_manager.output_hist)
87 self.assertEqual(res.result, None)
81 88
82 89 def test_multiline_string_cells(self):
83 90 "Code sprinkled with multiline strings should execute (GH-306)"
84 91 ip.run_cell('tmp=0')
85 92 self.assertEqual(ip.user_ns['tmp'], 0)
86 ip.run_cell('tmp=1;"""a\nb"""\n')
93 res = ip.run_cell('tmp=1;"""a\nb"""\n')
87 94 self.assertEqual(ip.user_ns['tmp'], 1)
95 self.assertEqual(res.success, True)
96 self.assertEqual(res.result, "a\nb")
88 97
89 98 def test_dont_cache_with_semicolon(self):
90 99 "Ending a line with semicolon should not cache the returned object (GH-307)"
91 100 oldlen = len(ip.user_ns['Out'])
92 101 for cell in ['1;', '1;1;']:
93 ip.run_cell(cell, store_history=True)
102 res = ip.run_cell(cell, store_history=True)
94 103 newlen = len(ip.user_ns['Out'])
95 104 self.assertEqual(oldlen, newlen)
105 self.assertIsNone(res.result)
96 106 i = 0
97 107 #also test the default caching behavior
98 108 for cell in ['1', '1;1']:
@@ -101,6 +111,10 b' class InteractiveShellTestCase(unittest.TestCase):'
101 111 i += 1
102 112 self.assertEqual(oldlen+i, newlen)
103 113
114 def test_syntax_error(self):
115 res = ip.run_cell("raise = 3")
116 self.assertIsInstance(res.error_before_exec, SyntaxError)
117
104 118 def test_In_variable(self):
105 119 "Verify that In variable grows with user input (GH-284)"
106 120 oldlen = len(ip.user_ns['In'])
@@ -330,8 +344,9 b' class InteractiveShellTestCase(unittest.TestCase):'
330 344
331 345 try:
332 346 trap.hook = failing_hook
333 ip.run_cell("1", silent=True)
347 res = ip.run_cell("1", silent=True)
334 348 self.assertFalse(d['called'])
349 self.assertIsNone(res.result)
335 350 # double-check that non-silent exec did what we expected
336 351 # silent to avoid
337 352 ip.run_cell("1")
@@ -443,9 +458,11 b' class InteractiveShellTestCase(unittest.TestCase):'
443 458
444 459 ip.set_custom_exc((ValueError,), my_handler)
445 460 try:
446 ip.run_cell("raise ValueError('test')")
461 res = ip.run_cell("raise ValueError('test')")
447 462 # Check that this was called, and only once.
448 463 self.assertEqual(called, [ValueError])
464 # Check that the error is on the result object
465 self.assertIsInstance(res.error_in_exec, ValueError)
449 466 finally:
450 467 # Reset the custom exception hook
451 468 ip.set_custom_exc((), None)
@@ -760,7 +777,9 b' class TestAstTransformInputRejection(unittest.TestCase):'
760 777 ip.run_cell("'unsafe'")
761 778
762 779 with expect_exception_tb, expect_no_cell_output:
763 ip.run_cell("'unsafe'")
780 res = ip.run_cell("'unsafe'")
781
782 self.assertIsInstance(res.error_before_exec, InputRejected)
764 783
765 784 def test__IPYTHON__():
766 785 # This shouldn't raise a NameError, that's all
General Comments 0
You need to be logged in to leave comments. Login now