blob: 7db5ba6020b2dd222a272869ded7b4c419661e77 [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
34<CODE>gensuitemodule.py</CODE>, and lives in
Jack Jansen08365421996-04-19 15:56:08 +000035<CODE>Mac:scripts</CODE>. When we start it, it asks us for an input
Jack Jansena6308131996-03-18 13:38:52 +000036file and we point it to the Eudora Light executable. It starts parsing
37the AETE resource, and for each AppleEvent suite it finds it prompts
38us for the filename of the resulting python module. Remember to change
39folders for the first module, you don't want to clutter up the Eudora
40folder with your python interfaces. If you want to skip a suite you
41press cancel and the process continues with the next suite. In the
42case of Eudora, you do <EM>not</EM> want to generate the Required
43suite, because it will be empty. AppleScript understands that an empty
44suite means "incorporate the whole standard suite by this name",
45gensuitemodule does not currently understand this. Creating the empty
46<CODE>Required_Suite.py</CODE> would hide the correct module of that
47name from our application. <p>
48
Jack Jansen08365421996-04-19 15:56:08 +000049<BLOCKQUOTE>
Jack Jansena6308131996-03-18 13:38:52 +000050Time for a sidebar. If you want to re-create
51<CODE>Required_Suite.py</CODE> or one of the other standard modules
52you should look in <CODE>System Folder:Extensions:Scripting
53Additions:Dialects:English Dialect</CODE>, that is where the core
54AppleEvent dictionaries live. Also, if you are looking for the
55<CODE>Finder_Suite</CODE> interface: don't look in the finder (it has
56an old System 7.0 scripting suite), look at the extension <CODE>Finder
57Scripting Extension</CODE>. <p>
Jack Jansen08365421996-04-19 15:56:08 +000058</BLOCKQUOTE>
Jack Jansena6308131996-03-18 13:38:52 +000059
60Let's glance at the <A
61HREF="scripting/Eudora_Suite.py">Eudora_Suite.py</A> just created. You
62may want to open Script Editor alongside, and have a look at how it
63interprets the dictionary. EudoraSuite.py starts with some
64boilerplate, then come some dictionaries implementing the OSA
65Enumerations, then a big class definition with methods for each
66AppleScript Verb and finally some comments. The Enumerations we will
67skip, it suffices to know that whenever you have to pass an enumerator
68to a method you can pass the english name and don't have to bother
69with the 4-letter type code. So, you can say
70<CODE><PRE>
71 eudora.notice(occurrence="mail_arrives")
72</PRE></CODE>
73instead of the rather more cryptic
74<CODE><PRE>
75 eudora.notice(occurrence="wArv")
76</PRE></CODE>
77
78The <CODE>Eudora_Suite</CODE> class is the bulk of the code
79generated. For each verb it contains a method. Each method knows what
80arguments the verb expects, and it makes handy use of the keyword
81argument scheme introduced in Python 1.3 to present a palatable
82interface to the python programmer. You will see that each method
83calls some routines from <CODE>aetools</CODE>, an auxiliary module
Jack Jansen08365421996-04-19 15:56:08 +000084living in <CODE>Lib:toolbox</CODE> which contains some other nifty
Jack Jansena6308131996-03-18 13:38:52 +000085AppleEvent tools as well. Have a look at it sometime, there is (of
86course) no documentation yet. <p>
87
88The other thing you notice is that each method calls
89<CODE>self.send</CODE>, but no such method is defined. You will have
90to provide it by subclassing or multiple inheritance, as we shall see
91later. <p>
92
93The module ends with some comments. Sadly, gensuitemodule is not yet
94able to turn the Object Specifiers into reasonable Python code. For
95now, if you need object specifiers, you will have to use the routines
96defined in <CODE>aetools.py</CODE> (and <CODE>aetypes.py</CODE>, which
97it incorporates). You use these in the form <CODE>aetools.Word(10,
98aetools.Document(1))</CODE> where the corresponding AppleScript
99terminology would be <CODE>word 10 of the first
100document</CODE>. Examine the two modules mentioned above along with
101the comments at the end of your suite module if you need to create
102more than the standard object specifiers. <p>
103
104<H2>Using a Python suite module</H2>
105
106Now that we have created the suite module we can use it in an
107application. We do this by creating a class that inherits
108<CODE>Eudora_Suite</CODE> and the <CODE>TalkTo</CODE> class from
109<CODE>aetools</CODE>. The <CODE>TalkTo</CODE> class is basically a
110container for the <CODE>send</CODE> method used by the methods from
111the suite classes. <p>
112
113Actually, our class will also inherit <CODE>Required_Suite</CODE>,
114because we also need functionality from that suite: the quit
115command. Gensuitemodule could have created this completely derived
116class for us, since it has access to all information needed to build
117the class but unfortunately it does not do so at the moment. All in
118all, the heart of our program looks like this:
119<CODE><PRE>
120 import Eudora_Suite, Required_Suite, aetools
121
122 class Eudora(aetools.TalkTo, Required_Suite.Required_Suite, \
123 Eudora_Suite.Eudora_Suite):
124 pass
125</PRE></CODE>
126
127Yes, our class body is <CODE>pass</CODE>, all functionality is already
128provided by the base classes, the only thing we have to do is glue it
129together in the right way. <p>
130
131Looking at the sourcefile <A
132HREF="scripting/testeudora.py">testeudora.py</A> we see that it starts
Jack Jansen08365421996-04-19 15:56:08 +0000133with some imports. Then we get the class definition
Jack Jansena6308131996-03-18 13:38:52 +0000134for our main object and a constant giving the signature of Eudora. <p>
135
136This, again, needs a little explanation. There are various ways to
137describe to AppleScript which program we want to talk to, but the
138easiest one to use (from Python, at least) is creator
139signature. Application name would be much nicer, but Python currently
140does not have a module that interfaces to the Finder database (which
141would allow us to map names to signatures). The other alternative,
142<CODE>ChooseApplication</CODE> from the program-to-program toolbox, is
143also not available from Python at the moment. <p>
144
145The main program itself is a wonder of simplicity. We create the
146object that talks to Eudora (passing the signature as argument), ask
147the user what she wants and call the appropriate method of the talker
148object. The use of keyword arguments with the same names as used by
149AppleScript make passing the parameters a breeze. <p>
150
151The exception handling does need a few comments, though. Since
152AppleScript is basically a connectionless RPC protocol nothing happens
153when we create to talker object. Hence, if the destination application
154is not running we will not notice until we send our first
155command. There is another thing to note about errors returned by
156AppleScript calls: even though <CODE>MacOS.Error</CODE> is raised not
157all of the errors are actually <CODE>OSErr</CODE>-type errors, some
158are error codes returned by the server application. In that case, the
159error message will be incorrect. <p>
160
161That concludes our simple example. Again, let me emphasize that
162scripting support in Python is not very complete at the moment, and
163the details of how to use AppleEvents will definitely change in the
164near future. This will not only fix all the ideosyncracies noted in
165this document but also break existing programs, since the current
166suite organization will have to change to fix some of the problems.
167Still, if you want to experiment with AppleEvents right now: go ahead!
168<p>