views:

238

answers:

1

I have a program that uses the win32com library to control iTunes, but have been having some issues getting it to compile into an executable. The problem seems to revolve around using DispatchWithEvents instead of Dispatch. I've created a very simple program to illustrate my problem:

import win32com.client
win32com.client.gencache.is_readonly = False #From py2exe wiki

class ITunesEvents(object):
    def __init__(self): self.comEnabled = True
    def OnCOMCallsDisabledEvent(self, reason): self.comEnabled = False
    def OnCOMCallsEnabledEvent(self): self.comEnabled = True

# The first line works in the exe, the second doesn't.
itunes = win32com.client.Dispatch("iTunes.Application")
#itunes = win32com.client.DispatchWithEvents("iTunes.Application", ITunesEvents)

lib = getattr(itunes, "LibraryPlaylist")
src = getattr(lib, "Source")
playlists = getattr(src, "Playlists")

print "Found %i playlists." % getattr(playlists, "Count")

Using Dispatch, the program compiles and runs correctly. Using DispatchWithEvents, the program runs fine when called from the command line, but produces the following error when running the exe:

Traceback (most recent call last):
File "sandbox.py", line 16, in <module>
  itunes = win32com.client.DispatchWithEvents("iTunes.Application", ITunesEvents)
File "win32com\client\__init__.pyc", line 252, in DispatchWithEvents
File "win32com\client\gencache.pyc", line 520, in EnsureModule
File "win32com\client\gencache.pyc", line 287, in MakeModuleForTypelib
File "win32com\client\makepy.pyc", line 259, in GenerateFromTypeLibSpec
File "win32com\client\gencache.pyc", line 141, in GetGeneratePath
IOError: [Errno 2] No such file or directory: '[distDir]\\library.zip\\win32com\\gen_py\\__init__.py'

I've also tried using PyInstaller, which gives a similar error:

File "<string>", line 16, in <module>
File "[outDir]/win32com.client", line 252, in DispatchWithEvents
File "[outDir]/win32com.client.gencache", line 520, in EnsureModule
File "[outDir]/win32com.client.gencache", line 287, in MakeModuleForTypelib
File "[outDir]/win32com.client.makepy", line 286, in GenerateFromTypeLibSpec
File "[outDir]/win32com.client.gencache", line 550, in AddModuleToCache
File "[outDir]/win32com.client.gencache", line 629, in _GetModule
File "[pyinstallerDir]\iu.py", line 455, in importHook
    raise ImportError, "No module named %s" % fqname
ImportError: No module named win32com.gen_py.9E93C96F-CF0D-43F6-8BA8-B807A3370712x0x1x13

I know I can manually add the typelib in my setup.py file, but I'd like to run the code on computers with different versions of iTunes without recompiling so I'd prefer to dynamically create it. If there's no way to do this with the setup/spec, maybe there is another way to load the events? Thanks.


Addition:

Thanks to Ryan, I found I could take the generated py file and after a little digging, was able to come up with the following.

Take the generated py file (from makepy.py) and rename it somewhere like cominterface.py. Then you'll need to do the following to actually create the COM object with event handler.

import cominterface
from types import ClassType
from win32com.client import EventsProxy, _event_setattr_

class ItunesEvents:
    '''iTunes events class. See cominterface for details.'''
    def OnPlayerPlayEvent(self, t):print "Playing..."
    def OnPlayerStopEvent(self, t): print "Stopping..."

itunes = cominterface.iTunesApp()
rClass = ClassType("COMEventClass", (itunes.__class__, itunes.default_source, ItunesEvents), {'__setattr__': _event_setattr_})
instance = rClass(itunes._oleobj_)
itunes.default_source.__init__(instance, instance)
#ItunesEvents.__init__(instance) #Uncomment this line if your events class has __init__.
itunes = EventsProxy(instance)

Then you can go about your business.

A: 

Instead of depending on the cache, I'd recommend going into the local cache directory, copying the generated file into your local project file, and naming it something like ITunesInterface.py, and calling to that explicitly. This will make py2exe pull it into your compiled app.

Ryan Ginstrom
I'm thinking this could work, but I can't seem to figure out how to call the interface explicitly, especially with Events. I had ``Dispatch`` working, but then realized that was just calling win32com as normal. Is there information somewhere on how to do this?
jeffaudio