Archive for Python

PyGame on Mac OS X with PyOpenGL

As a result of Richard Jones’s talk at OSDC2005, I decided to install PyGame to play around with.

The demos in the talk were slick, even to the point where PyGame was used as the presentation tool. Much more dynamic than PowerPoint.

Unfortunately, install is where a bit of trouble started. There didn’t seem to be a simple explanation as to how to get it functioning on my Mac. The presentation was great, and on a Mac…

Must be possible!

As with most of these things, the joy is in going down blind alleys, banging your head against (several) walls and making discoveries. The only way to truly learn something. Unfortunately, the pain can’t be totally avoided. Without it, there were no mistakes. No mistakes lead to no need to learn. Or so I kept telling myself.

After several rounds with the wall at the end of the alley, some libraries I built. Some refused to build. Some I later discovered binary packages for.

I’ve included a bunch of links below that point to the required packages. It should be relatively easy to grab everything and have a working PyGame installation.

  1. Python 2.4
  2. PyObjC
  3. Python Numeric
  4. Python Image Library (PIL)
  5. SDL - C library for user handling
  6. SDL_ttf - C library for TrueType fonts
  7. SDL_image - C library for image handling
  8. SDL_mixer - C library for sound
  9. SMPEG - C based Mpeg and MP3 library. (Checkout smpeg from CVS; autoconf; automake; ./configure; make; create framework. )
  10. PyGame
  11. PySDL is now in PyGame - Python binding for SDL

Optional packages:

  1. PyOpenGL - OpenGL bindings for Python
  2. FreeType - TrueType font library, listed as a dependency of SDL_ttf but I have no issues without it (yet?).

If it helps, my /Library/Frameworks looks like:


% ls -d [Ss][^t]* Python.framework
Python.framework/       SDL_mixer.framework/
SDL.framework/          SDL_ttf.framework/
SDL_image.framework/    smpeg.framework/

Useful things are now available in /Developer/Python/pygame, with /Developer/Python/pygame/Examples providing a bunch of examples to test your installation.

A very big grin spread over my face watching the cube rotate from typing:


% python /Developer/Python/pygame/Examples/glcube.py

MochiKit Intro Screencast posted!

If you have ever tried JavaScript programming, you know how painful it is. You can not help but be impressed by Bob Ippolito’s work on bringing MochiKit together. Useful on its own, and extremely good as part of TurboGears. To quote:

MochiKit makes JavaScript suck less

MochiKit is a highly documented and well tested, suite of JavaScript libraries that will help you get shit done, fast. We took all the good ideas we could find from our Python, Objective-C, etc. experience and adapted it to the crazy world of JavaScript.

Watch the screencast to see how easy JavaScript programming can be.

Showing a NSSavePanel as a sheet

Using a sheet to display a save panel adds for a significant user interface improvement. Based on the simplicity of a range of other Cocoa features, I was surprised at how tricky it was to get working.

The issues had nothing to do with Cocoa per-se, but with the PyObjC bridge.

Creating the panel and creating the sheet is as follows:


sp = NSSavePanel.savePanel()
sp.setRequiredFileType_('ics')
sp.setNameFieldLabel_('Export As:')

sp.beginSheetForDirectory_file_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
    userDocumentFolder(),'default.ics',
    NSApp().mainWindow(),self,
    'didEndSheet:returnCode:contextInfo:',0)

The tricky bit is the asynchronous callback, which is passed in as an Objective C selector: 'didEndSheet:returnCode:contextInfo:'

In Python terms, the following method does the trick:


def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, info):
    if returnCode == NSCancelButton:
        return

    # Save the file returned by sheet.filename()
    print sheet.filename()

I only had one slight problem. The application crashed when the user clicked a button on the sheet.

The issue is that the Python bridge has no idea as to the type of the arguments in the method listed as the callback from the sheet. When the callback occurs from Cocoa, it goes searching for a method which matches a specific type signature, as documented in NSSavePanel.

An explicit type signature is required for the method to be discoverable. PyObjC provides a few ways of doing this.

Note: the name of the callback function is up to programmer, which is why the type signature is important.

The Python 2.4 version (using decorators) looks like:


from objc import *

@objc.signature('v@:@ii')
def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, info):
    if returnCode == NSCancelButton:
        return

    # Save the file returned by sheet.filename()

With the standard version like:


from objc import *

def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, info):
    if returnCode == NSCancelButton:
        return

    # Save the file returned by sheet.filename()
    print sheet.filename()
didEndSheet_returnCode_contextInfo_ = objc.selector(
        didEndSheet_returnCode_contextInfo_ , signature='v@:@ii')

With the type information added in, the callback can locate the method on the object and all is well in the world. Files are saved, memory protected, everybody is happy.

Almost.

I had a simple question, where did this type string come from?

Searching on Google had resulted in two different strings. Both worked. However they were quite different.


@objc.signature('v16@4:8@12i16i20')
@objc.signature('v@:@ii')

Somewhere, there had to be an explanation for all of this.

The PyObjC documentation provided some good hints. The intro mentions the different syntax for specifying the type signature, and provides an example.

Using a convenience method on AppHelper, the type information does not need to be explicitly included in the code for the endSheet case:


from PyObjCTools import AppHelper

# Python 2.4
@AppHelper.endSheetMethod
def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, info):
	pass

# Python 2.3
def didEndSheet_returnCode_contextInfo_(self, sheet, returnCode, info):
    pass
didEndSheet_returnCode_contextInfo_ = AppHelper.endSheetMethod(
        didEndSheet_returnCode_contextInfo_)

This third option provides cleaner code, but still didn’t explain where type signatures come from.

(I must have been a difficult child, a solution isn’t enough, the need to know why/how is the motivator)

The key came in reading through the documentation on wrapping an Objective C class. When the documentation refers to a ‘raw Objective-C method signature’, it means what it says. The string appears to be Objective-C’s internal representation of the type signature.

Apple provides documentation on the syntax. (Note: Try here for updated docs)

Probably a bit further into Objective-C than I wanted to delve, but much was learnt along the way. A good abstraction allows you to peek through it. I’m happy to learn that PyObjC provides that flexibility, and that most of the time, it isn’t needed.

Exporting Delicious Library to the web

I’ve been using Delicious Library for a while now and have been incredibly happy with the results. I even managed to figure out which books I had on loan and more importantly discovered I was missing some books from my collection!

The other thing I’d been playing with was including some books on this site that I like. You may have noticed them down the side. There is a bit of PHP code behind the pictures, but I still need to add in the Amazon id number for each book.

My aim is to use Delicious Library to keep track of my physical media, including rating it, and to publish the highest rated media onto the site. Given that Library is basically a thick client built on top of Amazon, I could even include my referrer link, so if anyone buys anything I get a small commission to feed my book buying habits.

Library supports exporting its catalog as a tab separated file. I had hoped for an ‘Export to XML’ option, so it would be simple XSL transform and tada!

Given the Python focus of this site, it didn’t take long before I had a workable script up and running.

The important bits of the script are included below:


from Kid import *
import sys

def normalizeString(str):
    return str.replace('//', '/').replace('/ ', '<br/>').replace('*','<br/>*')

file = open(sys.argv[1], 'rb')
for line in file:
    lines.append(line[:-1])

headings = lines[0].split('t')

books = [dict(zip(headings, normalizeString(line).split('t'))) for line in lines[1:]]

Template(file='template.kid', books=books).serialize()

The Template object is from the Kid template library, which is responsible for generating the resultant XHTML. The template.kid file that I used iterates through the list of dictionaries of books and displays as links the ones that I’ve given a rating of (5/5).


<?xml version='1.0' encoding='utf-8'?>
<html xmlns='http://www.w3.org/1999/xhtml' xmlns:py='http://purl.org/kid/ns#'>
  <head><title>Delicious Library</title></head>
  <body>
    <ul>
      <li py:for='book in books' py:if="book['rating'] == '5'">
        <a py:attrs="href='http://www.amazon.com/exec/obidos/ASIN/' + book['asin'] + '/pseudofish-20?creative=327641&camp=14573&link_code=as1'">
        ${book['title']}
        </a>
      </li>
    </ul>
  </body>
</html>

The end result, is that I can now add books into Library as I purchase and read them, and if I rate the book highly enough then it will turn up on this blog.

Source code and samples are available here. Requires Delicious Library (full or demo) and Kid.

Pimp My Code, Part 5: In Python

Wil Shipley continues his Pimp My Code series with an article reviewing what should have been a very simple method to return a path inside the Application Support folder.

Well worth a read, both for the stylistic as well as the comedic commentary.

From my perspective, it is also a useful source of learning about Cocoa from one of the better programmers.

In Python, the final example looks something like:


from Foundation import *

def applicationSupportFolder():
    return NSSearchPathForDirectoriesInDomains(
        NSApplicationSupportDirectory, NSUserDomainMask, True
    )[0].stringByAppendingPathComponent_(
        NSProcessInfo.processInfo().processName())

Note: The code has been reformatted to (attempt to) fit your screen.