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“AppleScript Dictionary,” 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’t bother with a real 25user-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 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— 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 ‘AETE’ or ‘AEUT’ 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—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 “Where is enum 'xyz ' declared?”. 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 “normal” 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’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’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 “table of contents” 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 ‘AETE’ or 346‘AEUT’ 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 ‘Get 349AETE’ 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