mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	[3.13] gh-118893: Evaluate all statements in the new REPL separately (GH-119318) (#119408)
(cherry picked from commit a3e4fec873)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
			
			
This commit is contained in:
		
							parent
							
								
									eafd633fac
								
							
						
					
					
						commit
						a463cd8e45
					
				
					 5 changed files with 128 additions and 9 deletions
				
			
		|  | @ -30,6 +30,7 @@ | ||||||
| import linecache | import linecache | ||||||
| import sys | import sys | ||||||
| import code | import code | ||||||
|  | import ast | ||||||
| from types import ModuleType | from types import ModuleType | ||||||
| 
 | 
 | ||||||
| from .readline import _get_reader, multiline_input | from .readline import _get_reader, multiline_input | ||||||
|  | @ -74,9 +75,36 @@ def __init__( | ||||||
|         super().__init__(locals=locals, filename=filename, local_exit=local_exit)  # type: ignore[call-arg] |         super().__init__(locals=locals, filename=filename, local_exit=local_exit)  # type: ignore[call-arg] | ||||||
|         self.can_colorize = _colorize.can_colorize() |         self.can_colorize = _colorize.can_colorize() | ||||||
| 
 | 
 | ||||||
|  |     def showsyntaxerror(self, filename=None): | ||||||
|  |         super().showsyntaxerror(colorize=self.can_colorize) | ||||||
|  | 
 | ||||||
|     def showtraceback(self): |     def showtraceback(self): | ||||||
|         super().showtraceback(colorize=self.can_colorize) |         super().showtraceback(colorize=self.can_colorize) | ||||||
| 
 | 
 | ||||||
|  |     def runsource(self, source, filename="<input>", symbol="single"): | ||||||
|  |         try: | ||||||
|  |             tree = ast.parse(source) | ||||||
|  |         except (OverflowError, SyntaxError, ValueError): | ||||||
|  |             self.showsyntaxerror(filename) | ||||||
|  |             return False | ||||||
|  |         if tree.body: | ||||||
|  |             *_, last_stmt = tree.body | ||||||
|  |         for stmt in tree.body: | ||||||
|  |             wrapper = ast.Interactive if stmt is last_stmt else ast.Module | ||||||
|  |             the_symbol = symbol if stmt is last_stmt else "exec" | ||||||
|  |             item = wrapper([stmt]) | ||||||
|  |             try: | ||||||
|  |                 code = compile(item, filename, the_symbol) | ||||||
|  |             except (OverflowError, ValueError): | ||||||
|  |                     self.showsyntaxerror(filename) | ||||||
|  |                     return False | ||||||
|  | 
 | ||||||
|  |             if code is None: | ||||||
|  |                 return True | ||||||
|  | 
 | ||||||
|  |             self.runcode(code) | ||||||
|  |         return False | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def run_multiline_interactive_console( | def run_multiline_interactive_console( | ||||||
|     mainmodule: ModuleType | None= None, future_flags: int = 0 |     mainmodule: ModuleType | None= None, future_flags: int = 0 | ||||||
|  | @ -144,9 +172,6 @@ def more_lines(unicodetext: str) -> bool: | ||||||
| 
 | 
 | ||||||
|             input_name = f"<python-input-{input_n}>" |             input_name = f"<python-input-{input_n}>" | ||||||
|             linecache._register_code(input_name, statement, "<stdin>")  # type: ignore[attr-defined] |             linecache._register_code(input_name, statement, "<stdin>")  # type: ignore[attr-defined] | ||||||
|             symbol = "single" if not contains_pasted_code else "exec" |  | ||||||
|             more = console.push(_strip_final_indent(statement), filename=input_name, _symbol=symbol)  # type: ignore[call-arg] |  | ||||||
|             if contains_pasted_code and more: |  | ||||||
|             more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg] |             more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg] | ||||||
|             assert not more |             assert not more | ||||||
|             input_n += 1 |             input_n += 1 | ||||||
|  |  | ||||||
|  | @ -94,7 +94,7 @@ def runcode(self, code): | ||||||
|         except: |         except: | ||||||
|             self.showtraceback() |             self.showtraceback() | ||||||
| 
 | 
 | ||||||
|     def showsyntaxerror(self, filename=None): |     def showsyntaxerror(self, filename=None, **kwargs): | ||||||
|         """Display the syntax error that just occurred. |         """Display the syntax error that just occurred. | ||||||
| 
 | 
 | ||||||
|         This doesn't display a stack trace because there isn't one. |         This doesn't display a stack trace because there isn't one. | ||||||
|  | @ -106,6 +106,7 @@ def showsyntaxerror(self, filename=None): | ||||||
|         The output is written by self.write(), below. |         The output is written by self.write(), below. | ||||||
| 
 | 
 | ||||||
|         """ |         """ | ||||||
|  |         colorize = kwargs.pop('colorize', False) | ||||||
|         type, value, tb = sys.exc_info() |         type, value, tb = sys.exc_info() | ||||||
|         sys.last_exc = value |         sys.last_exc = value | ||||||
|         sys.last_type = type |         sys.last_type = type | ||||||
|  | @ -123,7 +124,7 @@ def showsyntaxerror(self, filename=None): | ||||||
|                 value = SyntaxError(msg, (filename, lineno, offset, line)) |                 value = SyntaxError(msg, (filename, lineno, offset, line)) | ||||||
|                 sys.last_exc = sys.last_value = value |                 sys.last_exc = sys.last_value = value | ||||||
|         if sys.excepthook is sys.__excepthook__: |         if sys.excepthook is sys.__excepthook__: | ||||||
|             lines = traceback.format_exception_only(type, value) |             lines = traceback.format_exception_only(type, value, colorize=colorize) | ||||||
|             self.write(''.join(lines)) |             self.write(''.join(lines)) | ||||||
|         else: |         else: | ||||||
|             # If someone has set sys.excepthook, we let that take precedence |             # If someone has set sys.excepthook, we let that take precedence | ||||||
|  |  | ||||||
							
								
								
									
										92
									
								
								Lib/test/test_pyrepl/test_interact.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								Lib/test/test_pyrepl/test_interact.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | import contextlib | ||||||
|  | import io | ||||||
|  | import unittest | ||||||
|  | from unittest.mock import patch | ||||||
|  | from textwrap import dedent | ||||||
|  | 
 | ||||||
|  | from test.support import force_not_colorized | ||||||
|  | 
 | ||||||
|  | from _pyrepl.simple_interact import InteractiveColoredConsole | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestSimpleInteract(unittest.TestCase): | ||||||
|  |     def test_multiple_statements(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         class A: | ||||||
|  |             def foo(self): | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |         class B: | ||||||
|  |             def bar(self): | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |         a = 1 | ||||||
|  |         a | ||||||
|  |         """) | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         with ( | ||||||
|  |             patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror, | ||||||
|  |             patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource, | ||||||
|  |         ): | ||||||
|  |             more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg] | ||||||
|  |         self.assertFalse(more) | ||||||
|  |         showsyntaxerror.assert_not_called() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def test_multiple_statements_output(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = dedent("""\ | ||||||
|  |         b = 1 | ||||||
|  |         b | ||||||
|  |         a = 1 | ||||||
|  |         a | ||||||
|  |         """) | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         f = io.StringIO() | ||||||
|  |         with contextlib.redirect_stdout(f): | ||||||
|  |             more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg] | ||||||
|  |         self.assertFalse(more) | ||||||
|  |         self.assertEqual(f.getvalue(), "1\n") | ||||||
|  | 
 | ||||||
|  |     def test_empty(self): | ||||||
|  |         namespace = {} | ||||||
|  |         code = "" | ||||||
|  |         console = InteractiveColoredConsole(namespace, filename="<stdin>") | ||||||
|  |         f = io.StringIO() | ||||||
|  |         with contextlib.redirect_stdout(f): | ||||||
|  |             more = console.push(code, filename="<stdin>", _symbol="single")  # type: ignore[call-arg] | ||||||
|  |         self.assertFalse(more) | ||||||
|  |         self.assertEqual(f.getvalue(), "") | ||||||
|  | 
 | ||||||
|  |     def test_runsource_compiles_and_runs_code(self): | ||||||
|  |         console = InteractiveColoredConsole() | ||||||
|  |         source = "print('Hello, world!')" | ||||||
|  |         with patch.object(console, "runcode") as mock_runcode: | ||||||
|  |             console.runsource(source) | ||||||
|  |             mock_runcode.assert_called_once() | ||||||
|  | 
 | ||||||
|  |     def test_runsource_returns_false_for_successful_compilation(self): | ||||||
|  |         console = InteractiveColoredConsole() | ||||||
|  |         source = "print('Hello, world!')" | ||||||
|  |         result = console.runsource(source) | ||||||
|  |         self.assertFalse(result) | ||||||
|  | 
 | ||||||
|  |     @force_not_colorized | ||||||
|  |     def test_runsource_returns_false_for_failed_compilation(self): | ||||||
|  |         console = InteractiveColoredConsole() | ||||||
|  |         source = "print('Hello, world!'" | ||||||
|  |         f = io.StringIO() | ||||||
|  |         with contextlib.redirect_stderr(f): | ||||||
|  |             result = console.runsource(source) | ||||||
|  |         self.assertFalse(result) | ||||||
|  |         self.assertIn('SyntaxError', f.getvalue()) | ||||||
|  | 
 | ||||||
|  |     def test_runsource_shows_syntax_error_for_failed_compilation(self): | ||||||
|  |         console = InteractiveColoredConsole() | ||||||
|  |         source = "print('Hello, world!'" | ||||||
|  |         with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror: | ||||||
|  |             console.runsource(source) | ||||||
|  |             mock_showsyntaxerror.assert_called_once() | ||||||
|  | @ -543,7 +543,7 @@ def test_signatures(self): | ||||||
| 
 | 
 | ||||||
|         self.assertEqual( |         self.assertEqual( | ||||||
|             str(inspect.signature(traceback.format_exception_only)), |             str(inspect.signature(traceback.format_exception_only)), | ||||||
|             '(exc, /, value=<implicit>, *, show_group=False)') |             '(exc, /, value=<implicit>, *, show_group=False, **kwargs)') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PurePythonExceptionFormattingMixin: | class PurePythonExceptionFormattingMixin: | ||||||
|  |  | ||||||
|  | @ -155,7 +155,7 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ | ||||||
|     return list(te.format(chain=chain, colorize=colorize)) |     return list(te.format(chain=chain, colorize=colorize)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def format_exception_only(exc, /, value=_sentinel, *, show_group=False): | def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs): | ||||||
|     """Format the exception part of a traceback. |     """Format the exception part of a traceback. | ||||||
| 
 | 
 | ||||||
|     The return value is a list of strings, each ending in a newline. |     The return value is a list of strings, each ending in a newline. | ||||||
|  | @ -170,10 +170,11 @@ def format_exception_only(exc, /, value=_sentinel, *, show_group=False): | ||||||
|     :exc:`BaseExceptionGroup`, the nested exceptions are included as |     :exc:`BaseExceptionGroup`, the nested exceptions are included as | ||||||
|     well, recursively, with indentation relative to their nesting depth. |     well, recursively, with indentation relative to their nesting depth. | ||||||
|     """ |     """ | ||||||
|  |     colorize = kwargs.get("colorize", False) | ||||||
|     if value is _sentinel: |     if value is _sentinel: | ||||||
|         value = exc |         value = exc | ||||||
|     te = TracebackException(type(value), value, None, compact=True) |     te = TracebackException(type(value), value, None, compact=True) | ||||||
|     return list(te.format_exception_only(show_group=show_group)) |     return list(te.format_exception_only(show_group=show_group, colorize=colorize)) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # -- not official API but folk probably use these two functions. | # -- not official API but folk probably use these two functions. | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Miss Islington (bot)
						Miss Islington (bot)