1<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
2<html><head><title>Using the Open Scripting Architecture from Python</title></head>
3<body>
4<h1>Using the Open Scripting Architecture from Python</h1>
5<hr>
6
7<p><b>NOTE:</b> this document describes the OSA support that is shipped with
8the core python distribution. Most users are better of with the more
9userfriendly <a href="http://freespace.virgin.net/hamish.sanderson/appscript.html">appscript library</a>.
10
11<p>OSA support in Python is still not 100% complete, but
12there is already enough in place to allow you to do some nifty things
13with other programs from your python program. </p>
14
15
16<p>
17In this example, we will look at a scriptable application, extract its
18&#8220;AppleScript Dictionary,&#8221;  generate a Python interface package from
19the dictionary, and use that package to control the application.
20The application we are going to script is Disk Copy, Apple's standard
21utility for making copies of floppies, creating files that are mountable
22as disk images, etc.
23Because we want
24to concentrate on the OSA details, we won&#8217;t bother with a real
25user-interface for our application. </p>
26
27
28<p>
29<em>When we say &#8220;AppleScript&#8221; in this document we actually mean
30&#8220;the Open Scripting Architecture.&#8221; There is nothing
31AppleScript-specific in the Python implementation. Most of this document
32focuses on the classic Mac OS; <a href="#osx">Mac OS X</a> users have some
33additional tools.</em>
34</p>
35
36<h2>Python OSA architecture</h2>
37
38<p>Open Scripting suites and inheritance can be modelled rather nicely
39with Python packages, so we generate
40a package for each application we want to script. Each suite defined in
41the application becomes a module in the
42package, and the package main module imports everything from all the
43submodules and glues together all the classes (in Python terminology&#8212;
44events in OSA terminology or verbs in AppleScript terminology). </p>
45
46<p>
47A suite in an OSA application can extend the functionality of a standard
48suite. This is implemented in Python by importing everything from the
49module that implements the standard suites and overriding anything that has
50been extended. The standard suites live in the StdSuite package. </p>
51
52<p>
53This all sounds complicated, but the good news is that basic
54scripting is actually pretty simple. You can do strange and wondrous things
55with OSA scripting once you fully understand it. </p>
56
57<h2>Creating the Python interface package</h2>
58
59
60<p>There is a tool in the standard distribution that can automatically
61generate the interface packages.  This tool is called
62<code>gensuitemodule.py</code>, and lives in <code>Mac:scripts</code>.
63It looks through a file
64for an &#8216;AETE&#8217; or &#8216;AEUT&#8217; resource,
65the internal representation of the
66AppleScript dictionary, and parses the resource to generate the suite
67modules.
68When we start <code>gensuitemodule</code>, it asks us for an input file;
69for our example,
70we point it to the Disk Copy executable. </p>
71
72<p>
73Next, <code>gensuitemodule</code> wants a folder where it will store the
74package it is going to generate.
75Note that this is the package folder, not the parent folder, so we
76navigate to <code>Python:Mac:Demo:applescript</code>, create a folder
77<code>Disk_Copy</code>, and select that. </p>
78
79<p>
80We  next specify the folder from which <code>gensuitemodule</code>
81should import the standard suites. Here,
82we always select <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>. (There is
83one exception to this rule: when you are generating <code>StdSuites</code> itself
84you select <code>_builtinSuites</code>.)
85</p>
86
87<p>
88It starts parsing the AETE resource, and for
89each AppleEvent suite it finds, <code>gensuitemodule.py</code>
90prompts us for the filename of the
91resulting python module. Remember to change folders for the first
92module&#8212;you don't want to clutter up, say, the
93Disk Copy folder
94with your python
95interfaces. If you want to skip a suite, press <code>cancel</code> and the process
96continues with the next suite. </p>
97
98<h3>Summary</h3>
99
100<ol>
101
102	<li>Run <code>gensuitemodule</code>.</li>
103
104	<li>Select the application (or OSAX) for which you would like a Python interface.</li>
105
106	<li>Select the package folder where the interface modules should be
107	stored.</li>
108
109	<li>Specify the folder <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>
110	to import the standard suites (or <code>_builtinSuites</code> if you are
111	generating <code>StdSuites</code> itself). </li>
112
113	<li>Save the generated suites (use <code>cancel</code> to skip a suite).</li>
114
115
116</ol>
117
118
119<h3>Notes</h3>
120
121
122<ul>
123
124	<li>The interface package may occasionally need some editing by hand.  For example,
125	<code>gensuitemodule</code> does not handle all Python reserved words, so
126	if
127	 one of the AppleScript verbs is a Python reserved word, a <code>SyntaxError</code>
128	 may be raised when the package is imported.
129	Simply rename the class into something acceptable, if this happens;
130	take a look at how the
131	<code>print</code> verb is handled (automatically by <code>gensuitemodule</code>)
132	in the standard suites. But: f you need to edit your package this should be considered a
133	bug in gensuitemodule, so please report it so it can be fixed in future releases.
134	</li>
135
136
137	<li>If you want to re-create the StdSuite modules,
138you should look in one of two places. With versions of AppleScript older than 1.4.0
139(which first shipped with OS 9.0),  you will find the
140AEUT resources in <code>System Folder:Extensions:Scripting
141Additions:Dialects:English Dialect</code>. For newer versions, you will
142find them in <code>System Folder:Extensions:Applescript</code>.
143</li>
144
145	<li>Since MacPython 2.0, this new structure, with packages
146per application and submodules per suite, is used. Older MacPythons had a
147single level of modules, with uncertain semantics. With the new structure,
148it is possible for programs to override standard suites, as programs often do.
149
150</li>
151
152<li><code>Gensuitemodule.py</code> may ask you questions
153like &#8220;Where is enum 'xyz ' declared?&#8221;.
154This is either due to a misunderstanding on my part or (rather too commonly)
155bugs in the AETE resources. Pressing <code>cancel</code> is usually the
156right choice: it will cause the specific enum not to be treated as an enum
157but as a &#8220;normal&#8221; type. As things like fsspecs and TEXT strings clearly are
158not enumerators, this is correct. If someone understands what is really going on
159here, please let me know.</li>
160
161</ul>
162
163
164
165<h2>The Python interface package contents</h2>
166
167<p>
168Let&#8217;s glance at the
169<a href="applescript/Disk_Copy">Disk_Copy</a> package just created. You
170may want to open Script Editor alongside to see how it
171interprets the dictionary.
172</p>
173
174
175<p>
176The main package module is in <code>__init__.py</code>.
177The only interesting bit is the <code>Disk_Copy</code> class, which
178includes the event handling classes from the individual suites. It also
179inherits <code>aetools.TalkTo</code>, which is a base class that handles all
180details on how to start the program and talk to it, and a class variable
181<code>_signature</code> which is the default application this class will talk
182to (you can override this in various ways when you instantiate your class, see
183<code>aetools.py</code> for details).
184</p>
185
186<p>
187The <a href="applescript/Disk_Copy/Special_Events.py">Special_Events</a>
188module is a nice example of a suite module.
189The <code>Special_Events_Events</code> class is the bulk of the code
190generated. For each verb, it contains a method. Each method knows what
191arguments the verb expects, and it makes  use of keyword
192arguments to present a palatable
193interface to the python programmer.
194
195Notice that each method
196calls some routines from <code>aetools</code>, an auxiliary module
197living in <code>Mac:Lib</code>.
198The other thing to notice is that each method calls
199<code>self.send</code>.  This comes from the <code>aetools.TalkTo</code>
200baseclass. </p>
201
202
203<p>
204After the big class, there are a number of little class declarations. These
205declarations are for the (AppleEvent) classes and properties in the suite.
206They allow you to create object IDs, which can then be passed to the verbs.
207For instance,
208when scripting the popular email program Eudora,
209you would use <code>mailbox("inbox").message(1).sender</code>
210to get the name of the sender of the first message in mailbox
211inbox. It is
212also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
213which is sometimes needed because these classes don&#8217;t always inherit correctly
214from baseclasses, so you may have to use a class or property from another
215suite. </p>
216
217<p>
218Next we get the enumeration dictionaries, which allow you to pass
219english names as arguments to verbs, so you don't have to bother with the 4-letter
220type code. So, you can say
221<code>
222	diskcopy.create(..., filesystem="Mac OS Standard")
223</code>
224as it is called in Script Editor, instead of the cryptic lowlevel
225<code>
226	diskcopy.create(..., filesystem="Fhfs")
227</code></p>
228
229<p>
230Finally, we get the &#8220;table of contents&#8221; of the module, listing all
231classes and such
232by code, which is used by <code>gensuitemodule</code> itself: if you use this
233suite as a base package in a later run this is how it knows what is defined in this
234suite, and what the Python names are.
235</p>
236
237<h3>Notes</h3>
238
239<ul>
240
241	<li>The <code>aetools</code> module contains some other nifty
242AppleEvent tools as well. Have a look at it sometime, there is (of
243course) no documentation yet.
244</li>
245
246	<li>There are also some older object specifiers for standard objects in aetools.
247You use these in the form <code>aetools.Word(10,
248aetools.Document(1))</code>, where the corresponding AppleScript
249terminology would be <code>word 10 of the first
250document</code>. Examine
251<code>aetools</code> and <code>aetools.TalkTo</code>
252along with
253the comments at the end of your suite module if you need to create
254more than the standard object specifiers.
255</li>
256
257</ul>
258
259
260
261
262<h2>Using a Python suite module</h2>
263
264<p>
265Now that we have created the suite module, we can use it in a Python script.
266In older MacPython distributions this used to be a rather
267complicated affair, but with the package scheme and with the application signature
268known by the package it is very simple: you import the package and instantiate
269the class, e.g.
270<code>
271	talker = Disk_Copy.Disk_Copy(start=1)
272</code>
273You will usually specify the <code>start=1</code>: it will run the application if it is
274not already running.
275You may want to omit it if you want to talk to the application
276only if it is already running, or if the application is something like the Finder.
277Another way to ensure that  the application is running is to call <code>talker._start()</code>.
278</p>
279
280<p>
281Looking at the sourcefile <a
282href="applescript/makedisk.py">makedisk.py</a>, we see that it starts
283with some imports.  Naturally, one of these is the Python interface to Disk
284Copy.</p>
285
286<p>
287The main program itself is a wonder of simplicity: we create the
288object (<code>talker</code>) that talks to Disk Copy,
289create a disk, and mount it. The bulk of
290the work is done by <code>talker</code> and the Python interface package we
291just created.</p>
292
293<p>
294The exception handling does warrant a few comments, though. Since
295AppleScript is basically a connectionless RPC protocol,
296nothing happens
297when we create the <code>talker</code> object. Hence, if the destination application
298is not running, we will not notice until we send our first
299command (avoid this as described above). There is another thing to note about errors returned by
300AppleScript calls: <code>MacOS.Error</code> is raised for
301all of the errors that are known to be <code>OSErr</code>-type errors,
302while
303server generated errors raise <code>aetools.Error</code>. </p>
304
305<h2>Scripting Additions</h2>
306
307<p>
308If you want to use any of the scripting additions (or OSAXen, in
309everyday speech) from a Python program, you can use the same method
310as for applications, i.e. run <code>gensuitemodule</code> on the
311OSAX (commonly found in <code>System Folder:Scripting Additions</code>
312or something similar). There is one minor gotcha: the application
313signature to use is <code>MACS</code>. You will need to edit the main class
314in the <code>__init__.py</code> file of the created package and change the value
315of <code>_signature</code> to <code>MACS</code>, or use a subclass to the
316same effect.
317</p>
318
319<p>
320There are two minor points to watch out for when using <code>gensuitemodule</code>
321on OSAXen: they appear all to define the class <code>System_Object_Suite</code>,
322and a lot of them have the command set in multiple dialects. You have to
323watch out for name conflicts and make sure you select a reasonable dialect
324(some of the non-English dialects cause <code>gensuitemodule</code> to generate incorrect
325Python code). </p>
326
327Despite these difficulties, OSAXen offer a lot of possibilities.  Take a
328look at some of the OSAXen in the Scripting Additions folder, or
329<A HREF="http://www.osaxen.com/index.php">download</A> some from the net.
330
331<h2>Further Reading</h2>
332
333<p>
334If you want to look at more involved examples of applescripting, look at the standard
335modules <code>findertools</code> and <code>nsremote</code>, or (possibly better, as it
336is more involved) <code>fullbuild</code> from the <code>Mac:scripts</code> folder.
337</p>
338
339<h2><a name="alternatives">Alternatives</a></h2>
340
341<h3><a name="osx">Mac OS X</a></h3>
342
343<p>
344Under Mac OS X, the above still works, but with some new difficulties.
345The application package structure can hide the &#8216;AETE&#8217; or
346&#8216;AEUT&#8217; resource from <code>gensuitemodule</code>, so that,
347for example, it cannot generate an OSA interface to iTunes. Script
348Editor gets at the dictionary of such programs using a &#8216;Get
349AETE&#8217; AppleEvent, if someone wants to donate code to use the same
350method for gensuitemodule: by all means!
351</p>
352
353<p>
354One alternative is available through the Unix command line version of python.
355Apple has provided the <code>osacompile</code> and <code>osascript</code> tools,
356which can be used to compile and execute scripts written in OSA languages. See the
357man pages for more details.
358</p>
359
360
361</body>
362</html>
363