Processing selected files in a python script

I’ve amended a python script from Dr Drang to fit with my invoice workflow (I use Billings Pro). The script runs correctly from the command line (it extracts information from an invoice PDF and creates emails with MailMate) and so I’d like to create a KM macro to run it.

The script takes command line arguments of the files to be processed; in KM I created a new macro using “For each path in Finder selection”, then added the “Execute shell script” action to the action field, pointing to the script file.

When I run the macro (via a hotkey combination), the screen flashes, but doesn’t go further. I tried the debugger, but see nothing in that either.

Am I missing something fundamental in the way I’m trying to do this?

Thanks,

Des

Do you call the script and pass the argument with something like ~/Desktop/script.py %Variable%path% ?

The way I use to pass arguments to python scripts is via the environment variable:

#!/usr/bin/env python
import os
var = str(os.environ["KMVAR_path"]) # assuming the KM variable is named "path"
1 Like

Thanks for your reply. I was missing the %Variable%path% option to get the variable into the python script. Adding that got me further - the macro window displays the currently selected PDF; however, when I run the script, it pops up an error dialog:

The output of script ‘Unknown’ is:

/bin/sh: null: command not found

Re. the second part of your response - the script is reading command line input - doesn’t %Variable%path% act as the command line string?

Thanks,

Des

Actually I’m not sure the execute action evaluates the variable. That’s why I asked.

But it should be easy enough to edit the script to replace getting the variable from the argument by getting it from the environment variable.

Phillippe,

Thanks - that’s got me further. It is ‘KMVAR_Path’; I’ve unfortunately hit another roadblock:

Syntax Warning: May not be a PDF file (continuing anyway)
Syntax Error: Couldn't read xref table
Syntax Warning: PDF file is damaged - attempting to reconstruct xref table...
Syntax Error: Couldn't find trailer dictionary
Syntax Error: Couldn't read xref table
Traceback (most recent call last):
  File "/Users/des/Dropbox/InvoiceExtract.py", line 52, in <module>
    invText = subprocess.check_output(['/usr/local/bin/pdftotext', '-table', f, '-'])
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 575, in check_output
    raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['/usr/local/bin/pdftotext', '-table', '/', '-']' returned non-zero exit status 1

I suspect that this is because the variable isn’t being passed as a quoted string, so it’s hitting the first space in the pathname and throwing the error.

I’ve looked at how to quote the variable passed into the script, but I’ve reached the end of my little Python knowledge.

Thanks,

Des

You need to show us the action you are using, and preferably the script as well.

Firstly, no where in an Execute Script action can you use "%Variable%Anything%". Those are text tokens, and text tokens are not processed in script text.

If the python script is in a file on disk, then you can use something like this:

Execute Shell Script:
/Users/me/Documents/myscript.py "/Users/me/Documents/my file.pdf"

Or, if the path is in a variable called Path, then:

Execute Shell Script:
/Users/me/Documents/myscript.py "$KMVAR_Path"

Or, if you are inserting the script into the text field directly, then you use python code like @philippe showed to read the environment variable.

I'm using this action:

The script:

#!/usr/bin/python

import os
import os.path
import sys
import applescript
import subprocess

# Templates for the subject and body of the message.
sTemplate = 'Dougan Consulting Group Invoice {0}'
bTemplate = '''Attached is Dougan Consulting Group Invoice {0} for {1}, covering recent work activity. Payment is due {2}.

Please let me know if you have any questions or need further information.


Regards,

Des

'''

# Establish the home directory for later paths.
home = os.environ['HOME']

# Get the selected invoice PDF names from the command line.
pdfs = str(os.environ['KMVAR_Path'])

# Make a new mail message for each invoice.
for f in pdfs:
  f = os.path.abspath(f)

  # Use pdftotext from the xpdf project (http://foolabs.com/xpdf) to extract
  # the text from the PDF as a list of lines.
  invText = subprocess.check_output(['/usr/local/bin/pdftotext', '-table', f, '-'])

  # Extract the invoice number, email address, invoice amount, and due date.
  for line in invText.split('\n'):
    if 'Invoice No:' in line:
      parts = line.split(':')
      invoice = parts[1].split()[0].strip()

    if 'Email Address:' in line:
      email = line.split(':')[1].split()[0].lstrip()
      
    if 'Invoice Amount:' in line:
      parts = line.split(':')
      amount = parts[1].split()[0].strip()

    if 'Due Date:' in line:
      parts = line.split(':')
      due = parts[1].lstrip()

  # Add CC email addresses for specific clients.
  if email == 'xxx@xxxxxx.org':
     ccaddr = 'xxx@xxxxxx.org'
  elif email == 'xxx@xxxxxxx.com':
     ccaddr = 'xxx@xxxxxx.com'
  elif email == 'xxx@xxxxxx.ca':
     ccaddr = 'xxx@xxxxxx.ca'
  else:
     ccaddr = ''   
  
# Construct the subject and body.
  subject = sTemplate.format(invoice)
  body = bTemplate.format(invoice, amount, due)

# Create a MailMate message with the subject, body, and attachment.
  cmd = ['emate', 'mailto', '-t', email, '-c', ccaddr, '-s', subject, f]
  proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
  proc.stdin.write(body)

I've munged the email addresses and removed some of the body of the email, but otherwise it's all there.

Thanks for your help (and for a great utility).

Des

Just adjust your action to execute a script text

python /Users/des/Dropbox/InvoiceExtract.py "$KMVAR_Path"

If the script is executable, you may not need the "/usr/bin/python" at the front. And if you are using a different version of python, you may need to choose it explicitly (remember that the PATH you get inside Keyboard Maestro may be different to the PATH you have configured in Terminal).

I still see this error:

Syntax Warning: May not be a PDF file (continuing anyway)
Syntax Error: Couldn't read xref table
Syntax Warning: PDF file is damaged - attempting to reconstruct xref table...
Syntax Error: Couldn't find trailer dictionary
Syntax Error: Couldn't read xref table
Traceback (most recent call last):
  File "/Users/des/Dropbox/InvoiceExtract.py", line 52, in <module>
    invText = subprocess.check_output(['/usr/local/bin/pdftotext', '-table', f, '-'])
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/subprocess.py", line 575, in check_output
    raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['/usr/local/bin/pdftotext', '-table', '/', '-']' returned non-zero exit status 1

Something in the way the variable is being passed is causing the path to be replaced with “/”, as seen in the pdftotext command. Is there a mechanism to pass it as a POSIX string?

Thanks,

Des

I don't know python, but I would guess your problem lies here:

pdfs = str(os.environ['KMVAR_Path'])
for f in pdfs:

First you set pdfs to a string, and then you do "for f in pdfs". Since pdfs is a string, it is likely iterating through the characters of the string.

I imagine whatever the script was before, it was expecting pdfs to be an array of strings, and you have changed it to just a single string.

I have no idea how you create an array of something in python, though I'm sure its trivial.

Peter is right. Try this:

pdf = str(os.environ['KMVAR_Path'])
pdfs = [pdf]
for f in pdfs:

Thanks to you both - after making Phillippe’s change and adding the full path to the emate command, it’s working now.

Regards,

Des