28.2 Interfacing with COM: Cheap Public Relations

At this point, we have a program that is run whenever users fill in the feedback form and that writes out instances of the feedback data to files on the server. We'll use this data to do two things. First, a program that's run periodically (say, at 2 a.m., every night)[2] will look through the saved data, find out which saved pickled files correspond to complaints, and print out a customized letter to the complainer. The second use we'll make of that data is a GUI browser to look through the stored feedback entries. All this sounds sophisticated, but you'll be surprised at how simple it is using the right tools. Joe's web site is on a Windows machine, but other platforms work in similar ways.

[2] Setting up this kind of automatic regularly scheduled program is easily done on most platforms, using, for example, cron on Unix or the AT scheduler in Windows NT/2000/XP.

Before we talk about how to write this program, a word about the technology it uses, namely Microsoft's Component Object Model (COM). COM is, among other things, a standard for interaction between programs, which allows COM-compliant programs to talk to, access the data in, and execute commands in other COM-compliant programs. Roughly speaking, the program doing the calling is called a COM client, and the program doing the executing is called a COM server. All major Microsoft products are COM-aware and can act as servers. Microsoft Word is one of these, and the one we'll use here, since Microsoft Word is just fine for writing letters, which is what we're doing. Luckily for us, Python can be made COM-aware as well, on Windows. Mark Hammond and Greg Stein have made available a set of extensions to Python for Windows called win32com, which allow Python programs to do almost everything you can do with COM from any other language. You can write COM clients, servers, ActiveX scripting hosts, debuggers, and more, all in Python. We only need to do the first of these tasks, which is also the simplest. The basic tasks that our form letter generator program needs to do are:

  1. Open all of the pickled files in the appropriate directory and unpickle them to turn them back into Python objects.

  2. For each unpickled instance, test if the feedback is a complaint. If it is, find out the name and address of the person who filled out the form and go on to Step 3. If not, skip it.

  3. Open a Word document containing a template of the letter we want to send, and fill in the appropriate pieces with the customized information.

  4. Print the document and close it.

This task is almost as simple to express in Python with win32com. Here's a program called formletter.py:

from win32com.client import gencache, constants WORD = 'Word.Application' False, True = 0, -1 class Word:     def __init__(self):         self.app = gencache.EnsureDispatch(WORD)     def open(self, doc):         self.app.Documents.Open(FileName=doc)     def replace(self, source, target):         self.app.Selection.HomeKey(Unit=constants.wdLine)         find = self.app.Selection.Find         find.Text = "%"+source+"%"         find.Execute(  )         self.app.Selection.TypeText(Text=target)     def printdoc(self):         self.app.Application.PrintOut(  )     def close(self):         self.app.ActiveDocument.Close(SaveChanges=False) def print_formletter(data):     word.open(r"h:\David\Book\tofutemplate.doc")     word.replace("name", data.name)     word.replace("address", data.address)     word.replace("firstname", data.name.split(  )[0])     word.printdoc(  )     word.close(  ) if __name__ == '__main__':     import os, pickle     from feedback import DIRECTORY, FormData, FeedbackData     word = Word(  )     for filename in os.listdir(DIRECTORY):         data = pickle.load(open(os.path.join(DIRECTORY, filename)))         if data.type == 'complaint':             print "Printing letter for %(name)s." % vars(data)             print_formletter(data)         else:             print "Got comment from %(name)s, skipping printing." % vars(data)

The first few lines of the main program show the power of a well-designed framework. The first line is a standard import statement, except that it's worth noting that win32com is a package, not a module. It is, in fact, a collection of subpackages, modules, and functions. We need two things from the win32com package: the EnsureDispatch function in the gencache module, a function that allows us to dispatch functions to other objects (COM servers), and the constants submodule of the same module, which holds the constants defined by the COM objects we want to talk to.

The second line simply defines a variable that contains the name of the COM server we're interested in. It's called Word.Application, as you can find out from using a COM browser or reading Word's API (see the sidebar "Finding Out About COM Interfaces"). Using gencache.EnsureDispatch ensures that late binding is used for the Word library, and also ensures that all Word related constants are loaded.

Let's focus now on the if __name__ == '__main__' block, which is the next statement after the class and function definitions.

The first task is to read the data. We import the os and pickle modules because we're going to need functions they define, and then three references from the feedback module we just wrote: the DIRECTORY where the data is stored (this way if we change it in feedback.py, this module reflects the change the next time it's run), and the FormData and FeedbackData classes. The next line creates an instance of the Word class; this opens a connection with the Word COM server, starting the server if necessary.

The for loop is a simple iteration over the files in the directory with all the saved files. It's important that this directory contain only the pickled instances, since we're not doing any error checking. (We should make the code more robust, but we've ignored stability for simplicity.)

The first line in the for loop does the unpickling. It uses the load function from the pickle module, which takes a single argument, the file which is being unpickled. It returns as many references as were stored in the file in our case, just one. The data that was stored was just the instance of the FeedbackData class. The definition of the class itself isn't stored in the pickled file, just the instance values and a reference to the class. This design reduces the total size of pickled objects, and more importantly, it allows you to unpickle instances of previous versions of a class and automatically upgrade them to the newer class definitions.

The if statement inside the loop is straightforward.

The print_formletter function simply calls the various methods of the word instance of the Word class with the data extracted from the data instance. Note that we use the split method to extract the first name of the user, just to make the letter more friendly, but this risks strange behavior for nonstandard names.

In the Word class, the __init__ method appears simple yet hides a lot of work. It creates a connection with the COM server and stores a reference to that COM server in an instance variable app. Now, there are two ways in which the subsequent code might use this server: dynamic dispatch and nondynamic dispatch. In dynamic dispatch, Python doesn't know at the time the program is running what the interface to the COM server (in this case Microsoft Word) is. That's not a problem because COM allows Python to query the server and determine the number and kinds of arguments each function expects.

To explain the Word class methods, let's start with a possible template document, shown in Figure 28-3. so that we can see what needs to be done to it to customize it.

Figure 28-3. Joe's template letter to complainers
figs/lpy2_2803.gif

As you can see, it's a pretty average document, with the exception of some text in between % signs. We've used this notation just to make it easy for a program to find the parts that need customization, but any other technique could work as well. To use this template, we need to open the document, customize it, print it, and close it. Opening it is done by the open method of the Word class. The printing and closing are done similarly. To customize, we replace the %name%, %firstname%, and %address% text with the appropriate strings. That's what the replace method of the Word class does (we won't cover how we figured out what the exact sequence of calls should be; see the sidebar Finding Out About COM Interfaces for details).

Finding Out About COM Interfaces

How can you find out what the various methods and attributes of COM objects are? In general, COM objects are just like any other program; they should come with documentation. In the case of COM objects, however, it's quite possible to have the software without the documentation, simply because, as in the case of Word, it's possible to use Word without needing to program it. There are three strategies available to you if you want to explore a COM interface:

  • Find or buy the documentation; some COM programs have their documentation available on the Web, or available in printed form.

  • Use a COM browser to explore the objects. Pythonwin (part of the win32all extensions to Python on Windows), for example, comes with a COM browser tool that lets you explore the complex hierarchy of COM objects. It's not much more than a listing of available objects and functions, but sometimes that's all you need. Development tools such as Microsoft's Visual Studio also come with COM browsers.

  • Use another tool to find what's available. For the example, we simply used Microsoft Word's macro recorder facility to produce a VBA (Visual Basic for Applications) script, which is fairly straightforward to translate to Python. Macros tend to be fairly low-intelligence programs, meaning that the macro-recording facility can't pick up on the fact that you might want to do something 10 times, and so just records the same action multiple times. But they work fine for finding out that the equivalent of selecting the Print item of the File menu is to "say" ActiveDocument.PrintOut( ).


Putting all of this at work, the program, when run, outputs text like:

C:\Programs> python formletter.py Printing letter for John Doe. Got comment from Your Mom, skipping printing. Printing letter for Susan B. Anthony.

and prints two customized letters, ready to be sent in the mail. Note that the Word program doesn't show up on the desktop; by default, COM servers are invisible, so Word just acts behind the scenes. If Word is currently active on the desktop, each step is visible to the user.



Learning Python
Learning Python: Powerful Object-Oriented Programming
ISBN: 0596158068
EAN: 2147483647
Year: 2003
Pages: 253
Authors: Mark Lutz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net