Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotate axscript.client.error module and remove redundant code #2236

Merged
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
78 changes: 48 additions & 30 deletions com/win32comext/axscript/client/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand All @@ -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):
Expand All @@ -72,14 +79,27 @@ 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",
scode=winerror.DISP_E_EXCEPTION,
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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -206,15 +220,19 @@ 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
gives it to the host, along with out exception class. The host will
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)
Expand Down
39 changes: 22 additions & 17 deletions com/win32comext/axscript/client/framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import re
import sys
from typing import NoReturn

import pythoncom # Need simple connection point support
import win32api
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
)
Expand Down
Loading