Repository: nvaccess/nvda

Issue:
Python 3.3 and later: code.InteractiveConsole.showtraceback/showsyntaxerror will check for sys.excepthook, causing error messages to not be printed to Python Console
Hi,

Affects debugging #7105 and related Python 3 issues.

### Background:
Python 2.7's InteractiveConsole class (found in code.py) does not check sys.excepthook, but in Python 3.3, this value will be checked when printing tracebacks and syntax errors. This means that, in Python 2 version of NVDA, if Python Console is active, errors will be printed to the console, but in Python 3, it won't - instead, it is redirected to the log.

Relevant Python issue: https://bugs.python.org/issue12643

### Steps to reproduce:
Create a custom interactive console and file log handler. Then run them in Python 2 and 3, asking the interpreter to log anything you want to a file. Also, set up sys.excepthook so it'll point to a function that actualy logs things to files.

### Actual behavior:
In Python 2, any error messages are printed to the custom console, but in Python 3, it does not.

### Expected behavior:
Error messages are printed to the custom console in both Python 2 and 3.

### System configuration
#### NVDA installed/portable/running from source:
Source

#### NVDA version:
2019.1dev.alpha.16446, both original (Python 2) and Python 3 version

#### Windows version:
Windows 10 Version 1809

#### Name and version of other software in use when reproducing the issue:
Python 2.7.15, 3.7.1

#### Other information about your system:
None

### Other questions
#### Does the issue still occur after restarting your PC?
Yes

#### Have you tried any other versions of NVDA?
Yes - 2018.3.2, 2018.4 RC

### Possible solution:
One interesting thing to note is that when Python Console is active, custom snapshot variables are kept in the console object. Therefore in our sys.excepthook (which is pointed to logHandler._excepthook), check for them (if not None) and call the original sys.__excepthook__ function so error output to the console will work if the console is active. This will work in both Python 2 and 3, and in both cases, no need to edit Python Console class.

Thanks.


Discussion:
I've also looked into this. While I understand @josephsl's idea to check an active console from logHandler._excepthook, I think it will also add a line to the traceback about the error being raised from code.py. We also may need to do our own formatting on the traceback.

Resolving patch:
diff --git a/source/pythonConsole.py b/source/pythonConsole.py
--- a/source/pythonConsole.py
+++ b/source/pythonConsole.py
@@ -2,7 +2,7 @@
 #A part of NonVisual Desktop Access (NVDA)
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
-#Copyright (C) 2008-2017 NV Access Limited
+#Copyright (C) 2008-2019 NV Access Limited, Leonard de Ruijter
 
 import watchdog
 
@@ -99,8 +99,7 @@ def __init__(self, outputFunc, setPromptFunc, exitFunc, echoFunc=None, **kwargs)
 		#: @type: dict
 		self._namespaceSnapshotVars = None
 
-		# Can't use super here because stupid code.InteractiveConsole doesn't sub-class object. Grrr!
-		code.InteractiveConsole.__init__(self, locals=self.namespace, **kwargs)
+		super().__init__(locals=self.namespace, **kwargs)
 		self.prompt = ">>>"
 
 	def _set_prompt(self, prompt):
@@ -121,12 +120,24 @@ def push(self, line):
 		sys.stdout = sys.stderr = self
 		# Prevent this from messing with the gettext "_" builtin.
 		saved_ = builtins._
-		more = code.InteractiveConsole.push(self, line)
+		more = super().push(line)
 		sys.stdout, sys.stderr = stdout, stderr
 		builtins._ = saved_
 		self.prompt = "..." if more else ">>>"
 		return more
 
+	def showsyntaxerror(self, filename=None):
+		excepthook = sys.excepthook
+		sys.excepthook = sys.__excepthook__
+		super().showsyntaxerror(filename=filename)
+		sys.excepthook = excepthook
+
+	def showtraceback(self):
+		excepthook = sys.excepthook
+		sys.excepthook = sys.__excepthook__
+		super().showtraceback()
+		sys.excepthook = excepthook
+
 	def updateNamespaceSnapshotVars(self):
 		"""Update the console namespace with a snapshot of NVDA's current state.
 		This creates/updates variables for the current focus, navigator object, etc.


Test patch: