Rewritten, clarified, corrected and cleaned up by Michael J. Barber.

This commit is contained in:
Jack Jansen 2002-01-25 15:06:19 +00:00
parent b2ecc2c6c8
commit c7a7d2d4e8
1 changed files with 298 additions and 141 deletions

View File

@ -1,207 +1,364 @@
<HTML><HEAD><TITLE>Using Open Scripting Extension from Python</TITLE></HEAD>
<BODY>
<H1>Using Open Scripting Extension from Python</H1>
<HR>
<!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html><head><title>Using the Open Scripting Architecture from Python</title></head>
<body>
<h1>Using the Open Scripting Architecture from Python</h1>
<hr>
OSA support in Python is still not 100% complete, but
<p>OSA support in Python is still not 100% complete, but
there is already enough in place to allow you to do some nifty things
to other programs from your python program. <P>
with 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>
<p>
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>
&#8220;AppleScript Dictionary,&#8221; generate a Python interface package from
the dictionary, and use that package to control the application.
The application we are going to script is Disk Copy, Apple's standard
utility for making copies of floppies, creating files that are mountable
as disk images, etc. <p>
as disk images, etc.
Because we want
to concentrate on the OSA details, we won&#8217;t bother with a real
user-interface for our application. </p>
<H2>Python OSA architecture</H2>
Open Scripting suites and inheritance can be modelled rather nicely with
with Python packages, so for each application we want to script we generate
a package. Each suite defined in the application becomes a module in the
<p>
<em>When we say &#8220;AppleScript&#8221; in this document we actually mean
&#8220;the Open Scripting Architecture.&#8221; There is nothing
AppleScript-specific in the Python implementation. Most of this document
focuses on the classic Mac OS; <a href="#osx">Mac OS X</a> users have some
additional tools.</em>
</p>
<h2>Python OSA architecture</h2>
<p>Open Scripting suites and inheritance can be modelled rather nicely
with Python packages, so we generate
a package for each application we want to script. Each suite defined in
the application becomes a module in the
package, and the package main module imports everything from all the
submodules and glues all the classes (Python terminology, OSA terminology is
events, AppleScript terminology is verbs) together. <p>
submodules and glues together all the classes (in Python terminology&#8212;
events in OSA terminology or verbs in AppleScript terminology). </p>
<p>
A suite in an OSA application can extend the functionality of a standard
suite, and this is implemented in Python by importing everything from the
module that implements the standard suite and overriding anything that has
been extended. The standard suites live in the StdSuite package. <p>
suite. This is implemented in Python by importing everything from the
module that implements the standard suites and overriding anything that has
been extended. The standard suites live in the StdSuite package. </p>
This all sounds complicated, and you can do strange and wondrous things
with it once you fully understand it, but the good news is that simple
scripting is actually pretty simple. <p>
<p>
This all sounds complicated, but the good news is that basic
scripting is actually pretty simple. You can do strange and wondrous things
with OSA scripting once you fully understand it. </p>
<H2>Creating the Python interface module</H2>
<h2>Creating the Python interface package</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
Disk Copy executable. <p>
Next it wants a folder where it will store the package it is going to generate.
<p>There is a tool in the standard distribution that can automatically
generate the interface packages. This tool is called
<code>gensuitemodule.py</code>, and lives in <code>Mac:scripts</code>.
It looks through a file
for an &#8216;AETE&#8217; or &#8216;AEUT&#8217; resource,
the internal representation of the
AppleScript dictionary, and parses the resource to generate the suite
modules.
When we start <code>gensuitemodule</code>, it asks us for an input file;
for our example,
we point it to the Disk Copy executable. </p>
<p>
Next, <code>gensuitemodule</code> wants a folder where it will store the
package it is going to generate.
Note that this is the package folder, not the parent folder, so we
navigate to <code>Python:Mac:Demo:applescript</code>, create a folder
<code>Disk_Copy</code> and select that. <p>
<code>Disk_Copy</code>, and select that. </p>
Next it wants the folder from which it should import the standard suites. Here
you always select <code>Python:Mac:Lib:lib-scriptpackages</code>. (There is
<p>
We next specify the folder from which <code>gensuitemodule</code>
should import the standard suites. Here,
we always select <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>. (There is
one exception to this rule: when you are generating <code>StdSuites</code> itself
you select <code>cancel</code>, for obvious reasons). <p>
<!-- you select <code>cancel</code>.) -->
you select <code>_builtinSuites</code>.)
</p>
<p>
It starts parsing the AETE resource, and for
each AppleEvent suite it finds it prompts us for the filename of the
each AppleEvent suite it finds, <code>gensuitemodule.py</code>
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. <p>
module&#8212;you don't want to clutter up, say, the
Eudora folder
<!--Why Eudora folder? Was there an earlier example featuring Eudora?
Added "say" to make it seem less specific.-->
with your python
interfaces. If you want to skip a suite, press <code>cancel</code> and the process
continues with the next suite. </p>
Gensuitemodule may ask you questions like "Where is enum 'xyz ' declared?".
This is either due to a misunderstanding on my part or (rather too common)
<h3>Summary</h3>
<ol>
<li>Run <code>gensuitemodule</code>.</li>
<li>Select the application (or OSAX) for which you would like a Python interface.</li>
<li>Select the package folder where the interface modules should be
stored.</li>
<li>Specify the folder <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>
<!-- to import the standard suites (or <code>cancel</code> if you are -->
to import the standard suites (or <code>_builtinSuites</code> if you are
generating <code>StdSuites</code> itself). </li>
<li>Save the generated suites (use <code>cancel</code> to skip a suite).</li>
</ol>
<h3>Notes</h3>
<ul>
<li>The interface package may need some editing by hand. For example,
<code>gensuitemodule</code> does not handle all Python reserved words, so
if
one of the AppleScript verbs is a Python reserved word, a <code>SyntaxError</code>
may be raised when the package is imported.
Simply rename the class into something acceptable, if this happens;
take a look at how the
<code>print</code> verb is handled (automatically by <code>gensuitemodule</code>)
in the standard suites. </li>
<li>If you want to re-create the StdSuite modules,
you should look in one of two places. With versions of AppleScript older than 1.4.0
(which first shipped with OS 9.0), you will find the
AEUT resources in <code>System Folder:Extensions:Scripting
Additions:Dialects:English Dialect</code>. For newer versions, you will
find them in <code>System Folder:Extensions:Applescript</code>.
</li>
<li>Since MacPython 2.0, this new structure, with packages
per application and submodules per suite, is used. Older MacPythons had a
single level of modules, with uncertain semantics. With the new structure,
it is possible for programs to override standard suites, as programs often do.
It is a good idea to convert your own old programs to the new scheme.
<!-- , but if you -->
<!-- really want them, the old standard suites are still available in -->
<!-- <code>:Mac:Lib:lib-scripting</code>. -->
<!-- Seems to be gone in MacPython 2.2 -->
</li>
<li><code>Gensuitemodule.py</code> may ask you questions
like &#8220;Where is enum 'xyz ' declared?&#8221;.
This is either due to a misunderstanding on my part or (rather too commonly)
bugs in the AETE resources. Pressing <code>cancel</code> is usually the
right option, it will cause the specific enum not to be treated as an enum
but as a "normal" type. As things like fsspecs and TEXT strings clearly are
not enumerators this is correct. If someone understands what is really going on
here please let me know. <p>
right choice: it will cause the specific enum not to be treated as an enum
but as a &#8220;normal&#8221; type. As things like fsspecs and TEXT strings clearly are
not enumerators, this is correct. If someone understands what is really going on
here, please let me know.</li>
<BLOCKQUOTE>
Time for a sidebar. If you want to re-create the StdSuite modules
you should look in one of two places. On older systems you will find the
AEUT resources in <CODE>System Folder:Extensions:Scripting
Additions:Dialects:English Dialect</CODE>. On newer systems you will
find them in <code>System Folder:Extensions:Applescript</code>. <p>
</BLOCKQUOTE>
</ul>
Let's glance at the <A
HREF="applescript/Disk_Copy">Disk_Copy</A> package just created. You
may want to open Script Editor alongside, and have a look at how it
interprets the dictionary. The main package module is in <code>__init__.py</code>
and the only interesting bit is the <code>Disk_Copy</code> class, which
<h2>The Python interface package contents</h2>
<p>
Let&#8217;s glance at the
<a href="applescript/Disk_Copy">Disk_Copy</a> package just created. You
may want to open Script Editor alongside to see how it
interprets the dictionary.
</p>
<p>
The main package module is in <code>__init__.py</code>.
The only interesting bit is the <code>Disk_Copy</code> class, which
includes the event handling classes from the individual suites. It also
inherits <code>aetools.TalkTo</code>, which is a base class that handles all
details on how to start the program and talk to it, and a class variable
<code>_signature</code> which is the default application this class will talk
to (you can override this in various when you instantiate your class, see
to (you can override this in various ways when you instantiate your class, see
<code>aetools.py</code> for details).
<p>
<blockquote>
Let us do another sidebar. Since MacPython 2.0 this new structure, with packages
per application and submodules per suite, is used. Older MacPythons had a
single level of modules, with uncertain semantics. With the new structure
it is possible for programs to override standard suites, as programs often do.
It is a good idea to convert your own old programs to the new scheme, but if you
really want the old standard suites are still available in
<code>:Mac:Lib:lib-scripting</code>.
</blockquote>
</p>
<p>
The <a href="applescript/Disk_Copy/Special_Events.py">Special_Events</a>
module is a nice example of a suite module.
The <CODE>Special_Events_Events</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 keyword
The <code>Special_Events_Events</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 use of keyword
arguments 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>
interface to the python programmer.
The other thing you notice is that each method calls
<CODE>self.send</CODE>, this comes from the <code>aetools.TalkTo</code> baseclass. <p>
Notice that each method
calls some routines from <code>aetools</code>, an auxiliary module
living in <code>Mac:Lib</code>.
The other thing to notice is that each method calls
<code>self.send</code>. This comes from the <code>aetools.TalkTo</code>
baseclass. </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.
<p>
After the big class, there are 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
For instance,
<!--Is this for Eudora again? I'll assume so...-->
when scripting the popular email program Eudora,
you would use <code>mailbox("inbox").message(1).sender</code>
to get the name of the sender of the first message in mailbox
inbox. It is
also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
which is sometimes needed because these classes don't always 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>
which is sometimes needed because these classes don&#8217;t always inherit correctly
from baseclasses, so you may have to use a class or property from another
suite. </p>
<p>
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>
<code>
diskcopy.create(..., filesystem="Mac OS Standard")
</PRE></CODE>
as it is called in Script Editor, in stead of the cryptic lowlevel
<CODE><PRE>
</code>
as it is called in Script Editor, instead of the cryptic lowlevel
<code>
diskcopy.create(..., filesystem="Fhfs")
</PRE></CODE><p>
</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>
<p>
Finally, we get the &#8220;table of contents&#8221; of the module, listing all
classes and such
by code, which is used by <code>gensuitemodule</code>.
<!--
| Not sure I understand. Is the code used by gensuitemodule, or is the TOC
| module used by gensuitemodule?
-->
</p>
<H2>Using a Python suite module</H2>
<h3>Notes</h3>
Now that we have created the suite module we can use it in a Python script.
<ul>
<li>The <code>aetools</code> module contains some other nifty
AppleEvent tools as well. Have a look at it sometime, there is (of
course) no documentation yet.
</li>
<li>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
<code>aetools</code> and <code>aetools.TalkTo</code>
along with
the comments at the end of your suite module if you need to create
more than the standard object specifiers.
</li>
</ul>
<h2>Using a Python suite module</h2>
<p>
Now that we have created the suite module, we can use it in a Python script.
In older MacPython distributions this used to be a rather
complicated affair, but with the package scheme and with the application signature
known by the package it is very simple: you import the package and instantiate
the class, as
<CODE><PRE>
the class, e.g.
<code>
talker = Disk_Copy.Disk_Copy(start=1)
</PRE></CODE>
You will usually specify the start=1: it will run the application if it is
not already running. You may want to omit it if you want to talk to the application
only if it is already running, or if the application is something like the Finder. <p>
</code>
You will usually specify the <code>start=1</code>: it will run the application if it is
not already running.
You may want to omit it if you want to talk to the application
only if it is already running, or if the application is something like the Finder.
Another way to ensure that the application is running is to call <code>talker.start()</code>.
</p>
Looking at the sourcefile <A
HREF="applescript/makedisk.py">makedisk.py</A> we see that it starts
with some imports.
<p>
Looking at the sourcefile <a
href="applescript/makedisk.py">makedisk.py</a>, we see that it starts
with some imports. Naturally, one of these is the Python interface to Disk
Copy.</p>
The main program itself is a wonder of simplicity. We create the
object that talks to Disk Copy, creates a disk and mounts it. <p>
<p>
The main program itself is a wonder of simplicity: we create the
object (<code>talker</code>) that talks to Disk Copy,
create a disk, and mount it. The bulk of
the work is done by <code>talker</code> and the Python interface package we
just created.</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>
<p>
The exception handling does warrant a few comments, though. Since
AppleScript is basically a connectionless RPC protocol,
nothing happens
when we create the <code>talker</code> object. Hence, if the destination application
is not running, we will not notice until we send our first
command (avoid this as described above). 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,
while
server generated errors raise <code>aetools.Error</code>. </p>
<H2>Scripting Additions</H2>
<h2>Scripting Additions</h2>
<p>
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>
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:Scripting Additions</code>
or something similar). There is one minor gotcha: the application
signature to use is <CODE>'MACS'</CODE>. <P>
signature to use is <code>MACS</code>. You will need to edit the main class
in the <code>__init__.py</code> file of the created package and change the value
of <code>_signature</code> to <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>,
<p>
There are two minor points to watch out for when using <code>gensuitemodule</code>
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>
watch out for name conflicts and make sure you select a reasonable dialect
(some of the non-English dialects cause <code>gensuitemodule</code> to generate incorrect
Python code). </p>
<H2>Further Reading</H2>
Despite these difficulties, OSAXen offer a lot of possibilities. Take a
look at some of the OSAXen in the Scripting Additions folder, or
<A HREF="http://www.osaxen.com/index.php">download</A> some from the net.
If you want to look at more involved examples of applescripting look at the standard
<h2>Further Reading</h2>
<p>
If you want to look at more involved examples of applescripting, look at the standard
modules <code>findertools</code> and <code>nsremote</code>, or (possibly better, as it
is more involved) <code>fullbuild</code> from the Mac:scripts folder.
is more involved) <code>fullbuild</code> from the <code>Mac:scripts</code> folder.
</p>
<h2><a name="alternatives">Alternatives</a></h2>
<h3><a name="osx">Mac OS X</a></h3>
<p>
Under Mac OS X, the above still works, but with some new difficulties.
The application package structure can hide the &#8216;AETE&#8217; or &#8216;AEUT&#8217; resource
from <code>gensuitemodule</code>, so that, for example, it cannot generate an OSA interface to
iTunes.
</p>
<p>
One alternative is available through the Unix command line version of python.
Apple has provided the <code>osacompile</code> and <code>osascript</code> tools,
which can be used to compile and execute scripts written in OSA languages. See the
man pages for more details.
</p>
</body>
</html>