/Mac/Demo/applescript.html

http://unladen-swallow.googlecode.com/ · HTML · 362 lines · 289 code · 73 blank · 0 comment · 0 complexity · b72b1f992724680ab08c9a3a7fc10abc MD5 · raw file

  1. <!doctype HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
  2. <html><head><title>Using the Open Scripting Architecture from Python</title></head>
  3. <body>
  4. <h1>Using the Open Scripting Architecture from Python</h1>
  5. <hr>
  6. <p><b>NOTE:</b> this document describes the OSA support that is shipped with
  7. the core python distribution. Most users are better of with the more
  8. userfriendly <a href="http://freespace.virgin.net/hamish.sanderson/appscript.html">appscript library</a>.
  9. <p>OSA support in Python is still not 100% complete, but
  10. there is already enough in place to allow you to do some nifty things
  11. with other programs from your python program. </p>
  12. <p>
  13. In this example, we will look at a scriptable application, extract its
  14. &#8220;AppleScript Dictionary,&#8221; generate a Python interface package from
  15. the dictionary, and use that package to control the application.
  16. The application we are going to script is Disk Copy, Apple's standard
  17. utility for making copies of floppies, creating files that are mountable
  18. as disk images, etc.
  19. Because we want
  20. to concentrate on the OSA details, we won&#8217;t bother with a real
  21. user-interface for our application. </p>
  22. <p>
  23. <em>When we say &#8220;AppleScript&#8221; in this document we actually mean
  24. &#8220;the Open Scripting Architecture.&#8221; There is nothing
  25. AppleScript-specific in the Python implementation. Most of this document
  26. focuses on the classic Mac OS; <a href="#osx">Mac OS X</a> users have some
  27. additional tools.</em>
  28. </p>
  29. <h2>Python OSA architecture</h2>
  30. <p>Open Scripting suites and inheritance can be modelled rather nicely
  31. with Python packages, so we generate
  32. a package for each application we want to script. Each suite defined in
  33. the application becomes a module in the
  34. package, and the package main module imports everything from all the
  35. submodules and glues together all the classes (in Python terminology&#8212;
  36. events in OSA terminology or verbs in AppleScript terminology). </p>
  37. <p>
  38. A suite in an OSA application can extend the functionality of a standard
  39. suite. This is implemented in Python by importing everything from the
  40. module that implements the standard suites and overriding anything that has
  41. been extended. The standard suites live in the StdSuite package. </p>
  42. <p>
  43. This all sounds complicated, but the good news is that basic
  44. scripting is actually pretty simple. You can do strange and wondrous things
  45. with OSA scripting once you fully understand it. </p>
  46. <h2>Creating the Python interface package</h2>
  47. <p>There is a tool in the standard distribution that can automatically
  48. generate the interface packages. This tool is called
  49. <code>gensuitemodule.py</code>, and lives in <code>Mac:scripts</code>.
  50. It looks through a file
  51. for an &#8216;AETE&#8217; or &#8216;AEUT&#8217; resource,
  52. the internal representation of the
  53. AppleScript dictionary, and parses the resource to generate the suite
  54. modules.
  55. When we start <code>gensuitemodule</code>, it asks us for an input file;
  56. for our example,
  57. we point it to the Disk Copy executable. </p>
  58. <p>
  59. Next, <code>gensuitemodule</code> wants a folder where it will store the
  60. package it is going to generate.
  61. Note that this is the package folder, not the parent folder, so we
  62. navigate to <code>Python:Mac:Demo:applescript</code>, create a folder
  63. <code>Disk_Copy</code>, and select that. </p>
  64. <p>
  65. We next specify the folder from which <code>gensuitemodule</code>
  66. should import the standard suites. Here,
  67. we always select <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>. (There is
  68. one exception to this rule: when you are generating <code>StdSuites</code> itself
  69. you select <code>_builtinSuites</code>.)
  70. </p>
  71. <p>
  72. It starts parsing the AETE resource, and for
  73. each AppleEvent suite it finds, <code>gensuitemodule.py</code>
  74. prompts us for the filename of the
  75. resulting python module. Remember to change folders for the first
  76. module&#8212;you don't want to clutter up, say, the
  77. Disk Copy folder
  78. with your python
  79. interfaces. If you want to skip a suite, press <code>cancel</code> and the process
  80. continues with the next suite. </p>
  81. <h3>Summary</h3>
  82. <ol>
  83. <li>Run <code>gensuitemodule</code>.</li>
  84. <li>Select the application (or OSAX) for which you would like a Python interface.</li>
  85. <li>Select the package folder where the interface modules should be
  86. stored.</li>
  87. <li>Specify the folder <code>Python:Mac:Lib:lib-scriptpackages:StdSuites</code>
  88. to import the standard suites (or <code>_builtinSuites</code> if you are
  89. generating <code>StdSuites</code> itself). </li>
  90. <li>Save the generated suites (use <code>cancel</code> to skip a suite).</li>
  91. </ol>
  92. <h3>Notes</h3>
  93. <ul>
  94. <li>The interface package may occasionally need some editing by hand. For example,
  95. <code>gensuitemodule</code> does not handle all Python reserved words, so
  96. if
  97. one of the AppleScript verbs is a Python reserved word, a <code>SyntaxError</code>
  98. may be raised when the package is imported.
  99. Simply rename the class into something acceptable, if this happens;
  100. take a look at how the
  101. <code>print</code> verb is handled (automatically by <code>gensuitemodule</code>)
  102. in the standard suites. But: f you need to edit your package this should be considered a
  103. bug in gensuitemodule, so please report it so it can be fixed in future releases.
  104. </li>
  105. <li>If you want to re-create the StdSuite modules,
  106. you should look in one of two places. With versions of AppleScript older than 1.4.0
  107. (which first shipped with OS 9.0), you will find the
  108. AEUT resources in <code>System Folder:Extensions:Scripting
  109. Additions:Dialects:English Dialect</code>. For newer versions, you will
  110. find them in <code>System Folder:Extensions:Applescript</code>.
  111. </li>
  112. <li>Since MacPython 2.0, this new structure, with packages
  113. per application and submodules per suite, is used. Older MacPythons had a
  114. single level of modules, with uncertain semantics. With the new structure,
  115. it is possible for programs to override standard suites, as programs often do.
  116. </li>
  117. <li><code>Gensuitemodule.py</code> may ask you questions
  118. like &#8220;Where is enum 'xyz ' declared?&#8221;.
  119. This is either due to a misunderstanding on my part or (rather too commonly)
  120. bugs in the AETE resources. Pressing <code>cancel</code> is usually the
  121. right choice: it will cause the specific enum not to be treated as an enum
  122. but as a &#8220;normal&#8221; type. As things like fsspecs and TEXT strings clearly are
  123. not enumerators, this is correct. If someone understands what is really going on
  124. here, please let me know.</li>
  125. </ul>
  126. <h2>The Python interface package contents</h2>
  127. <p>
  128. Let&#8217;s glance at the
  129. <a href="applescript/Disk_Copy">Disk_Copy</a> package just created. You
  130. may want to open Script Editor alongside to see how it
  131. interprets the dictionary.
  132. </p>
  133. <p>
  134. The main package module is in <code>__init__.py</code>.
  135. The only interesting bit is the <code>Disk_Copy</code> class, which
  136. includes the event handling classes from the individual suites. It also
  137. inherits <code>aetools.TalkTo</code>, which is a base class that handles all
  138. details on how to start the program and talk to it, and a class variable
  139. <code>_signature</code> which is the default application this class will talk
  140. to (you can override this in various ways when you instantiate your class, see
  141. <code>aetools.py</code> for details).
  142. </p>
  143. <p>
  144. The <a href="applescript/Disk_Copy/Special_Events.py">Special_Events</a>
  145. module is a nice example of a suite module.
  146. The <code>Special_Events_Events</code> class is the bulk of the code
  147. generated. For each verb, it contains a method. Each method knows what
  148. arguments the verb expects, and it makes use of keyword
  149. arguments to present a palatable
  150. interface to the python programmer.
  151. Notice that each method
  152. calls some routines from <code>aetools</code>, an auxiliary module
  153. living in <code>Mac:Lib</code>.
  154. The other thing to notice is that each method calls
  155. <code>self.send</code>. This comes from the <code>aetools.TalkTo</code>
  156. baseclass. </p>
  157. <p>
  158. After the big class, there are a number of little class declarations. These
  159. declarations are for the (AppleEvent) classes and properties in the suite.
  160. They allow you to create object IDs, which can then be passed to the verbs.
  161. For instance,
  162. when scripting the popular email program Eudora,
  163. you would use <code>mailbox("inbox").message(1).sender</code>
  164. to get the name of the sender of the first message in mailbox
  165. inbox. It is
  166. also possible to specify this as <code>sender(message(1, mailbox("inbox")))</code>,
  167. which is sometimes needed because these classes don&#8217;t always inherit correctly
  168. from baseclasses, so you may have to use a class or property from another
  169. suite. </p>
  170. <p>
  171. Next we get the enumeration dictionaries, which allow you to pass
  172. english names as arguments to verbs, so you don't have to bother with the 4-letter
  173. type code. So, you can say
  174. <code>
  175. diskcopy.create(..., filesystem="Mac OS Standard")
  176. </code>
  177. as it is called in Script Editor, instead of the cryptic lowlevel
  178. <code>
  179. diskcopy.create(..., filesystem="Fhfs")
  180. </code></p>
  181. <p>
  182. Finally, we get the &#8220;table of contents&#8221; of the module, listing all
  183. classes and such
  184. by code, which is used by <code>gensuitemodule</code> itself: if you use this
  185. suite as a base package in a later run this is how it knows what is defined in this
  186. suite, and what the Python names are.
  187. </p>
  188. <h3>Notes</h3>
  189. <ul>
  190. <li>The <code>aetools</code> module contains some other nifty
  191. AppleEvent tools as well. Have a look at it sometime, there is (of
  192. course) no documentation yet.
  193. </li>
  194. <li>There are also some older object specifiers for standard objects in aetools.
  195. You use these in the form <code>aetools.Word(10,
  196. aetools.Document(1))</code>, where the corresponding AppleScript
  197. terminology would be <code>word 10 of the first
  198. document</code>. Examine
  199. <code>aetools</code> and <code>aetools.TalkTo</code>
  200. along with
  201. the comments at the end of your suite module if you need to create
  202. more than the standard object specifiers.
  203. </li>
  204. </ul>
  205. <h2>Using a Python suite module</h2>
  206. <p>
  207. Now that we have created the suite module, we can use it in a Python script.
  208. In older MacPython distributions this used to be a rather
  209. complicated affair, but with the package scheme and with the application signature
  210. known by the package it is very simple: you import the package and instantiate
  211. the class, e.g.
  212. <code>
  213. talker = Disk_Copy.Disk_Copy(start=1)
  214. </code>
  215. You will usually specify the <code>start=1</code>: it will run the application if it is
  216. not already running.
  217. You may want to omit it if you want to talk to the application
  218. only if it is already running, or if the application is something like the Finder.
  219. Another way to ensure that the application is running is to call <code>talker._start()</code>.
  220. </p>
  221. <p>
  222. Looking at the sourcefile <a
  223. href="applescript/makedisk.py">makedisk.py</a>, we see that it starts
  224. with some imports. Naturally, one of these is the Python interface to Disk
  225. Copy.</p>
  226. <p>
  227. The main program itself is a wonder of simplicity: we create the
  228. object (<code>talker</code>) that talks to Disk Copy,
  229. create a disk, and mount it. The bulk of
  230. the work is done by <code>talker</code> and the Python interface package we
  231. just created.</p>
  232. <p>
  233. The exception handling does warrant a few comments, though. Since
  234. AppleScript is basically a connectionless RPC protocol,
  235. nothing happens
  236. when we create the <code>talker</code> object. Hence, if the destination application
  237. is not running, we will not notice until we send our first
  238. command (avoid this as described above). There is another thing to note about errors returned by
  239. AppleScript calls: <code>MacOS.Error</code> is raised for
  240. all of the errors that are known to be <code>OSErr</code>-type errors,
  241. while
  242. server generated errors raise <code>aetools.Error</code>. </p>
  243. <h2>Scripting Additions</h2>
  244. <p>
  245. If you want to use any of the scripting additions (or OSAXen, in
  246. everyday speech) from a Python program, you can use the same method
  247. as for applications, i.e. run <code>gensuitemodule</code> on the
  248. OSAX (commonly found in <code>System Folder:Scripting Additions</code>
  249. or something similar). There is one minor gotcha: the application
  250. signature to use is <code>MACS</code>. You will need to edit the main class
  251. in the <code>__init__.py</code> file of the created package and change the value
  252. of <code>_signature</code> to <code>MACS</code>, or use a subclass to the
  253. same effect.
  254. </p>
  255. <p>
  256. There are two minor points to watch out for when using <code>gensuitemodule</code>
  257. on OSAXen: they appear all to define the class <code>System_Object_Suite</code>,
  258. and a lot of them have the command set in multiple dialects. You have to
  259. watch out for name conflicts and make sure you select a reasonable dialect
  260. (some of the non-English dialects cause <code>gensuitemodule</code> to generate incorrect
  261. Python code). </p>
  262. Despite these difficulties, OSAXen offer a lot of possibilities. Take a
  263. look at some of the OSAXen in the Scripting Additions folder, or
  264. <A HREF="http://www.osaxen.com/index.php">download</A> some from the net.
  265. <h2>Further Reading</h2>
  266. <p>
  267. If you want to look at more involved examples of applescripting, look at the standard
  268. modules <code>findertools</code> and <code>nsremote</code>, or (possibly better, as it
  269. is more involved) <code>fullbuild</code> from the <code>Mac:scripts</code> folder.
  270. </p>
  271. <h2><a name="alternatives">Alternatives</a></h2>
  272. <h3><a name="osx">Mac OS X</a></h3>
  273. <p>
  274. Under Mac OS X, the above still works, but with some new difficulties.
  275. The application package structure can hide the &#8216;AETE&#8217; or
  276. &#8216;AEUT&#8217; resource from <code>gensuitemodule</code>, so that,
  277. for example, it cannot generate an OSA interface to iTunes. Script
  278. Editor gets at the dictionary of such programs using a &#8216;Get
  279. AETE&#8217; AppleEvent, if someone wants to donate code to use the same
  280. method for gensuitemodule: by all means!
  281. </p>
  282. <p>
  283. One alternative is available through the Unix command line version of python.
  284. Apple has provided the <code>osacompile</code> and <code>osascript</code> tools,
  285. which can be used to compile and execute scripts written in OSA languages. See the
  286. man pages for more details.
  287. </p>
  288. </body>
  289. </html>