fix(com): except pywintypes.com_error raised NameError without pywin32
Release / test (push) Canceled after 0s
Release / build (push) Canceled after 0s
Release / publish-pypi (push) Canceled after 0s
Release / github-release (push) Canceled after 0s

Python evaluates an `except` clause on the way out of every failed call,
not only the ones it is meant to catch. `pywintypes` is bound only when
`sys.platform == "win32"` and the import succeeded, so on any host
without pywin32 -- all of Linux, and Windows installed without the
`[com]` extra -- an ordinary exception leaving `ComBackend._run` hit that
clause and became `NameError: name 'pywintypes' is not defined`, burying
the real error.

Every Linux CI leg failed on exactly this, in
test_ordinary_errors_are_unchanged. The timeout tests survived only
because they matched an earlier `except`; nothing else in the suite sends
a non-timeout exception through `_run`.

Fixed with a module-level `_COM_ERROR` tuple: `(pywintypes.com_error,)`
where the import worked, `()` where it did not. An empty tuple catches
nothing, which is correct when no com_error type exists to be raised -- a
placeholder Exception subclass would be worse than the bug, silently
swallowing unrelated errors.

Reproduced on Windows before fixing by deleting the module attribute,
which resolves the name the same way the absent import does; that is now
a test, alongside one pinning `_COM_ERROR` as a tuple. Verified through
the locked environment CI uses: 1244 passed, 1 xfailed.

README test-count snapshot 1243 -> 1245.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Umut CAN
2026-08-06 10:22:49 +03:00
co-authored by Claude Opus 5
parent 4e0f8a90fd
commit e27071ab48
3 changed files with 51 additions and 2 deletions
+13 -1
View File
@@ -59,6 +59,18 @@ if _WIN32_AVAILABLE:
else:
_COM_IMPORTS_OK = False
#: What `_run` may catch as a COM failure. A *tuple*, because where pywin32 is
#: absent the right value is the empty one: `except ()` catches nothing, which
#: is exactly correct when no `com_error` type exists to be raised.
#:
#: `except pywintypes.com_error` used to be written directly, and Python
#: evaluates that name on the way out of every failed call — not only COM ones.
#: On any host without pywin32 (all of Linux, and Windows installed without the
#: `[com]` extra) it raised `NameError: name 'pywintypes' is not defined` and
#: buried the real exception. A placeholder Exception subclass would be worse
#: than the bug: it would silently swallow unrelated errors.
_COM_ERROR: tuple[type[BaseException], ...] = (pywintypes.com_error,) if _COM_IMPORTS_OK else ()
try:
from PIL import Image as PILImage
@@ -682,7 +694,7 @@ class ComBackend(AutoCADBackend):
"(press ESC in AutoCAD). Increase COM_CALL_TIMEOUT if a long "
"operation is expected."
) from e
except pywintypes.com_error as e:
except _COM_ERROR as e:
hr = e.args[0] if e.args else 0
if hr in (-2147221246, -2147221005, -2147417842):
_COM_STATE.pop("app", None)