mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 05:31:20 +00:00 
			
		
		
		
	gh-127495: Append to history file after every statement in PyREPL (GH-132294)
This commit is contained in:
		
							parent
							
								
									614d79231d
								
							
						
					
					
						commit
						276252565c
					
				
					 4 changed files with 47 additions and 1 deletions
				
			
		|  | @ -90,6 +90,7 @@ | |||
|     # "set_pre_input_hook", | ||||
|     "set_startup_hook", | ||||
|     "write_history_file", | ||||
|     "append_history_file", | ||||
|     # ---- multiline extensions ---- | ||||
|     "multiline_input", | ||||
| ] | ||||
|  | @ -453,6 +454,7 @@ def read_history_file(self, filename: str = gethistoryfile()) -> None: | |||
|                         del buffer[:] | ||||
|                     if line: | ||||
|                         history.append(line) | ||||
|         self.set_history_length(self.get_current_history_length()) | ||||
| 
 | ||||
|     def write_history_file(self, filename: str = gethistoryfile()) -> None: | ||||
|         maxlength = self.saved_history_length | ||||
|  | @ -464,6 +466,19 @@ def write_history_file(self, filename: str = gethistoryfile()) -> None: | |||
|                 entry = entry.replace("\n", "\r\n")  # multiline history support | ||||
|                 f.write(entry + "\n") | ||||
| 
 | ||||
|     def append_history_file(self, filename: str = gethistoryfile()) -> None: | ||||
|         reader = self.get_reader() | ||||
|         saved_length = self.get_history_length() | ||||
|         length = self.get_current_history_length() - saved_length | ||||
|         history = reader.get_trimmed_history(length) | ||||
|         f = open(os.path.expanduser(filename), "a", | ||||
|                  encoding="utf-8", newline="\n") | ||||
|         with f: | ||||
|             for entry in history: | ||||
|                 entry = entry.replace("\n", "\r\n")  # multiline history support | ||||
|                 f.write(entry + "\n") | ||||
|         self.set_history_length(saved_length + length) | ||||
| 
 | ||||
|     def clear_history(self) -> None: | ||||
|         del self.get_reader().history[:] | ||||
| 
 | ||||
|  | @ -533,6 +548,7 @@ def insert_text(self, text: str) -> None: | |||
| get_current_history_length = _wrapper.get_current_history_length | ||||
| read_history_file = _wrapper.read_history_file | ||||
| write_history_file = _wrapper.write_history_file | ||||
| append_history_file = _wrapper.append_history_file | ||||
| clear_history = _wrapper.clear_history | ||||
| get_history_item = _wrapper.get_history_item | ||||
| remove_history_item = _wrapper.remove_history_item | ||||
|  |  | |||
|  | @ -30,8 +30,9 @@ | |||
| import os | ||||
| import sys | ||||
| import code | ||||
| import warnings | ||||
| 
 | ||||
| from .readline import _get_reader, multiline_input | ||||
| from .readline import _get_reader, multiline_input, append_history_file | ||||
| 
 | ||||
| 
 | ||||
| _error: tuple[type[Exception], ...] | type[Exception] | ||||
|  | @ -144,6 +145,10 @@ def maybe_run_command(statement: str) -> bool: | |||
|             input_name = f"<python-input-{input_n}>" | ||||
|             more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single")  # type: ignore[call-arg] | ||||
|             assert not more | ||||
|             try: | ||||
|                 append_history_file() | ||||
|             except (FileNotFoundError, PermissionError, OSError) as e: | ||||
|                 warnings.warn(f"failed to open the history file for writing: {e}") | ||||
|             input_n += 1 | ||||
|         except KeyboardInterrupt: | ||||
|             r = _get_reader() | ||||
|  |  | |||
|  | @ -112,6 +112,7 @@ def _run_repl( | |||
|         else: | ||||
|             os.close(master_fd) | ||||
|             process.kill() | ||||
|             process.wait(timeout=SHORT_TIMEOUT) | ||||
|             self.fail(f"Timeout while waiting for output, got: {''.join(output)}") | ||||
| 
 | ||||
|         os.close(master_fd) | ||||
|  | @ -1564,6 +1565,27 @@ def test_readline_history_file(self): | |||
|             self.assertEqual(exit_code, 0) | ||||
|             self.assertNotIn("\\040", pathlib.Path(hfile.name).read_text()) | ||||
| 
 | ||||
|     def test_history_survive_crash(self): | ||||
|         env = os.environ.copy() | ||||
|         commands = "1\nexit()\n" | ||||
|         output, exit_code = self.run_repl(commands, env=env) | ||||
|         if "can't use pyrepl" in output: | ||||
|             self.skipTest("pyrepl not available") | ||||
| 
 | ||||
|         with tempfile.NamedTemporaryFile() as hfile: | ||||
|             env["PYTHON_HISTORY"] = hfile.name | ||||
|             commands = "spam\nimport time\ntime.sleep(1000)\npreved\n" | ||||
|             try: | ||||
|                 self.run_repl(commands, env=env) | ||||
|             except AssertionError: | ||||
|                 pass | ||||
| 
 | ||||
|             history = pathlib.Path(hfile.name).read_text() | ||||
|             self.assertIn("spam", history) | ||||
|             self.assertIn("time", history) | ||||
|             self.assertNotIn("sleep", history) | ||||
|             self.assertNotIn("preved", history) | ||||
| 
 | ||||
|     def test_keyboard_interrupt_after_isearch(self): | ||||
|         output, exit_code = self.run_repl(["\x12", "\x03", "exit"]) | ||||
|         self.assertEqual(exit_code, 0) | ||||
|  |  | |||
|  | @ -0,0 +1,3 @@ | |||
| In PyREPL, append a new entry to the ``PYTHON_HISTORY`` file *after* every | ||||
| statement.  This should preserve command-line history after interpreter is | ||||
| terminated.  Patch by Sergey B Kirpichev. | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Sergey B Kirpichev
						Sergey B Kirpichev