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

Ensure disp_class cannot be None in DispatchWithEvents and WithEvents #2322

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion com/win32com/client/CLSIDToClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
Access.
"""

mapCLSIDToClass = {}
from __future__ import annotations

mapCLSIDToClass: dict[str, type] = {}
Comment on lines +20 to +22
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary for this change. It just helped be make sense of, and validate the type narrowing.



def RegisterCLSID(clsid, pythonClass):
Expand Down
105 changes: 47 additions & 58 deletions com/win32com/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Note that if the unknown dispatch object then returns a known
# dispatch object, the known class will be used. This contrasts
# with dynamic.Dispatch behaviour, where dynamic objects are always used.
from __future__ import annotations

import sys
from itertools import chain
Expand Down Expand Up @@ -263,7 +264,39 @@ def __setattr__(self, attr, val):
setattr(self._obj_, attr, val)


def DispatchWithEvents(clsid, user_event_class):
def __get_disp_and_event_classes(dispatch):
# Create/Get the object.
disp = Dispatch(dispatch)

if disp.__class__.__dict__.get("CLSID"):
return disp.__class__

# Eeek - no makepy support - try and build it.
error_msg = "This COM object can not automate the makepy process - please run makepy manually for this object"
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
gencache.EnsureModule(tla[0], tla[1], tla[3], tla[4], bValidateFile=0)
# Get the class from the module.
disp_class = gencache.GetClassForProgID(str(disp_clsid))
except pythoncom.com_error as error:
raise TypeError(error_msg) from error

if disp_class is None:
raise TypeError(error_msg)
# Get the clsid
clsid = disp_class.CLSID
# Create a new class that derives from 2 classes:
# the event sink class and the user class.
events_class = getevents(clsid)
if events_class is None:
raise ValueError("This COM object does not support events.")
return disp, disp_class, events_class


def DispatchWithEvents(clsid, user_event_class) -> EventsProxy:
"""Create a COM object that can fire events to a user defined class.
clsid -- The ProgID or CLSID of the object to create.
user_event_class -- A Python class object that responds to the events.
Expand Down Expand Up @@ -302,39 +335,14 @@ class object that derives from three classes:
Visible changed: 1
>>>
"""
# Create/Get the object.
disp = Dispatch(clsid)
if not disp.__class__.__dict__.get(
"CLSID"
): # Eeek - no makepy support - try and build it.
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
gencache.EnsureModule(tla[0], tla[1], tla[3], tla[4], bValidateFile=0)
# Get the class from the module.
disp_class = gencache.GetClassForProgID(str(disp_clsid))
except pythoncom.com_error:
raise TypeError(
"This COM object can not automate the makepy process - please run makepy manually for this object"
)
else:
disp_class = disp.__class__
# If the clsid was an object, get the clsid
clsid = disp_class.CLSID
# Create a new class that derives from 3 classes - the dispatch class, the event sink class and the user class.
events_class = getevents(clsid)
if events_class is None:
raise ValueError("This COM object does not support events.")
disp, disp_class, events_class = __get_disp_and_event_classes(clsid)
result_class = type(
"COMEventClass",
(disp_class, events_class, user_event_class),
{"__setattr__": _event_setattr_},
)
instance = result_class(
disp._oleobj_
) # This only calls the first base class __init__.
# This only calls the first base class __init__.
instance = result_class(disp._oleobj_)
events_class.__init__(instance, instance)
if hasattr(user_event_class, "__init__"):
user_event_class.__init__(instance)
Expand Down Expand Up @@ -365,33 +373,14 @@ def WithEvents(disp, user_event_class):
This is mainly useful where using DispatchWithEvents causes
circular reference problems that the simple proxy doesn't deal with
"""
disp = Dispatch(disp)
if not disp.__class__.__dict__.get(
"CLSID"
): # Eeek - no makepy support - try and build it.
try:
ti = disp._oleobj_.GetTypeInfo()
disp_clsid = ti.GetTypeAttr()[0]
tlb, index = ti.GetContainingTypeLib()
tla = tlb.GetLibAttr()
gencache.EnsureModule(tla[0], tla[1], tla[3], tla[4], bValidateFile=0)
# Get the class from the module.
disp_class = gencache.GetClassForProgID(str(disp_clsid))
except pythoncom.com_error:
raise TypeError(
"This COM object can not automate the makepy process - please run makepy manually for this object"
)
else:
disp_class = disp.__class__
# Get the clsid
clsid = disp_class.CLSID
# Create a new class that derives from 2 classes - the event sink
# class and the user class.
events_class = getevents(clsid)
if events_class is None:
raise ValueError("This COM object does not support events.")
result_class = type("COMEventClass", (events_class, user_event_class), {})
instance = result_class(disp) # This only calls the first base class __init__.
disp, disp_class, events_class = __get_disp_and_event_classes(disp)
result_class = type(
"COMEventClass",
(events_class, user_event_class),
{},
)
# This only calls the first base class __init__.
instance = result_class(disp)
if hasattr(user_event_class, "__init__"):
user_event_class.__init__(instance)
return instance
Expand Down Expand Up @@ -556,7 +545,7 @@ def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *ar
def __getattr__(self, attr):
args = self._prop_map_get_.get(attr)
if args is None:
raise AttributeError(f"'{repr(self)}' object has no attribute '{attr}'")
raise AttributeError(f"'{self!r}' object has no attribute '{attr}'")
return self._ApplyTypes_(*args)

def __setattr__(self, attr, value):
Expand All @@ -566,7 +555,7 @@ def __setattr__(self, attr, value):
try:
args, defArgs = self._prop_map_put_[attr]
except KeyError:
raise AttributeError(f"'{repr(self)}' object has no attribute '{attr}'")
raise AttributeError(f"'{self!r}' object has no attribute '{attr}'")
self._oleobj_.Invoke(*(args + (value,) + defArgs))

def _get_good_single_object_(self, obj, obUserName=None, resultCLSID=None):
Expand Down
Loading