OSA support in Python is still not 100% complete, but there is already enough in place to allow you to do some nifty things with other programs from your python program.
In this example, we will look at a scriptable application, extract its “AppleScript Dictionary,” generate a Python interface package from the dictionary, and use that package to control the application. The application we are going to script is Disk Copy, Apple's standard utility for making copies of floppies, creating files that are mountable as disk images, etc. Because we want to concentrate on the OSA details, we won’t bother with a real user-interface for our application.
When we say “AppleScript” in this document we actually mean “the Open Scripting Architecture.” There is nothing AppleScript-specific in the Python implementation. Most of this document focuses on the classic Mac OS; Mac OS X users have some additional tools.
Open Scripting suites and inheritance can be modelled rather nicely with Python packages, so we generate a package for each application we want to script. Each suite defined in the application becomes a module in the package, and the package main module imports everything from all the submodules and glues together all the classes (in Python terminology— events in OSA terminology or verbs in AppleScript terminology).
A suite in an OSA application can extend the functionality of a standard suite. This is implemented in Python by importing everything from the module that implements the standard suites and overriding anything that has been extended. The standard suites live in the StdSuite package.
This all sounds complicated, but the good news is that basic scripting is actually pretty simple. You can do strange and wondrous things with OSA scripting once you fully understand it.
There is a tool in the standard distribution that can automatically
generate the interface packages. This tool is called
gensuitemodule.py, and lives in
It looks through a file
for an ‘AETE’ or ‘AEUT’ resource,
the internal representation of the
AppleScript dictionary, and parses the resource to generate the suite
When we start
gensuitemodule, it asks us for an input file;
for our example,
we point it to the Disk Copy executable.
gensuitemodule wants a folder where it will store the
package it is going to generate.
Note that this is the package folder, not the parent folder, so we
Python:Mac:Demo:applescript, create a folder
Disk_Copy, and select that.
We next specify the folder from which
should import the standard suites. Here,
we always select
Python:Mac:Lib:lib-scriptpackages:StdSuites. (There is
one exception to this rule: when you are generating
It starts parsing the AETE resource, and for
each AppleEvent suite it finds,
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, say, the
Disk Copy folder
with your python
interfaces. If you want to skip a suite, press
cancel and the process
continues with the next suite.
Python:Mac:Lib:lib-scriptpackages:StdSuitesto import the standard suites (or
_builtinSuitesif you are generating
cancelto skip a suite).
gensuitemoduledoes not handle all Python reserved words, so if one of the AppleScript verbs is a Python reserved word, a
SyntaxErrormay be raised when the package is imported. Simply rename the class into something acceptable, if this happens; take a look at how the
gensuitemodule) in the standard suites. But: f you need to edit your package this should be considered a bug in gensuitemodule, so please report it so it can be fixed in future releases.
System Folder:Extensions:Scripting Additions:Dialects:English Dialect. For newer versions, you will find them in
Gensuitemodule.pymay ask you questions like “Where is enum 'xyz ' declared?”. This is either due to a misunderstanding on my part or (rather too commonly) bugs in the AETE resources. Pressing
cancelis usually the right choice: it will cause the specific enum not to be treated as an enum but as a “normal” type. As things like fsspecs and TEXT strings clearly are not enumerators, this is correct. If someone understands what is really going on here, please let me know.
Let’s glance at the Disk_Copy package just created. You may want to open Script Editor alongside to see how it interprets the dictionary.
The main package module is in
The only interesting bit is the
Disk_Copy class, which
includes the event handling classes from the individual suites. It also
aetools.TalkTo, which is a base class that handles all
details on how to start the program and talk to it, and a class variable
_signature which is the default application this class will talk
to (you can override this in various ways when you instantiate your class, see
aetools.py for details).
module is a nice example of a suite module.
Special_Events_Events 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 use of keyword
arguments to present a palatable
interface to the python programmer.
Notice that each method
calls some routines from
aetools, an auxiliary module
The other thing to notice is that each method calls
self.send. This comes from the
After the big class, there are a number of little class declarations. These
declarations are for the (AppleEvent) classes and properties in the suite.
They allow you to create object IDs, which can then be passed to the verbs.
when scripting the popular email program Eudora,
you would use
to get the name of the sender of the first message in mailbox
inbox. It is
also possible to specify this as
which is sometimes needed because these classes don’t always inherit correctly
from baseclasses, so you may have to use a class or property from another
Next we get the enumeration dictionaries, which allow you to pass
english names as arguments to verbs, so you don't have to bother with the 4-letter
type code. So, you can say
diskcopy.create(..., filesystem="Mac OS Standard")
as it is called in Script Editor, instead of the cryptic lowlevel
Finally, we get the “table of contents” of the module, listing all
classes and such
by code, which is used by
gensuitemodule itself: if you use this
suite as a base package in a later run this is how it knows what is defined in this
suite, and what the Python names are.
aetoolsmodule contains some other nifty AppleEvent tools as well. Have a look at it sometime, there is (of course) no documentation yet.
aetools.Word(10, aetools.Document(1)), where the corresponding AppleScript terminology would be
word 10 of the first document. Examine
aetools.TalkToalong with the comments at the end of your suite module if you need to create more than the standard object specifiers.
Now that we have created the suite module, we can use it in a Python script.
In older MacPython distributions this used to be a rather
complicated affair, but with the package scheme and with the application signature
known by the package it is very simple: you import the package and instantiate
the class, e.g.
talker = Disk_Copy.Disk_Copy(start=1)
You will usually specify the
start=1: it will run the application if it is
not already running.
You may want to omit it if you want to talk to the application
only if it is already running, or if the application is something like the Finder.
Another way to ensure that the application is running is to call
Looking at the sourcefile makedisk.py, we see that it starts with some imports. Naturally, one of these is the Python interface to Disk Copy.
The main program itself is a wonder of simplicity: we create the
talker) that talks to Disk Copy,
create a disk, and mount it. The bulk of
the work is done by
talker and the Python interface package we
The exception handling does warrant a few comments, though. Since
AppleScript is basically a connectionless RPC protocol,
when we create the
talker object. Hence, if the destination application
is not running, we will not notice until we send our first
command (avoid this as described above). There is another thing to note about errors returned by
MacOS.Error is raised for
all of the errors that are known to be
server generated errors raise
If you want to use any of the scripting additions (or OSAXen, in
everyday speech) from a Python program, you can use the same method
as for applications, i.e. run
gensuitemodule on the
OSAX (commonly found in
System Folder:Scripting Additions
or something similar). There is one minor gotcha: the application
signature to use is
MACS. You will need to edit the main class
__init__.py file of the created package and change the value
MACS, or use a subclass to the
There are two minor points to watch out for when using
on OSAXen: they appear all to define the class
and a lot of them have the command set in multiple dialects. You have to
watch out for name conflicts and make sure you select a reasonable dialect
(some of the non-English dialects cause
gensuitemodule to generate incorrect
If you want to look at more involved examples of applescripting, look at the standard
nsremote, or (possibly better, as it
is more involved)
fullbuild from the
Under Mac OS X, the above still works, but with some new difficulties.
The application package structure can hide the ‘AETE’ or
‘AEUT’ resource from
gensuitemodule, so that,
for example, it cannot generate an OSA interface to iTunes. Script
Editor gets at the dictionary of such programs using a ‘Get
AETE’ AppleEvent, if someone wants to donate code to use the same
method for gensuitemodule: by all means!
One alternative is available through the Unix command line version of python.
Apple has provided the
which can be used to compile and execute scripts written in OSA languages. See the
man pages for more details.