Using Open Scripting Extension from Python


OSA support in Python is still far from complete, and what support there is is likely to change in the forseeable future. Still, there is already enough in place to allow you to do some nifty things to other programs from your python program.

Actually, when we say "AppleScript" in this document we actually mean "the Open Scripting Architecture", there is nothing AppleScript-specific in the Python implementation.

In this example, we will look at a scriptable application, extract its "AppleScript Dictionary" and generate a Python interface module from that and use that module to control the application. Because we want to concentrate on the OSA details we don't bother with a real user-interface for our application.

The application we are going to script is Eudora Light, a free mail program from QualComm. This is a very versatile mail-reader, and QualComm has an accompanying commercial version once your needs outgrow Eudora Light. Our program will tell Eudora to send queued mail, retrieve mail or quit.

Creating the Python interface module

There is a tool in the standard distribution that looks through a file for an 'AETE' or 'AEUT' resource, the internal representation of the AppleScript dictionary. This tool is called gensuitemodule.py, and lives in Tools:bgen:ae. When we start it, it asks us for an input file and we point it to the Eudora Light executable. It starts parsing the AETE resource, and for each AppleEvent suite it finds it prompts us for the filename of the resulting python module. Remember to change folders for the first module, you don't want to clutter up the Eudora folder with your python interfaces. If you want to skip a suite you press cancel and the process continues with the next suite. In the case of Eudora, you do not want to generate the Required suite, because it will be empty. AppleScript understands that an empty suite means "incorporate the whole standard suite by this name", gensuitemodule does not currently understand this. Creating the empty Required_Suite.py would hide the correct module of that name from our application.

Time for a sidebar. If you want to re-create Required_Suite.py or one of the other standard modules you should look in System Folder:Extensions:Scripting Additions:Dialects:English Dialect, that is where the core AppleEvent dictionaries live. Also, if you are looking for the Finder_Suite interface: don't look in the finder (it has an old System 7.0 scripting suite), look at the extension Finder Scripting Extension.

Let's glance at the Eudora_Suite.py just created. You may want to open Script Editor alongside, and have a look at how it interprets the dictionary. EudoraSuite.py starts with some boilerplate, then come some dictionaries implementing the OSA Enumerations, then a big class definition with methods for each AppleScript Verb and finally some comments. The Enumerations we will skip, it suffices to know that whenever you have to pass an enumerator to a method you can pass the english name and don't have to bother with the 4-letter type code. So, you can say

	eudora.notice(occurrence="mail_arrives")
instead of the rather more cryptic
	eudora.notice(occurrence="wArv")
The Eudora_Suite class is the bulk of the code generated. For each verb it contains a method. Each method knows what arguments the verb expects, and it makes handy use of the keyword argument scheme introduced in Python 1.3 to present a palatable interface to the python programmer. You will see that each method calls some routines from aetools, an auxiliary module living in Tools:bgen:ae which contains some other nifty AppleEvent tools as well. Have a look at it sometime, there is (of course) no documentation yet.

The other thing you notice is that each method calls self.send, but no such method is defined. You will have to provide it by subclassing or multiple inheritance, as we shall see later.

The module ends with some comments. Sadly, gensuitemodule is not yet able to turn the Object Specifiers into reasonable Python code. For now, if you need object specifiers, you will have to use the routines defined in aetools.py (and aetypes.py, which it incorporates). You use these in the form aetools.Word(10, aetools.Document(1)) where the corresponding AppleScript terminology would be word 10 of the first document. Examine the two modules mentioned above along with the comments at the end of your suite module if you need to create more than the standard object specifiers.

Using a Python suite module

Now that we have created the suite module we can use it in an application. We do this by creating a class that inherits Eudora_Suite and the TalkTo class from aetools. The TalkTo class is basically a container for the send method used by the methods from the suite classes.

Actually, our class will also inherit Required_Suite, because we also need functionality from that suite: the quit command. Gensuitemodule could have created this completely derived class for us, since it has access to all information needed to build the class but unfortunately it does not do so at the moment. All in all, the heart of our program looks like this:

	import Eudora_Suite, Required_Suite, aetools
	
	class Eudora(aetools.TalkTo, Required_Suite.Required_Suite, \
				Eudora_Suite.Eudora_Suite):
		pass
Yes, our class body is pass, all functionality is already provided by the base classes, the only thing we have to do is glue it together in the right way.

Looking at the sourcefile testeudora.py we see that it starts with some imports (and some addpack calls to extend sys.path to include Tools:bgen:ae, use of ni should be preferred over addpack but I have not managed to master it yet). Then we get the class definition for our main object and a constant giving the signature of Eudora.

This, again, needs a little explanation. There are various ways to describe to AppleScript which program we want to talk to, but the easiest one to use (from Python, at least) is creator signature. Application name would be much nicer, but Python currently does not have a module that interfaces to the Finder database (which would allow us to map names to signatures). The other alternative, ChooseApplication from the program-to-program toolbox, is also not available from Python at the moment.

The main program itself is a wonder of simplicity. We create the object that talks to Eudora (passing the signature as argument), ask the user what she wants and call the appropriate method of the talker object. The use of keyword arguments with the same names as used by AppleScript make passing the parameters a breeze.

The exception handling does need a few comments, though. Since AppleScript is basically a connectionless RPC protocol nothing happens when we create to talker object. Hence, if the destination application is not running we will not notice until we send our first command. There is another thing to note about errors returned by AppleScript calls: even though MacOS.Error is raised not all of the errors are actually OSErr-type errors, some are error codes returned by the server application. In that case, the error message will be incorrect.

That concludes our simple example. Again, let me emphasize that scripting support in Python is not very complete at the moment, and the details of how to use AppleEvents will definitely change in the near future. This will not only fix all the ideosyncracies noted in this document but also break existing programs, since the current suite organization will have to change to fix some of the problems. Still, if you want to experiment with AppleEvents right now: go ahead!