cpython/Mac/Demo/applescript.html

210 lines
10 KiB
HTML

<HTML><HEAD><TITLE>Using Open Scripting Extension from Python</TITLE></HEAD>
<BODY>
<H1>Using Open Scripting Extension from Python</H1>
<HR>
OSA support in Python is still far from complete, and what
support there is is likely to change in the forseeable future. Still,
there is already enough in place to allow you to do some nifty things
to other programs from your python program. <P>
<CITE>
Actually, when we say "AppleScript" in this document we actually mean
"the Open Scripting Architecture", there is nothing
AppleScript-specific in the Python implementation. <p>
</CITE>
In this example, we will look at a scriptable application, extract its
"AppleScript Dictionary" and generate a Python interface module from
that and use that module to control the application. Because we want
to concentrate on the OSA details we don't bother with a real
user-interface for our application. <p>
The application we are going to script is Eudora Light, a free mail
program from <A HREF="http://www.qualcomm.com">QualComm</A>. This is a
very versatile mail-reader, and QualComm has an accompanying
commercial version once your needs outgrow Eudora Light. Our program
will tell Eudora to send queued mail, retrieve mail or quit. <p>
<H2>Creating the Python interface module</H2>
There is a tool in the standard distribution that looks through a file
for an 'AETE' or 'AEUT' resource, the internal representation of the
AppleScript dictionary. This tool is called
<CODE>gensuitemodule.py</CODE>, and lives in <CODE>Mac:scripts</CODE>.
When we start it, it asks us for an input file and we point it to the
Eudora Light executable. It starts parsing the AETE resource, and for
each AppleEvent suite it finds it prompts us for the filename of the
resulting python module. Remember to change folders for the first
module, you don't want to clutter up the Eudora folder with your python
interfaces. If you want to skip a suite you press cancel and the process
continues with the next suite. In the case of Eudora, you do
<EM>not</EM> want to generate the Required and Standard suites, because
they are identical to the standard ones which are pregenerated (and
empty in the eudora binary). AppleScript understands that an empty suite
means "incorporate the whole standard suite by this name",
gensuitemodule does not currently understand this. Creating the empty
<CODE>Required_Suite.py</CODE> would hide the correct module of that
name from our application. <p>
Gensuitemodule may ask you questions like "Where is enum 'xyz ' declared?".
For the first time, cancel out of this dialog after taking down the
enum (or class or prop) name. After you've created all the suites look
for these codes, in the suites generated here and in the standard suites.
If you've found them all run gensuitemodule again and point it to the right
file for each declaration. Gensuitemodule will generate the imports to make the
reference work. <p>
<BLOCKQUOTE>
Time for a sidebar. If you want to re-create
<CODE>Required_Suite.py</CODE> or one of the other standard modules
you should look in <CODE>System Folder:Extensions:Scripting
Additions:Dialects:English Dialect</CODE>, that is where the core
AppleEvent dictionaries live. Also, if you are looking for the
<CODE>Finder_Suite</CODE> interface: don't look in the finder (it has
an old System 7.0 scripting suite), look at the extension <CODE>Finder
Scripting Extension</CODE>. <p>
</BLOCKQUOTE>
Let's glance at the <A
HREF="scripting/Eudora_Suite.py">Eudora_Suite.py</A> just created. You
may want to open Script Editor alongside, and have a look at how it
interprets the dictionary. EudoraSuite.py starts with some
boilerplate, then a big class definition with methods for each
AppleScript Verb, then some small class definitions and then some dictionary
initializations. <p>
The <CODE>Eudora_Suite</CODE> class is the bulk of the code
generated. For each verb it contains a method. Each method knows what
arguments the verb expects, and it makes handy use of the keyword
argument scheme introduced in Python 1.3 to present a palatable
interface to the python programmer. You will see that each method
calls some routines from <CODE>aetools</CODE>, an auxiliary module
living in <CODE>Lib:toolbox</CODE> which contains some other nifty
AppleEvent tools as well. Have a look at it sometime, there is (of
course) no documentation yet. <p>
The other thing you notice is that each method calls
<CODE>self.send</CODE>, but no such method is defined. You will have
to provide it by subclassing or multiple inheritance, as we shall see
later. <p>
After the big class we get a number of little class declarations. These
declarations are for the (appleevent) classes and properties in the suite.
They allow you to create object IDs, which can then be passed to the verbs.
For instance, to get the name of the sender of the first message in mailbox
inbox you would use <code>mailbox("inbox").message(1).sender</code>. It is
also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
which is sometimes needed because these classes don't inherit correctly
from baseclasses, so you may have to use a class or property from another suite. <p>
<blockquote>
There are also some older object specifiers for standard objects in aetools.
You use these in the form <CODE>aetools.Word(10,
aetools.Document(1))</CODE> where the corresponding AppleScript
terminology would be <CODE>word 10 of the first
document</CODE>. Examine the two modules mentioned above along with
the comments at the end of your suite module if you need to create
more than the standard object specifiers.
</blockquote>
Next we get the enumeration dictionaries, which allow you to pass
english names as arguments to verbs, so you don't have to bother with the 4-letter
type code. So, you can say
<CODE><PRE>
eudora.notice(occurrence="mail_arrives")
</PRE></CODE>
instead of the rather more cryptic
<CODE><PRE>
eudora.notice(occurrence="wArv")
</PRE></CODE><p>
Finally, we get the "table of contents" of the module, listing all classes and such
by code, which is used by gensuitemodule. <p>
<H2>Using a Python suite module</H2>
Now that we have created the suite module we can use it in an
application. We do this by creating a class that inherits
<CODE>Eudora_Suite</CODE> and the <CODE>TalkTo</CODE> class from
<CODE>aetools</CODE>. The <CODE>TalkTo</CODE> class is basically a
container for the <CODE>send</CODE> method used by the methods from
the suite classes. <p>
Actually, our class will also inherit <CODE>Required_Suite</CODE>,
because we also need functionality from that suite: the quit
command. Gensuitemodule could have created this completely derived
class for us, since it has access to all information needed to build
the class but unfortunately it does not do so at the moment. All in
all, the heart of our program looks like this:
<CODE><PRE>
import Eudora_Suite, Required_Suite, aetools
class Eudora(Eudora_Suite.Eudora_Suite, Required_Suite.Required_Suite, \
aetools.TalkTo):
pass
</PRE></CODE>
Yes, our class body is <CODE>pass</CODE>, all functionality is already
provided by the base classes, the only thing we have to do is glue it
together in the right way. <p>
Looking at the sourcefile <A
HREF="scripting/testeudora.py">testeudora.py</A> we see that it starts
with some imports. Then we get the class definition
for our main object and a constant giving the signature of Eudora. <p>
This, again, needs a little explanation. There are various ways to
describe to AppleScript which program we want to talk to, but the
easiest one to use (from Python, at least) is creator
signature. Application name would be much nicer, but Python currently
does not have a module that interfaces to the Finder database (which
would allow us to map names to signatures). The other alternative,
<CODE>ChooseApplication</CODE> from the program-to-program toolbox, is
also not available from Python at the moment. <p>
If you specify the application by creator you can specify an optional
<CODE>start</CODE> parameter, which will cause the application to be
started if it is not running. <P>
The main program itself is a wonder of simplicity. We create the
object that talks to Eudora (passing the signature as argument), ask
the user what she wants and call the appropriate method of the talker
object. The use of keyword arguments with the same names as used by
AppleScript make passing the parameters a breeze. <p>
The exception handling does need a few comments, though. Since
AppleScript is basically a connectionless RPC protocol nothing happens
when we create to talker object. Hence, if the destination application
is not running we will not notice until we send our first
command. There is another thing to note about errors returned by
AppleScript calls: <CODE>MacOS.Error</CODE> is raised for
all of the errors that are known to be <CODE>OSErr</CODE>-type errors,
server generated errors raise <CODE>aetools.Error</CODE>. <p>
<H2>Scripting Additions</H2>
If you want to use any of the scripting additions (or OSAXen, in
everyday speech) from a Python program you can use the same method
as for applications, i.e. run <CODE>gensuitemodule</CODE> on the
OSAX (commonly found in <CODE>System Folder:Extensions:Scripting Additions</CODE>
or something similar), define a class which inherits the generated
class and <CODE>aetools.TalkTo</CODE> and instantiate it. The application
signature to use is <CODE>'MACS'</CODE>. <P>
There are two minor points to watch out for when using gensuitemodule
on OSAXen: they appear all to define the class <CODE>System_Object_Suite</CODE>,
and a lot of them have the command set in multiple dialects. You have to
watch out for name conflicts, so, and make sure you select a reasonable dialect
(some of the non-english dialects cause gensuitemodule to generate incorrect
Python code). <P>
That concludes our simple example. Again, let me emphasize that
scripting support in Python is not very complete at the moment, and
the details of how to use AppleEvents will definitely change in the
near future. This will not only fix all the ideosyncracies noted in
this document but also break existing programs, since the current
suite organization will have to change to fix some of the problems.
Still, if you want to experiment with AppleEvents right now: go ahead!
<p>