Fun With PyObjc: Growl Notification Hell

Most of the time PyObjc is wonderful to work in, I love the python libraries and language.

Other times it just makes me angry.

It seems, at least initially, that PyObjc (2.2b3, the default installation with 10.6 snow leopard) does not work with the Growl framework. You can register your application with growl fine, but if you try to push notifications to growl nothing happens.

Here is what I tried initially to post a growl notification in pyobjc:

[code lang=”python”]
GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(u”Notification”, “A description.”, u”Notification”, objc.nil, 0, objc.NO, objc.nil)
[/code]

This does not work, and for a minute I thought I was the only one experiencing this problem (all open source pyobjc projects using growl had almost identical growl notification code) until I found this commit message on github. I found an associated discussion on the growl discussion group and was disheartened that the developer resorted using a command line version of growl to post notifications. However I decided to try one last thing, and checked the className of python objects passed through the objc bridge.

Here is a table of the objc className of different string creation methods in python:

  • unicode(“a string”) or u”a string”: OC_PythonUnicode
  • str(“a string”) or “a string”: OC_PythonString
  • r”a string”: OC_PythonString

Interestingly enough a python string (unicode, str, raw) is not ‘toll free’ bridged like CFString & NSString are. I guess this is to be expected and shouldn’t cause any problems since both OC_PythonString and OC_PythonUnicode are subclasses of NSString, the documentation even states “A Python unicode may be used anywhere a NSString is expected”. It seems that this is not always the case though. I tried one last thing before resorting to rewriting the code in objc:

[code lang=”python”]
GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(NSString.stringWithString_(“Notification”), NSString.stringWithString_(“A description.”), NSString.stringWithString_(“Notification”), objc.nil, 0, objc.NO, objc.nil)
[/code]

And it worked. Note that the created NSString’s don’t get converted by the bridge into a OC_PythonString object, they pass through as regular NSString’s. Note that you also write the above as the more python native:

[code lang=”python”]
GrowlApplicationBridge.notifyWithTitle_description_notificationName_iconData_priority_isSticky_clickContext_(NSString.stringWithString_(“Notification”), NSString.stringWithString_(“A description.”), NSString.stringWithString_(“Notification”), False, 0, False, None)
[/code]

One other interesting that I found is that bridge throws a memory leak warning when running NSString.alloc().initWithString_("Notification"):

[code]
UninitializedDeallocWarning: leaking an uninitialized object of type NSPlaceholderString
[/code]

My only guess is that because initWithString is called instead of init the bridge sees it as a uninitialized object (so I would guess that this warning is falsely thown). Unfortunately the PyObjc documentation is too sparse for me to able to determine what the real cause of the error is.