Thursday, May 27, 2010

Python and COM

Py2Exe can create OLE COM servers. In particular the sample that ships with py2exe creates the Python interpreter. That means win32 programs can execute Python scripts without having to deploy the Python Win32 extensions. That makes distributing apps that use Python scripting easier.
I am going to do this on Windows and access the Python objects through COM from Visual Foxpro. Just because M$ decided not to continue with the Fox doesn't stop it from being a great environment to play around in.

Executing:
C:\Python25\Lib\site-packages\py2exe\samples\pywin32\com_server>python setup.py py2exe
Creates a dist directory
C:\Python25\Lib\site-packages\py2exe\samples\pywin32\com_server\dist
Within the dist directory
regsvr32 interp.dll

Now we need a program (People.py) that gives us access to Python objects without having to expose each Python class as a COM server. Unfortunately in the following spaces are collapsed, and when reading Python code that is not a good thing!

import win32com.client

class Person(object):
_public_methods_ = ['getName']
_public_attrs_ = ['firstName']
def __init__(self, name):
self.firstName = name

def getName(self):
return self.firstName

class Student(Person):
def __init__(self, name):
super(Student, self).__init__(name)
# append the public methods and properties,
# otherwise the inherited Person._public_...
# would not be available to the COM client
Person._public_methods_.append('setExamResults')
Person._public_attrs_.append('examResults')

def setExamResults(self, exam):
# Exam is passed in from a COM client
self.examResults = win32com.client.Dispatch(exam)

In Visual Foxpro "*" is a comment, and "?" means print. Everything else should be pretty obvious.
From VFP:
oP = CreateObject("Python.Interpreter")
oP.Exec("import sys")
oP.Exec("from win32com.server.util import wrap")
? "The COM object is being hosted in " + oP.Eval("sys.executable")
? "Path is:" + oP.Eval("str(sys.path)")
oP.Exec('sys.path.append("c:\\temp")')
oP.Exec( "import People")
oP.Exec( "student = People.Student('Peter')")
* Get the object from Python into VFP by wrapping it
student = oP.eval("wrap( student )")
* Now call a function on that object
? student.getName()
* Displays "Peter"

* Create a VFP object to pass into Python
oExam = CREATEOBJECT("empty")
ADDPROPERTY(oEmpty,"English",86)

* Pass it in
student.setExamResults(oEmpty)

* Test that it has attached.
? student.examResults.English

* Displays 86

So what was the benefit of all that? Hmmm, imagine if we could call SQLalchemy objects from anywhere!! Or what if you have a legacy (only because M$ decreed it that way) app and wanted to continue to expand its functionality in an open source language.

No comments: