Python


21
May 12

More Python and Mac Clipboard [Link]

A really nice post about using the Mac clipboard in Python. Specifically, the script adds a nice bit to filter the clipboard content type. The entire site is great.


18
May 12

List Python Packages [Link]

Clark’s script prints the entire list of Python packages installed with easy_install. I don’t know what’s normal, but I feel like maybe I have too many.


15
May 12

Plumbum [Link]

Plumbum is a Python library for cross platform shell commands in Python? I’m not sure when I would use this instead of the Python OS modules. Maybe if I wrote more scripts that were intended to be cross platform. It’s something interesting to play with.

By way of Clark’s Tech Blog.


12
May 12

Upload to Amazon S3 from Dropbox using Hazel

I’ve been fiddling with my Hazel Dropbox to FTP rule lately. But in the middle of it, I received a polite prompt to make it work with Amazon S3 instead. I’m a sucker for learning something new so I made this Python based Hazel rule.

I Installed the boto module for working with Amazon AWS. This is a mature library of methods for doing all sorts of stuff with Amazon AWS. I really only needed a few methods though.

sudo easy_install boto

Then this script goes into a new Hazel rule.

import boto
from boto.s3.connection import S3Connection
import os
import sys
import urllib
from datetime import date, datetime
import subprocess
# This is how Hazel passes in the file path
hazelFilePath = sys.argv[1]
# Obviously, you'll need your own keys
aws_key = 'AWESOME_KEY'
aws_secret = 'SUPER_SECRET_KEY'
# This is where I store my log file for these links. It's a Dropbox file in my NVAlt notes folder
logFilePath = "/Users/weatherh/Dropbox/Notes/Linkin_Logs.txt"
nowTime = str(datetime.now())

# Method to add to clipboard
def setClipboardData(data):
    p = subprocess.Popen(['pbcopy'], stdin=subprocess.PIPE)
    p.stdin.write(data)
    p.stdin.close()
    retcode = p.wait()

# This is the method that does all of the uploading and writing to the log file.
# The method is generic enough to work with any S3 bucket that is passed.
def uploadToS3(localFilePath, S3Bucket):
    fileName = os.path.basename(localFilePath)
    # Determine the current month and year to create the upload path
    today = date.today()
    datePath = today.strftime("/%Y/%m/")
    # Create the URL for the image
    imageLink = 'http://'+urllib.quote(S3Bucket+datePath+fileName)
    # Connect to S3
    s3 = S3Connection(aws_key, aws_secret)
    bucket = s3.get_bucket(S3Bucket)
    key = bucket.new_key(datePath+'/'+fileName)
    key.set_contents_from_filename(localFilePath)
    key.set_acl('public-read')
    logfile = open(logFilePath, "a")

    try:
        # %% encode the file name and append the URL to the log file
        logfile.write(nowTime+'  '+imageLink+'\n')
        setClipboardData(imageLink)
    finally:
        logfile.close()

# How's this for terrible design. The actual body of the script is one line. I'm my own worst enemy.
uploadToS3(hazelFilePath, 'media.macdrifter.com')

This is very similar to the FTP rule. I add a file to a folder and Hazel will upload it to the designated S3 account and add a link to my Link Log file in NVAlt and my clipboard stack. Since the folder I use is in Dropbox, I can add an image or video right to Dropbox and get back an Amazon S3 link.

The Link log looks something like this:

Notes

My S3 bucket name is “media.macdrifter.com” so that I could use it as a subdomain for this site.

I structured the bucket folders like they are in my WordPress content directory. They exist as YEAR/MO, for example “2012/05″. I added a couple of extra lines to get this structure for me. If the folder does not already exist, the new_key method will create it for me.

If a file already exists with the same name as the one being uploaded, this script will overwrite it without warning. You’ve been warned.

It’s actually convenient that files will be overwritten. It means I can browse to the Dropbox folder that Hazel is watching and edit the image directly. Any saved changes are immediately pushed up to Amazon S3. It’s like directly editing images on S3 (with Acorn of course)

I don’t really use Amazon S3 for image hosting. I have no problem hosting the images myself. This was just something that struck me as interesting and someone else might find it useful.

I was quite impressed with the Boto module’s ease of use. Amazon’s S3 was also fairly easy to get going with. Configuring the CName on my DNS was the hardest part of the entire process. If I didn’t care about having a nice looking URL for the hosted images, I could have skipped the CName and gotten on with wasting my time in some other way. I like the nice looking URL’s though.


2
May 12

Automated FTP from Dropbox with Hazel

Macstories.net linked to QuickShot 2.0. It’s an iOS app that can send photos directly to Dropbox. But I was thinking, I would prefer an app that could quickly send images to my FTP server at Macdrifter.com. So I came up with a little Hazel rule to connect Dropbox and my FTP server.

Design Considerations

I want to put these files on my FTP server so that I can use them for posts to this site. That means I need an easy way to get the URL of the image. I previously built an email rule that uploaded the file and returned the link to me in a reply email. That worked really well, but email is not where I write my posts. I wanted the URL somewhere convenient. So I decided to have the resulting links added to a URL log file in my Dropbox Notes folder. NVAlt, WriteRoom and Nebulous Notes all point to my Notes folder.

I knew I would use FTP. I really like Transmit for FTP, but it seemed a little heavy handed for Hazel automation. I decided I needed to use straight Python for the FTP and skip the application scripting.

The Hazel rule is simple. It just looks for a new file it has never matched before. All of the work is done by a Python script.


Hazel Rule

Note that the Shell path is actually invoking Python.

The Script

I learned something new with this Python script, which is generally my goal. I learned about using the Paramiko module for SFTP. Paramiko is primarily a Python module for SSH that also includes some convenient methods for SFTP. I’m not sure if it passes the Drang sniff test but I like it.

To install Paramiko, just use pip:

sudo pip install paramiko

It should also install the pycrypto dependency.

Here’s the entire Python script.

import paramiko
from datetime import date
import os
import sys
import urllib
try:
    # This is how Hazel gets the incoming file path
    localPath = sys.argv[1]
    # Location to write the URL links
    logFilePath = "/Users/weatherh/Dropbox/Notes/Linkin_Logs.txt"
    # Base File Location on the FTP Host to upload the file
    remotePath = "/home/macdrifter/webapps/wp/wp-content/uploads/"
    fileName = os.path.basename(localPath)
    # Just logging paramiko incase of issues
    paramiko.util.log_to_file('/tmp/paramiko.log')
    host = "my_host_name"
    port = 22
    # This will be the base URL path for the file link
    url_Base = "http://www.macdrifter.com/wp-content/uploads/"
    transport = paramiko.Transport((host, port))
    password = "my_super_strong_password"
    username = "my_sftp_user_name"
    # Determine the current month and year to create the upload path
    today = date.today()
    datePath = today.strftime("%Y/%m/")
    # Used to create full remote file path
    remoteFilePath =  remotePath + datePath + fileName
    transport.connect(username = username, password = password)
    sftp = paramiko.SFTPClient.from_transport(transport)
    # Do the deed. Use the SFTP put command
    sftp.put(localPath, remoteFilePath)
    sftp.close()
    transport.close()
    #Open the URL log file for appending
    logfile = open(logFilePath, "a")
    try:
        # %% encode the file name and append the URL to the log file
        logfile.write(url_Base+datePath+urllib.quote(fileName)+'\n')
    finally:
        logfile.close()
except IOError:
    pass

Obviously, you will need to provide your own connection details and file paths. This script is specific to my needs.

The Workflow

There are two ways I am using this. The first is on my Mac. Any file I drag to this Dropbox FTP folder gets uploaded and a link added my link log. It’s amazingly convenient.


Link Log

The second is on iOS. The QuickShot app is cool. I configured it to upload to this Dropbox FTP folder. The file is uploaded to the server and a link silently added to my log file. I can upload any file from an application that supports Dropbox uploads. But QuickShot is pretty convenient.

I’ll also add that it is convenient to have a Dropbox folder filled with the images I have uploaded to my server. It provides a history for me to see what I was thinking while adding screen shots. I take a heck of a lot of screen shots.

Comments

There are a lot of modules imported for this script. sys is needed just to handle the connection with Hazel. os is needed just for getting the file name and writing the log file. Heck, I even import the urllib module just to encode the file name. But you know what? It works and it is fast, so I don’t care. CPU cycles are cheap. Isn’t that the point of these scripting languages?

It’s never a great idea to hard code login credentials. I might change this in the future to use a config file, but I’m not too concerned with the kind of stuff I use this for.

The error handling in the script is pretty lean. Other than the try and except blocks there is none. I should add more. I probably will not.

The FTP put command will overwrite any file with the same name in the same location. That’s by design. If I want to edit a file on my Mac, I can just open it in Acorn or optimize it with ImageOptim and Hazel will reprocess it when I save. The new version is uploaded but the URL link remains the same.

This will work with any file type. That’s cool but a bit scary. If I accidentally drop-in something private, then it will be uploaded to my public FTP host automatically. I’m aware of the potential problems. I’m a big boy. I can handle it.

Future Plans

I’d like to add some automatic image processing. I definitely want to include ImageOptim processing but I would also like to resize any image larger than 650px. It would save a lot of trouble with iPad images. I might need some different rules to provide multiple options for image resolution.

I’m thinking about adding the links to my pasteboard on my Mac. That way I would have the log file and also a running list in my pasteboard.


27
Apr 12

Python Outputty [Link]

I don’t remember how I stumbled across this, but Outputty is a Python module for transforming structured tabular data from one format to another. For example, converting CSV to an HTML table. Just look at this tutorial. Nice.


18
Apr 12

Python FTP [Link]

Congratulations to Dr. Drang on running the only site I follow that is astute enough to celebrate the birthday of FTP properly.


10
Apr 12

Python and P-lists [Link]

I learned five things from this 554 word post by Dr. Drang about determining the default browser on OS X.

  1. There’s a Python library for reading plists
  2. There’s a Python library for creating temp files that auto-delete
  3. The InternetConfig tool is still around in OS X 10.7
  4. Dr. Drang uses AppleScript like I do, as a wrapper for Shell and Python
  5. I invested in learning the right scripting language for the distopian future of AppleScript

5
Apr 12

AppleScript for Python Developers [Link]

A great cheat sheet for trying to understand AppleScript after working with Python.


8
Mar 12

Ignorance, Laziness and Python

Clark raises a concern over the death of AppScript. I have to say, I’ve never bothered with it so it will not dramatically impact me. I try not to use too many external Python modules. That’s not because I’m a super-engineer. It’s because I’m ignorant, lazy and selfish.

I’m ignorant to many of the modules that are available. PyPi is a good resource but the documentation is often lacking (that’s being generous). It takes almost as much time to learn how to do it with standard Python modules as it usually does for me to use the new module.

I’m also too lazy to keep modules up to date when Python versions change. It’s a lot of work to make sure each OS update still works with my hacks. The more modules, the more I have to install and test. It also means I need to document more. I need to leave myself notes in the code. I need to include GitHub sources and occasionally, complete explanations as to how to actually use the module. That’s a lot of work.

Finally, I’m also selfish. My hacks are for me. While I may post them here (and I try to be verbose about how they work), they are ultimately made for me. Macdrifter.com is kind of a diary1 that I happen to share with everyone. I track my little solutions and tricks here. As often as I post, I return here to do a search. That means I’m not big on going out of my way to make the hacks easier for everyone else. If I can use something that I’m already familiar with, I will. If I need a module that requires an install, I will.

I am worried about the future of Python on OS X. As well, I’m worried about the future of any scripting language. I think Apple plans to support Automator and Objective C long term. Those are the two ends of the spectrum. I expect everything in the middle to wither and be cutoff. I have to choose what kind of user I plan to be. Where’d I put that Hillegass book?


  1. My comments about a technical solution and problem are as much for my future self as they are for the rest of you. The astute reader may have noticed I rarely use “you” in my posts. That’s partially because It’s not my place to tell anyone how they should do anything. But really, it’s because I am talking to myself. Welcome to my dementia. Everyone’s invited. 

7
Mar 12

Paste and Match Styles with Exceptions [Link]

It gets better. Clark makes the Match Style command useful with some pyObjC magic.


6
Mar 12

Styled Text on the Clipboard [Link]

Nice work by Clark to manipulate text on the pasteboard via pyObjC.


19
Feb 12

Python at the Prompt

Pyp (short for Pyed Piper) is a project to enable python text processing at the unix prompt. I suck at sed and AWK but I suck less at Python so I think this looks interesting. I wish there was a Windows equivalent to work with CMD commands. If anything could use some help, it’s the Windows CMD interface.

The module supports standard Python string processing but also includes some built in variables for accessing strings from the shell.

The “p” variable processes each line of the shell output while “pp” processes the entire block as one string. There are several interesting examples available through the module home.

Here’s what I did to install the command on my Mac:

  1. Download the file and place it in the /usr/local/bin directory.
  2. Set the permissions to include execute (755 should work)
  3. Have fun

I learned something else interesting while reading about this module. Sony’s Imageworks has several really amazing open source projects. They were all developed to address internal needs and eventually released to the public.


18
Feb 12

Explaining List Comprehensions [Link]

A great explanation of list comprehensions at DZone. I’ve used them several times and the linked Python.org explanation was what I used to understand them. I’m not a mathematician or a Programmer but list comprehensions are pretty cool.


13
Feb 12

Updates to the Simple Dropbox Blogging Engine

I’ve been very happy with the results of my Hazel based blogging engine. 80% of my posts come through this mechanism now.1 In a tribute to Inception this very post was uploaded by this mechanism.

As a quick recap, it allows me to blog using Simplenote or Dropbox.3 Hazel watches my Dropbox “Notes” folder for a file tagged with “post”. Once identified, Hazel copies the file to an archive, processes the MultiMarkdown and posts here at Macdrifter.com.

The system has been working flawlessly, except when I screw around with it. But something that has been frustrating is that my posts must be written with multimarkdown meta data headers. Sometimes I just want a quick post. I also just wanted a more complete system for blogging while moving around. It has been very liberating to blog directly from Simplenote.

Improvements

This new and improved version of the Python script brings the following enhancements:

  1. If no title field is provided, the note title is used for the post
  2. If no category is provided it defaults to “uncategorized”
  3. If a new category is entered then the script creates the new category on Macdrifter.com before posting.
  4. The post id is added to the text file header after it posts.
  5. Added custom growl support. More on this in a future post.

There was a substantial change to the script. I really wanted to stick with the plain XMLRPC module for making posts. However this proved ever-more annoying as I tried to access more WordPress API features. I gave in and switched to the fantastic WordPress-XMLRPC module. I highly recommend this module to my future stubborn self.2

Install the module from PyPi.

I’ve also added better handling for missing meta data. Previously the script would fail when I forgot the meta data. Now it will post with the note title as the post title and use the “uncategorized” category if either piece of meta data is missing from the header.

For the custom Growl support, I’m using the GNTP module for Python(Growl Notification Transport Protocol). This can easily be disabled. For more info on GNTP see the official documentation. It is a great module and works really well. It allows me to send custom growl messages for exceptions as well.

Future

I really wanted to manipulate the OpenMeta tags from within the script. Clark has provided a great template to work off of. However, that’s a lot of over head to add one tag. I’ll wait for Hazel 3.

The next level of complexity for me, is figuring out how to handle post edits. I’d love the ability to go into Nebulous Notes and make a quick fix and have that automatically updated on the site. I’ve mapped out what that might look like, but I’m not happy with the process yet. Having the post id in the meta data is the key to making this viable. When I get more time, I’ll see what comes together.

The Meat

Here’s the guts of the process. It’s Python. It’s not good. I’m really just a hack with patience and a tolerance for failure. This may break and it could wipe out all of your notes or corrupt your WordPress site. I doubt it, but I make no guarantees. Also, if you use, just give me a hat tip. Don’t be a dick.

import markdown
from wordpress_xmlrpc import Client, WordPressPost
from wordpress_xmlrpc.methods.posts import GetRecentPosts, NewPost
from wordpress_xmlrpc import Client, WordPressCategory
from wordpress_xmlrpc.methods.categories import NewCategory, SetPostCategories, GetCategories
import gntp.notifier
import os
import sys

#Connection details for the WordPress xmlrpc connection
wpUrl = 'http://www.macdrifter.com/xmlrpc.php'
wpUser = 'account'
wpPass = 'password'
# Hold the server instance. Needed to extract categories and make post
wpServer = Client(wpUrl, wpUser, wpPass)
postCategoryList = ['uncategorized']
title = ''
uploadCategoryList = []
## For Testing. Uncomment but comment the Hazel specific comment
#filePath = "/Volumes/Macintosh HD 2/Dropbox/Macdrifter/Posts/OmniFocus Do Over.txt"
## For Hazel
filePath = sys.argv[1]

def postToWordPress(title, html, postCategoryList):
    post = WordPressPost()
    post.title = title
    post.description = html
    post.categories = postCategoryList
    post_id = wpServer.call(NewPost(post, True))
    return post_id


def allWordPressCategories():
    categoryList = wpServer.call(GetCategories())
    categoryListLower = []
    # Create a list with just lowercase.
    for cat in categoryList:
        categoryListLower.append(str(cat).lower())
    return categoryListLower


def createNewWordPressCategory(category):
    new_category = WordPressCategory()
    new_category.name = category
    new_category.cat_id = wpServer.call(NewCategory(new_category))
    return new_category.cat_id
   
def growlInitialize():
    growl = gntp.notifier.GrowlNotifier(
    applicationName = "Simple DropBlog",
    notifications = ["New Messages"],
    defaultNotifications = ["New Messages"])
    growl.register()
    return growl
   
try:
    myFile = open(filePath, "r")
    rawText = myFile.read()
    myFile.close()
    fileName = os.path.basename(filePath)
    # Start off using the file name as the post title
    title = os.path.splitext(fileName)[0]
    # Handle the odd characters. Just kill them.
    rawText = rawText.decode('utf-8')
   
    # Process with MD Extras and meta data support
    md = markdown.Markdown(extensions = ['extra', 'meta'])
   
    # Get the html text
    html = md.convert(rawText)

## If post_id exists then already posted. We're done.
## FUTURE: USE POST ID TO UPDATE EDITED POST
    if 'post_id' not in md.Meta:
        # extract the title from the meta dictionary.
        # If there is no title attribute, keep the file name as the post title
        func = getattr(md, "Meta['title']", None)
        if func:
            title = md.Meta['title']
            # title is actualy a list
            title = title[0]
        print title
       
## extract the categories but keep them as a list
        # If no categories exist then the default is uncategorized. Don't bother getting the WP list
        func = getattr(md, "Meta['category']", None)
        if func:
            postCategoryList = md.Meta['category']
            categoryList = allWordPressCategories()
                       
## Handle categories that do not already exist on WordPress blog
            for postCategory in postCategoryList:
                # Test each category term. If it does not exist, create it
                if postCategory.lower() not in categoryList:
                    new_category = createNewWordPressCategory(postCategory)
                    # For testing
                    print "new category id for : "
                    print new_category
       
## Only post if there is a title in the meta data
        if title != '' :
            newPost_id = postToWordPress(title, html, postCategoryList)
            # For testing
            print "new post id: "
            print newPost_id
           
## Append the post_id to the beginning of the file
            #Get the file contents
            myFile = open(filePath, "r")
            myFileContent = myFile.read()
            myFile.close()
            # Write the post id and append the rest of the text
            myFile = open(filePath, "w")
            myFile.write('post_id: ' + newPost_id + '\n' + myFileContent)
            myFile.close()
            postGrowl = growlInitialize()
            postMSG = newPost_id + ": " + title + " Posted to Macdrifter"
            postGrowl.notify(
                noteType = "New Messages",
                title = postMSG,
                description = "The Blog Post was successful",
                icon = None,
                sticky = False,
                priority = 1)
except Exception, e:
    postGrowl = growlInitialize()
    postMSG = "Post Failed!"
    postGrowl.notify(
        noteType = "New Messages",
        title = postMSG,
        description = e,
        icon = None,
        sticky = False,
        priority = 2)

  1. I manually post the Writer Workflows because it represents someone else’s work and I have an obligation not to mess it up. If I write entirely at my Mac then I write in BBEdit and I have another script to post from BBEdit directly. 
  2. Quit being a jerk future-self. Just install a module and get on with your work. Don’t reinvent the wheel. 
  3. If I ever abandon Simplenote or NVAlt then I’ll need a different trigger. As far as I know there are no other iOS apps that can embed an OpenMeta tag in a Dropbox file.