A Better main.py for Python-Cocoa Apps

  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.
  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.
  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.
  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.
  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.
  • : Function split() is deprecated in /home/kourge/kourge.net/modules/filter/filter.module on line 895.

Although Apple describes both Python and Ruby as "first-class citizens" under a Leopard development environment, I feel that Apple meant it more programmatically than documentationally. Sure, both PyObjC and RubyCocoa are mature bridges, but both lack documentation from Apple (you have to rely on the community for gotchas) and RubyCocoa seems to receive more attention than PyObjC when it comes to tutorials for starters.

Moreover, the starting templates for Cocoa-Python apps and Cocoa-Ruby apps are not created equal. For example, while Apple encourages the scripts to be put into Resources, only a Cocoa-Ruby bootstrap file generated by Xcode (called main.rb) would automatically require them. A Cocoa-Python bootstrap file generated by Xcode (called main.py) would not import them, and this problem would manifest itself when you first attempt to do bindings in Interface Builder.

First, observe a generated main.rb:

require 'osx/cocoa'

def rb_main_init
  path = OSX::NSBundle.mainBundle.resourcePath.fileSystemRepresentation
  rbfiles = Dir.entries(path).select {|x| /\.rb\z/ =~ x}
  rbfiles -= [ File.basename(__FILE__) ]
  rbfiles.each do |path|
    require( File.basename(path) )
  end
end

if $0 == __FILE__ then
  rb_main_init
  OSX.NSApplicationMain(0, nil)
end

It automatically requires all Ruby scripts except for itself. While, on the other hand, main.py does not:

#import modules required by application
import objc
import Foundation
import AppKit

from PyObjCTools import AppHelper

# import modules containing classes required to start application and load MainMenu.nib
import BlehAppDelegate

# pass control to AppKit
AppHelper.runEventLoop()

Here, the Delegate class is automatically generated. It can be seen that only the delegate is imported, and nothing else is.

Here is an improvised main.py, taking cue from main.rb:

#import modules required by application
import sys
import os
import objc
import Foundation
import AppKit

from PyObjCTools import AppHelper

# import modules containing classes required to start application and load MainMenu.nib
import PyAveragerAppDelegate
pyfiles = AppKit.NSBundle.mainBundle().resourcePath().fileSystemRepresentation()
pyfiles = [x for x in os.listdir(pyfiles) if x[-3:] == '.py']
if sys.argv[0] in pyfiles: pyfiles.remove(sys.argv[0])
for file in pyfiles:
  __import__(os.path.splitext(file)[0])


# pass control to AppKit
AppHelper.runEventLoop()

Lastly, one interesting aspect that I observed is that Xcode generates a xib file for MainMenu when a Cocoa-Python project is created, but a nib file when a Cocoa-Ruby project is created.