Updated AppleScript module for Python

JavaScript for Automation (JXA) isn’t the only way to do AppleScripty things in programs written in a language other than AppleScript. The osascript command allows you to run AppleScript within any script, as long as the scripting language has a way to call out to system commands. Shell commands can use backticks or the $() construct. Perl has backticks (which it nicked from the shell), the qx() construct, and the system command. Ruby is very much like Perl, but has %x() instead of qx(). Python has a variety of tools in the subprocess module.

Several years ago, I wrote a short Python module called applescript that used subprocess to run AppleScript code within my Python scripts. This was back when I was using Python 2, which has a slightly different subprocess than today’s Python 3. Even when I switched to using Python 3 (via Anaconda) for most of my programming, I stuck with Python 2 for the scripts that used the applescript module. After all, the scripts worked and Apple wasn’t going to just remove the Python 2 at /usr/bin/python, was it?

Well, Apple did just that in one of Monterey’s point releases, and the various Python 2 scripts I’d been relying on for nearly a decade all broke. So I rewrote the applescript module to work in Python 3, applied the 2to3 command to the old scripts that needed updating, and all’s well with the world again.

Here’s the new version of applescript.py:

python:
 1:  import subprocess
 2:  
 3:  def asrun(ascript):
 4:    "Run the given AppleScript and return the standard output."
 5:  
 6:    osa = subprocess.run(['/usr/bin/osascript', '-'], input=ascript, text=True, capture_output=True)
 7:    if osa.returncode == 0:
 8:      return osa.stdout.rstrip()
 9:    else:
10:      raise ChildProcessError(f'AppleScript: {osa.stderr.rstrip()}')
11:  
12:  
13:  def asquote(astr):
14:    "Return the AppleScript equivalent of the given string."
15:  
16:    astr = astr.replace('"', '" & quote & "')
17:    return '"{}"'.format(astr)

The asquote function, which I seldom use, is about the same as it was. The asrun function takes advantage of the run function in Python 3’s subprocess module to execute the string of AppleScript passed to it.

Here’s an example script, called lastnames, that uses asrun:

python:
 1:  #!/usr/bin/env python
 2:  
 3:  import sys
 4:  from applescript import asrun
 5:  
 6:  cmdTemplate = '''
 7:  tell application "Contacts"
 8:    set myPeople to name of every person whose last name is "{0}"
 9:  end tell
10:  set AppleScript's text item delimiters to linefeed
11:  get myPeople as text
12:  '''
13:  
14:  cmd = cmdTemplate.format(sys.argv[1])
15:  
16:  people = asrun(cmd)
17:  print(people)

The cmdTemplate string is straight AppleScript except for the {0} in Line 8. This is a placeholder that the format function (Line 14) uses to insert the script’s first argument. If the script is run like this from the command line,

lastnames Smith

then “Smith” will be format’s argument, and the output will be a string of the full names of all the Smiths in my Contacts, one line per Smith.

This is, of course, a toy script. The applescript module becomes truly useful in scripts that use Python to assemble disparate pieces of information to build the AppleScript command, which then pulls other information out of an app and presents it back to Python for further processing. I tend to use applescript with Contacts, Calendar, Reminders, and Mail—apps that contain the information I use to run my business but whose AppleScript dictionaries can’t process that information the way I want.