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