blob: f527b5225dea4d1836e96939bb8433a34d12a652 [file] [log] [blame]
Jack Jansena6308131996-03-18 13:38:52 +00001<HTML><HEAD><TITLE>Using Open Scripting Extension from Python</TITLE></HEAD>
2<BODY>
3<H1>Using Open Scripting Extension from Python</H1>
4<HR>
5
6OSA support in Python is still far from complete, and what
7support there is is likely to change in the forseeable future. Still,
8there is already enough in place to allow you to do some nifty things
9to other programs from your python program. <P>
10
11<CITE>
12Actually, when we say "AppleScript" in this document we actually mean
13"the Open Scripting Architecture", there is nothing
14AppleScript-specific in the Python implementation. <p>
15</CITE>
16
17In this example, we will look at a scriptable application, extract its
18"AppleScript Dictionary" and generate a Python interface module from
19that and use that module to control the application. Because we want
20to concentrate on the OSA details we don't bother with a real
21user-interface for our application. <p>
22
23The application we are going to script is Eudora Light, a free mail
24program from <A HREF="http://www.qualcomm.com">QualComm</A>. This is a
25very versatile mail-reader, and QualComm has an accompanying
26commercial version once your needs outgrow Eudora Light. Our program
27will tell Eudora to send queued mail, retrieve mail or quit. <p>
28
29<H2>Creating the Python interface module</H2>
30
31There is a tool in the standard distribution that looks through a file
32for an 'AETE' or 'AEUT' resource, the internal representation of the
33AppleScript dictionary. This tool is called
Jack Jansenf10786b1997-08-19 14:00:56 +000034<CODE>gensuitemodule.py</CODE>, and lives in <CODE>Mac:scripts</CODE>.
35When we start it, it asks us for an input file and we point it to the
36Eudora Light executable. It starts parsing the AETE resource, and for
37each AppleEvent suite it finds it prompts us for the filename of the
38resulting python module. Remember to change folders for the first
39module, you don't want to clutter up the Eudora folder with your python
40interfaces. If you want to skip a suite you press cancel and the process
41continues with the next suite. In the case of Eudora, you do
42<EM>not</EM> want to generate the Required and Standard suites, because
43they are identical to the standard ones which are pregenerated (and
44empty in the eudora binary). AppleScript understands that an empty suite
45means "incorporate the whole standard suite by this name",
Jack Jansena6308131996-03-18 13:38:52 +000046gensuitemodule does not currently understand this. Creating the empty
47<CODE>Required_Suite.py</CODE> would hide the correct module of that
48name from our application. <p>
49
Jack Jansenf10786b1997-08-19 14:00:56 +000050Gensuitemodule may ask you questions like "Where is enum 'xyz ' declared?".
51For the first time, cancel out of this dialog after taking down the
52enum (or class or prop) name. After you've created all the suites look
53for these codes, in the suites generated here and in the standard suites.
54If you've found them all run gensuitemodule again and point it to the right
55file for each declaration. Gensuitemodule will generate the imports to make the
56reference work. <p>
57
Jack Jansen08365421996-04-19 15:56:08 +000058<BLOCKQUOTE>
Jack Jansena6308131996-03-18 13:38:52 +000059Time for a sidebar. If you want to re-create
60<CODE>Required_Suite.py</CODE> or one of the other standard modules
61you should look in <CODE>System Folder:Extensions:Scripting
62Additions:Dialects:English Dialect</CODE>, that is where the core
63AppleEvent dictionaries live. Also, if you are looking for the
64<CODE>Finder_Suite</CODE> interface: don't look in the finder (it has
65an old System 7.0 scripting suite), look at the extension <CODE>Finder
66Scripting Extension</CODE>. <p>
Jack Jansen08365421996-04-19 15:56:08 +000067</BLOCKQUOTE>
Jack Jansena6308131996-03-18 13:38:52 +000068
69Let's glance at the <A
70HREF="scripting/Eudora_Suite.py">Eudora_Suite.py</A> just created. You
71may want to open Script Editor alongside, and have a look at how it
72interprets the dictionary. EudoraSuite.py starts with some
Jack Jansenf10786b1997-08-19 14:00:56 +000073boilerplate, then a big class definition with methods for each
74AppleScript Verb, then some small class definitions and then some dictionary
75initializations. <p>
Jack Jansena6308131996-03-18 13:38:52 +000076
77The <CODE>Eudora_Suite</CODE> class is the bulk of the code
78generated. For each verb it contains a method. Each method knows what
79arguments the verb expects, and it makes handy use of the keyword
80argument scheme introduced in Python 1.3 to present a palatable
81interface to the python programmer. You will see that each method
82calls some routines from <CODE>aetools</CODE>, an auxiliary module
Jack Jansen08365421996-04-19 15:56:08 +000083living in <CODE>Lib:toolbox</CODE> which contains some other nifty
Jack Jansena6308131996-03-18 13:38:52 +000084AppleEvent tools as well. Have a look at it sometime, there is (of
85course) no documentation yet. <p>
86
87The other thing you notice is that each method calls
88<CODE>self.send</CODE>, but no such method is defined. You will have
89to provide it by subclassing or multiple inheritance, as we shall see
90later. <p>
91
Jack Jansenf10786b1997-08-19 14:00:56 +000092After the big class we get a number of little class declarations. These
93declarations are for the (appleevent) classes and properties in the suite.
94They allow you to create object IDs, which can then be passed to the verbs.
95For instance, to get the name of the sender of the first message in mailbox
96inbox you would use <code>mailbox("inbox").message(1).sender</code>. It is
97also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
98which is sometimes needed because these classes don't inherit correctly
99from baseclasses, so you may have to use a class or property from another suite. <p>
100
101<blockquote>
102There are also some older object specifiers for standard objects in aetools.
103You use these in the form <CODE>aetools.Word(10,
Jack Jansena6308131996-03-18 13:38:52 +0000104aetools.Document(1))</CODE> where the corresponding AppleScript
105terminology would be <CODE>word 10 of the first
106document</CODE>. Examine the two modules mentioned above along with
107the comments at the end of your suite module if you need to create
Jack Jansenf10786b1997-08-19 14:00:56 +0000108more than the standard object specifiers.
109</blockquote>
110
111Next we get the enumeration dictionaries, which allow you to pass
112english names as arguments to verbs, so you don't have to bother with the 4-letter
113type code. So, you can say
114<CODE><PRE>
115 eudora.notice(occurrence="mail_arrives")
116</PRE></CODE>
117instead of the rather more cryptic
118<CODE><PRE>
119 eudora.notice(occurrence="wArv")
120</PRE></CODE><p>
121
122Finally, we get the "table of contents" of the module, listing all classes and such
123by code, which is used by gensuitemodule. <p>
Jack Jansena6308131996-03-18 13:38:52 +0000124
125<H2>Using a Python suite module</H2>
126
127Now that we have created the suite module we can use it in an
128application. We do this by creating a class that inherits
129<CODE>Eudora_Suite</CODE> and the <CODE>TalkTo</CODE> class from
130<CODE>aetools</CODE>. The <CODE>TalkTo</CODE> class is basically a
131container for the <CODE>send</CODE> method used by the methods from
132the suite classes. <p>
133
134Actually, our class will also inherit <CODE>Required_Suite</CODE>,
135because we also need functionality from that suite: the quit
136command. Gensuitemodule could have created this completely derived
137class for us, since it has access to all information needed to build
138the class but unfortunately it does not do so at the moment. All in
139all, the heart of our program looks like this:
140<CODE><PRE>
141 import Eudora_Suite, Required_Suite, aetools
142
Jack Jansenf10786b1997-08-19 14:00:56 +0000143 class Eudora(Eudora_Suite.Eudora_Suite, Required_Suite.Required_Suite, \
144 aetools.TalkTo):
Jack Jansena6308131996-03-18 13:38:52 +0000145 pass
146</PRE></CODE>
147
148Yes, our class body is <CODE>pass</CODE>, all functionality is already
149provided by the base classes, the only thing we have to do is glue it
150together in the right way. <p>
151
152Looking at the sourcefile <A
153HREF="scripting/testeudora.py">testeudora.py</A> we see that it starts
Jack Jansen08365421996-04-19 15:56:08 +0000154with some imports. Then we get the class definition
Jack Jansena6308131996-03-18 13:38:52 +0000155for our main object and a constant giving the signature of Eudora. <p>
156
157This, again, needs a little explanation. There are various ways to
158describe to AppleScript which program we want to talk to, but the
159easiest one to use (from Python, at least) is creator
160signature. Application name would be much nicer, but Python currently
161does not have a module that interfaces to the Finder database (which
162would allow us to map names to signatures). The other alternative,
163<CODE>ChooseApplication</CODE> from the program-to-program toolbox, is
164also not available from Python at the moment. <p>
165
Jack Jansenbdf03a01996-09-20 15:22:47 +0000166If you specify the application by creator you can specify an optional
167<CODE>start</CODE> parameter, which will cause the application to be
168started if it is not running. <P>
169
Jack Jansena6308131996-03-18 13:38:52 +0000170The main program itself is a wonder of simplicity. We create the
171object that talks to Eudora (passing the signature as argument), ask
172the user what she wants and call the appropriate method of the talker
173object. The use of keyword arguments with the same names as used by
174AppleScript make passing the parameters a breeze. <p>
175
176The exception handling does need a few comments, though. Since
177AppleScript is basically a connectionless RPC protocol nothing happens
178when we create to talker object. Hence, if the destination application
179is not running we will not notice until we send our first
180command. There is another thing to note about errors returned by
Jack Jansen0fb1d821996-11-20 15:13:24 +0000181AppleScript calls: <CODE>MacOS.Error</CODE> is raised for
182all of the errors that are known to be <CODE>OSErr</CODE>-type errors,
183server generated errors raise <CODE>aetools.Error</CODE>. <p>
Jack Jansena6308131996-03-18 13:38:52 +0000184
Jack Jansena742d111996-12-23 17:28:53 +0000185<H2>Scripting Additions</H2>
186
187If you want to use any of the scripting additions (or OSAXen, in
188everyday speech) from a Python program you can use the same method
189as for applications, i.e. run <CODE>gensuitemodule</CODE> on the
190OSAX (commonly found in <CODE>System Folder:Extensions:Scripting Additions</CODE>
191or something similar), define a class which inherits the generated
192class and <CODE>aetools.TalkTo</CODE> and instantiate it. The application
193signature to use is <CODE>'MACS'</CODE>. <P>
194
195There are two minor points to watch out for when using gensuitemodule
196on OSAXen: they appear all to define the class <CODE>System_Object_Suite</CODE>,
197and a lot of them have the command set in multiple dialects. You have to
198watch out for name conflicts, so, and make sure you select a reasonable dialect
199(some of the non-english dialects cause gensuitemodule to generate incorrect
200Python code). <P>
201
Jack Jansena6308131996-03-18 13:38:52 +0000202That concludes our simple example. Again, let me emphasize that
203scripting support in Python is not very complete at the moment, and
204the details of how to use AppleEvents will definitely change in the
205near future. This will not only fix all the ideosyncracies noted in
206this document but also break existing programs, since the current
207suite organization will have to change to fix some of the problems.
208Still, if you want to experiment with AppleEvents right now: go ahead!
209<p>