Fix three PyChecker-detected gotchas.
[python/dscho.git] / Mac / Demo / applescript.html
blob183d66599a2fbd0a7dee19134807fc33c1a1ead8
1 <HTML><HEAD><TITLE>Using Open Scripting Extension from Python</TITLE></HEAD>
2 <BODY>
3 <H1>Using Open Scripting Extension from Python</H1>
4 <HR>
6 OSA support in Python is still not 100% complete, but
7 there is already enough in place to allow you to do some nifty things
8 to other programs from your python program. <P>
10 <CITE>
11 Actually, when we say "AppleScript" in this document we actually mean
12 "the Open Scripting Architecture", there is nothing
13 AppleScript-specific in the Python implementation. <p>
14 </CITE>
16 In this example, we will look at a scriptable application, extract its
17 "AppleScript Dictionary" and generate a Python interface module from
18 that and use that module to control the application. Because we want
19 to concentrate on the OSA details we don't bother with a real
20 user-interface for our application. <p>
22 The application we are going to script is Disk Copy, Apple's standard
23 utility for making copies of floppies, creating files that are mountable
24 as disk images, etc. <p>
26 <H2>Python OSA architecture</H2>
28 Open Scripting suites and inheritance can be modelled rather nicely with
29 with Python packages, so for each application we want to script we generate
30 a package. Each suite defined in the application becomes a module in the
31 package, and the package main module imports everything from all the
32 submodules and glues all the classes (Python terminology, OSA terminology is
33 events, AppleScript terminology is verbs) together. <p>
35 A suite in an OSA application can extend the functionality of a standard
36 suite, and this is implemented in Python by importing everything from the
37 module that implements the standard suite and overriding anything that has
38 been extended. The standard suites live in the StdSuite package. <p>
40 This all sounds complicated, and you can do strange and wondrous things
41 with it once you fully understand it, but the good news is that simple
42 scripting is actually pretty simple. <p>
44 <H2>Creating the Python interface module</H2>
46 There is a tool in the standard distribution that looks through a file
47 for an 'AETE' or 'AEUT' resource, the internal representation of the
48 AppleScript dictionary. This tool is called
49 <CODE>gensuitemodule.py</CODE>, and lives in <CODE>Mac:scripts</CODE>.
50 When we start it, it asks us for an input file and we point it to the
51 Disk Copy executable. <p>
53 Next it wants a folder where it will store the package it is going to generate.
54 Note that this is the package folder, not the parent folder, so we
55 navigate to <code>Python:Mac:Demo:applescript</code>, create a folder
56 <code>Disk_Copy</code> and select that. <p>
58 Next it wants the folder from which it should import the standard suites. Here
59 you always select <code>Python:Mac:Lib:lib-scriptpackages</code>. (There is
60 one exception to this rule: when you are generating <code>StdSuites</code> itself
61 you select <code>cancel</code>, for obvious reasons). <p>
63 It starts parsing the AETE resource, and for
64 each AppleEvent suite it finds it prompts us for the filename of the
65 resulting python module. Remember to change folders for the first
66 module, you don't want to clutter up the Eudora folder with your python
67 interfaces. If you want to skip a suite you press cancel and the process
68 continues with the next suite. <p>
70 Gensuitemodule may ask you questions like "Where is enum 'xyz ' declared?".
71 This is either due to a misunderstanding on my part or (rather too common)
72 bugs in the AETE resources. Pressing <code>cancel</code> is usually the
73 right option, it will cause the specific enum not to be treated as an enum
74 but as a "normal" type. As things like fsspecs and TEXT strings clearly are
75 not enumerators this is correct. If someone understands what is really going on
76 here please let me know. <p>
78 <BLOCKQUOTE>
79 Time for a sidebar. If you want to re-create the StdSuite modules
80 you should look in one of two places. On older systems you will find the
81 AEUT resources in <CODE>System Folder:Extensions:Scripting
82 Additions:Dialects:English Dialect</CODE>. On newer systems you will
83 find them in <code>System Folder:Extensions:Applescript</code>. <p>
84 </BLOCKQUOTE>
86 Let's glance at the <A
87 HREF="applescript/Disk_Copy">Disk_Copy</A> package just created. You
88 may want to open Script Editor alongside, and have a look at how it
89 interprets the dictionary. The main package module is in <code>__init__.py</code>
90 and the only interesting bit is the <code>Disk_Copy</code> class, which
91 includes the event handling classes from the individual suites. It also
92 inherits <code>aetools.TalkTo</code>, which is a base class that handles all
93 details on how to start the program and talk to it, and a class variable
94 <code>_signature</code> which is the default application this class will talk
95 to (you can override this in various when you instantiate your class, see
96 <code>aetools.py</code> for details).
97 <p>
98 <blockquote>
99 Let us do another sidebar. Since MacPython 2.0 this new structure, with packages
100 per application and submodules per suite, is used. Older MacPythons had a
101 single level of modules, with uncertain semantics. With the new structure
102 it is possible for programs to override standard suites, as programs often do.
103 It is a good idea to convert your own old programs to the new scheme, but if you
104 really want the old standard suites are still available in
105 <code>:Mac:Lib:lib-scripting</code>.
106 </blockquote>
108 The <a href="applescript/Disk_Copy/Special_Events.py">Special_Events</a>
109 module is a nice example of a suite module.
110 The <CODE>Special_Events_Events</CODE> class is the bulk of the code
111 generated. For each verb it contains a method. Each method knows what
112 arguments the verb expects, and it makes handy use of keyword
113 arguments to present a palatable
114 interface to the python programmer. You will see that each method
115 calls some routines from <CODE>aetools</CODE>, an auxiliary module
116 living in <CODE>Lib:toolbox</CODE> which contains some other nifty
117 AppleEvent tools as well. Have a look at it sometime, there is (of
118 course) no documentation yet. <p>
120 The other thing you notice is that each method calls
121 <CODE>self.send</CODE>, this comes from the <code>aetools.TalkTo</code> baseclass. <p>
123 After the big class we get a number of little class declarations. These
124 declarations are for the (appleevent) classes and properties in the suite.
125 They allow you to create object IDs, which can then be passed to the verbs.
126 For instance, to get the name of the sender of the first message in mailbox
127 inbox you would use <code>mailbox("inbox").message(1).sender</code>. It is
128 also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
129 which is sometimes needed because these classes don't always inherit correctly
130 from baseclasses, so you may have to use a class or property from another suite. <p>
132 <blockquote>
133 There are also some older object specifiers for standard objects in aetools.
134 You use these in the form <CODE>aetools.Word(10,
135 aetools.Document(1))</CODE> where the corresponding AppleScript
136 terminology would be <CODE>word 10 of the first
137 document</CODE>. Examine the two modules mentioned above along with
138 the comments at the end of your suite module if you need to create
139 more than the standard object specifiers.
140 </blockquote>
142 Next we get the enumeration dictionaries, which allow you to pass
143 english names as arguments to verbs, so you don't have to bother with the 4-letter
144 type code. So, you can say
145 <CODE><PRE>
146 diskcopy.create(..., filesystem="Mac OS Standard")
147 </PRE></CODE>
148 as it is called in Script Editor, in stead of the cryptic lowlevel
149 <CODE><PRE>
150 diskcopy.create(..., filesystem="Fhfs")
151 </PRE></CODE><p>
153 Finally, we get the "table of contents" of the module, listing all classes and such
154 by code, which is used by gensuitemodule. <p>
156 <H2>Using a Python suite module</H2>
158 Now that we have created the suite module we can use it in a Python script.
160 In older MacPython distributions this used to be a rather
161 complicated affair, but with the package scheme and with the application signature
162 known by the package it is very simple: you import the package and instantiate
163 the class, as
164 <CODE><PRE>
165 talker = Disk_Copy.Disk_Copy(start=1)
166 </PRE></CODE>
167 You will usually specify the start=1: it will run the application if it is
168 not already running. You may want to omit it if you want to talk to the application
169 only if it is already running, or if the application is something like the Finder. <p>
171 Looking at the sourcefile <A
172 HREF="applescript/makedisk.py">makedisk.py</A> we see that it starts
173 with some imports.
175 The main program itself is a wonder of simplicity. We create the
176 object that talks to Disk Copy, creates a disk and mounts it. <p>
178 The exception handling does need a few comments, though. Since
179 AppleScript is basically a connectionless RPC protocol nothing happens
180 when we create to talker object. Hence, if the destination application
181 is not running we will not notice until we send our first
182 command. There is another thing to note about errors returned by
183 AppleScript calls: <CODE>MacOS.Error</CODE> is raised for
184 all of the errors that are known to be <CODE>OSErr</CODE>-type errors,
185 server generated errors raise <CODE>aetools.Error</CODE>. <p>
187 <H2>Scripting Additions</H2>
189 If you want to use any of the scripting additions (or OSAXen, in
190 everyday speech) from a Python program you can use the same method
191 as for applications, i.e. run <CODE>gensuitemodule</CODE> on the
192 OSAX (commonly found in <CODE>System Folder:Extensions:Scripting Additions</CODE>
193 or something similar). There is one minor gotcha: the application
194 signature to use is <CODE>'MACS'</CODE>. <P>
196 There are two minor points to watch out for when using gensuitemodule
197 on OSAXen: they appear all to define the class <CODE>System_Object_Suite</CODE>,
198 and a lot of them have the command set in multiple dialects. You have to
199 watch out for name conflicts, so, and make sure you select a reasonable dialect
200 (some of the non-english dialects cause gensuitemodule to generate incorrect
201 Python code). <P>
203 <H2>Further Reading</H2>
205 If you want to look at more involved examples of applescripting look at the standard
206 modules <code>findertools</code> and <code>nsremote</code>, or (possibly better, as it
207 is more involved) <code>fullbuild</code> from the Mac:scripts folder.