diff --git a/CHANGES.txt b/CHANGES.txt index 4f85fb3e80..1855816774 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,8 @@ Coming in build 307, as yet unreleased -------------------------------------- ### pywin32 +* Marked `exc_type` and `exc_traceback` in `win32comext.axscript.client.error.AXScriptException.__init__` as deprecated. + They are now unused and all information is taken from the `exc_value` parameter. * Fixed non-overriden `pywin.scintilla.formatter.Formatter.ColorizeString` raising `TypeError` instead of `RuntimeError` due to too many parameters (#2216, @Avasam) * Fixed broken since Python 3 tokenization in `win32comext.axdebug.codecontainer.pySourceCodeContainer.GetSyntaxColorAttributes` (#2216, @Avasam) * Fixed a `TypeError` due to incorrect kwargs in `win32comext.axscript.client.pydumper.Register` (#2216, @Avasam) diff --git a/com/win32comext/axscript/client/error.py b/com/win32comext/axscript/client/error.py index 594ffd9396..4af2c452a8 100644 --- a/com/win32comext/axscript/client/error.py +++ b/com/win32comext/axscript/client/error.py @@ -4,29 +4,36 @@ as well as the IActiveScriptError interface code. """ +from __future__ import annotations + import re import traceback +import warnings +from types import TracebackType import pythoncom import win32com.server.util import winerror from win32com.axscript import axscript from win32com.server.exception import COMException +from win32comext.axscript.client.debug import DebugManager +from win32comext.axscript.client.framework import AXScriptCodeBlock, COMScript +from win32comext.axscript.server.axsite import AXSite debugging = 0 -def FormatForAX(text): +def FormatForAX(text: str): """Format a string suitable for an AX Host""" # Replace all " with ', so it works OK in HTML (ie, ASP) return ExpandTabs(AddCR(text)) -def ExpandTabs(text): +def ExpandTabs(text: str): return re.sub(r"\t", " ", text) -def AddCR(text): +def AddCR(text: str): return re.sub(r"\n", "\r\n", text) @@ -45,7 +52,7 @@ def _query_interface_(self, iid): print("IActiveScriptError QI - unknown IID", iid) return 0 - def _SetExceptionInfo(self, exc): + def _SetExceptionInfo(self, exc: AXScriptException): self.exception = exc def GetSourceLineText(self): @@ -72,7 +79,14 @@ class AXScriptException(COMException): object. """ - def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback): + def __init__( + self, + site: COMScript, + codeBlock: AXScriptCodeBlock | None, + exc_type: None = None, + exc_value: BaseException | None = None, + exc_traceback: None = None, + ): # set properties base class shares via base ctor... super().__init__( description="Unknown Exception", @@ -80,6 +94,12 @@ def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback): source="Python ActiveX Scripting Engine", ) + if exc_type is not None or exc_traceback is not None: + warnings.warn( + "`exc_type` and `exc_traceback` were redundant and are now unused.", + category=DeprecationWarning, + ) + # And my other values... if codeBlock is None: self.sourceContext = 0 @@ -89,18 +109,18 @@ def __init__(self, site, codeBlock, exc_type, exc_value, exc_traceback): self.startLineNo = codeBlock.startLineNumber self.linetext = "" - self.__BuildFromException(site, exc_type, exc_value, exc_traceback) + self.__BuildFromException(site, exc_value) - def __BuildFromException(self, site, type, value, tb): + def __BuildFromException(self, site: COMScript, value: BaseException | None): if debugging: import linecache linecache.clearcache() try: - if issubclass(type, SyntaxError): + if isinstance(value, SyntaxError): self._BuildFromSyntaxError(value) else: - self._BuildFromOther(site, type, value, tb) + self._BuildFromOther(site, value) except: # Error extracting traceback info!!! traceback.print_exc() # re-raise. @@ -111,13 +131,16 @@ def _BuildFromSyntaxError(self, exc: SyntaxError): msg = exc.msg or "Unknown Error" offset = exc.offset or 0 line = exc.text or "" + lineno = exc.lineno or 0 self.description = FormatForAX(msg) - self.lineno = exc.lineno + self.lineno = lineno self.colno = offset - 1 self.linetext = ExpandTabs(line.rstrip()) - def _BuildFromOther(self, site, exc_type, value, tb): + def _BuildFromOther(self, site: COMScript, value: BaseException | None): + tb = value.__traceback__ if value else None + exc_type = type(value) if value else None self.colno = -1 self.lineno = 0 if debugging: # Full traceback if debugging. @@ -132,7 +155,6 @@ def _BuildFromOther(self, site, exc_type, value, tb): "r_reload", "r_open", ] # hide from these functions down in the traceback. - depth = None tb_top = tb while tb_top: filename, lineno, name, line = self.ExtractTracebackInfo(tb_top, site) @@ -141,8 +163,7 @@ def _BuildFromOther(self, site, exc_type, value, tb): tb_top = tb_top.tb_next format_items = [] if tb_top: # found one. - depth = 0 - tb_look = tb_top + tb_look: TracebackType | None = tb_top # Look down for our bottom while tb_look: filename, lineno, name, line = self.ExtractTracebackInfo(tb_look, site) @@ -155,15 +176,14 @@ def _BuildFromOther(self, site, exc_type, value, tb): self.lineno = lineno self.linetext = line format_items.append((filename, lineno, name, line)) - depth = depth + 1 tb_look = tb_look.tb_next else: - depth = None tb_top = tb bits = ["Traceback (most recent call last):\n"] - bits.extend(traceback.format_list(format_items)) - if exc_type == pythoncom.com_error: + # Fixed in https://github.com/python/typeshed/pull/11675 , to be included in next mypy release + bits.extend(traceback.format_list(format_items)) # type: ignore[arg-type] + if isinstance(value, pythoncom.com_error): desc = f"{value.strerror} (0x{value.hresult:x})" if ( value.hresult == winerror.DISP_E_EXCEPTION @@ -176,23 +196,17 @@ def _BuildFromOther(self, site, exc_type, value, tb): bits.extend(traceback.format_exception_only(exc_type, value)) self.description = ExpandTabs("".join(bits)) - # Clear tracebacks etc. - tb = tb_top = tb_look = None - def ExtractTracebackInfo(self, tb, site): + def ExtractTracebackInfo(self, tb: TracebackType, site: COMScript): import linecache - f = tb.tb_frame lineno = tb.tb_lineno - co = f.f_code + co = tb.tb_frame.f_code filename = co.co_filename name = co.co_name - line = linecache.getline(filename, lineno) + line: str | None = linecache.getline(filename, lineno) if not line: - try: - codeBlock = site.scriptCodeBlocks[filename] - except KeyError: - codeBlock = None + codeBlock = site.scriptCodeBlocks.get(filename) if codeBlock: # Note: 'line' will now be unicode. line = codeBlock.GetLineNo(lineno) @@ -206,7 +220,11 @@ def __repr__(self): return "AXScriptException Object with description:" + self.description -def ProcessAXScriptException(scriptingSite, debugManager, exceptionInstance): +def ProcessAXScriptException( + scriptingSite: AXSite, + debugManager: DebugManager, + exceptionInstance: AXScriptException, +): """General function to handle any exception in AX code This function creates an instance of our IActiveScriptError interface, and @@ -214,7 +232,7 @@ def ProcessAXScriptException(scriptingSite, debugManager, exceptionInstance): likely call back on the IActiveScriptError interface to get the source text and other information not normally in COM exceptions. """ - # traceback.print_exc() + # traceback.print_exc() instance = IActiveScriptError() instance._SetExceptionInfo(exceptionInstance) gateway = win32com.server.util.wrap(instance, axscript.IID_IActiveScriptError) diff --git a/com/win32comext/axscript/client/framework.py b/com/win32comext/axscript/client/framework.py index 44ef43ac9a..8c5152c3de 100644 --- a/com/win32comext/axscript/client/framework.py +++ b/com/win32comext/axscript/client/framework.py @@ -11,6 +11,7 @@ import re import sys +from typing import NoReturn import pythoncom # Need simple connection point support import win32api @@ -112,7 +113,7 @@ def trace(*args): print() -def RaiseAssert(scode, desc): +def RaiseAssert(scode, desc) -> NoReturn: """A debugging function that raises an exception considered an "Assertion".""" print("**************** ASSERTION FAILED *******************") print(desc) @@ -122,7 +123,14 @@ def RaiseAssert(scode, desc): class AXScriptCodeBlock: """An object which represents a chunk of code in an AX Script""" - def __init__(self, name, codeText, sourceContextCookie, startLineNumber, flags): + def __init__( + self, + name: str, + codeText: str, + sourceContextCookie: int, + startLineNumber: int, + flags, + ): self.name = name self.codeText = codeText self.codeObject = None @@ -139,7 +147,7 @@ def GetFileName(self): def GetDisplayName(self): return self.name - def GetLineNo(self, no): + def GetLineNo(self, no: int): pos = -1 for i in range(no - 1): pos = self.codeText.find("\n", pos + 1) @@ -628,7 +636,7 @@ def __init__(self): self.safetyOptions = 0 self.lcid = 0 self.subItems = {} - self.scriptCodeBlocks = {} + self.scriptCodeBlocks: dict[str, AXScriptCodeBlock] = {} def _query_interface_(self, iid): if self.debugManager: @@ -1086,7 +1094,7 @@ def _ApplyInScriptedSection(self, fn, args): else: return fn(*args) - def ApplyInScriptedSection(self, codeBlock, fn, args): + def ApplyInScriptedSection(self, codeBlock: AXScriptCodeBlock | None, fn, args): self.BeginScriptedSection() try: try: @@ -1105,7 +1113,9 @@ def _CompileInScriptedSection(self, code, name, type): self.debugManager.OnEnterScript() return compile(code, name, type) - def CompileInScriptedSection(self, codeBlock, type, realCode=None): + def CompileInScriptedSection( + self, codeBlock: AXScriptCodeBlock, type, realCode=None + ): if codeBlock.codeObject is not None: # already compiled return 1 if realCode is None: @@ -1137,7 +1147,7 @@ def _ExecInScriptedSection(self, codeObject, globals, locals=None): else: exec(codeObject, globals, locals) - def ExecInScriptedSection(self, codeBlock, globals, locals=None): + def ExecInScriptedSection(self, codeBlock: AXScriptCodeBlock, globals, locals=None): if locals is None: locals = globals assert ( @@ -1185,31 +1195,26 @@ def EvalInScriptedSection(self, codeBlock, globals, locals=None): except: self.HandleException(codeBlock) - def HandleException(self, codeBlock): - # NOTE - Never returns - raises a ComException - exc_type, exc_value, exc_traceback = sys.exc_info() + def HandleException(self, codeBlock: AXScriptCodeBlock | None) -> NoReturn: + """Never returns - raises a ComException""" + exc_type, exc_value, *_ = sys.exc_info() # If a SERVER exception, re-raise it. If a client side COM error, it is # likely to have originated from the script code itself, and therefore # needs to be reported like any other exception. if IsCOMServerException(exc_type): # Ensure the traceback doesnt cause a cycle. - exc_traceback = None raise # It could be an error by another script. if ( - issubclass(pythoncom.com_error, exc_type) + isinstance(exc_value, pythoncom.com_error) and exc_value.hresult == axscript.SCRIPT_E_REPORTED ): # Ensure the traceback doesnt cause a cycle. - exc_traceback = None raise COMException(scode=exc_value.hresult) - exception = error.AXScriptException( - self, codeBlock, exc_type, exc_value, exc_traceback - ) + exception = error.AXScriptException(self, codeBlock, exc_value=exc_value) # Ensure the traceback doesnt cause a cycle. - exc_traceback = None result_exception = error.ProcessAXScriptException( self.scriptSite, self.debugManager, exception )