Re: [Pyobjc-dev] twisted and deferred
Brought to you by:
ronaldoussoren
From: David B. <db3...@gm...> - 2007-11-08 21:01:30
|
Antonio <str...@ti...> writes: > hi all, > > I'm trying to use Twisted Perspective Broker with pyobjc ... the server > run on a Linux server, and the Linux client work very well ... > > I don't understand HOW use the deferred and tthe reactor with PyObjC ... > can someone help me or give me a link to a tutorial ? I'm running a PB-based client/server app (both on OS X at the moment) that works very nicely with PyObjC. I've been using PyObjC 1.4 (from source) with Python 2.5.1 and Twisted 2.5.0. I originally started with Bob's blog at: http://bob.pythonmac.org/archives/2005/04/17/twisted-and-foreign-event-loops/ and the example WebServicesTool application in the PyObjc Examples directory. (Note that I had to correct its use of "threadedselectreactor" to the current (in Twisted 2.5) "_threadedselect") >From your posted code, I think you're on the right track. I think the only critical issue you have is in trying to use the default reactor rather than the _threadedselect version. (I'm assuming that MyObject is defined via IB in MainMenu.nib as your application class delegate) I have found that I needed to improve the sample applicationShouldTerminate: handling to permit a system shutdown if the application is still running (by avoiding the use of NSTerminateCancel/False). By using NSTerminateFinal, and an extra reactor shutdown callback, I can let the reactor finish shutting down and still tell OS X that the application is permitting termination. While setting up your PB client in your constructor for your application delegate is certainly doable in simple cases, I'm generally a fan of keeping the non-GUI elements in separate modules and trying to let the GUI use that as business/processing logic. Here's a sample of my server's startup module, server_ui.py. This serves as the main module for the PyObjC version of my server (built with py2app), but the actual server logic is over in a "server" module. The latter is cross-platform portable (no PyObjC/UI code). Note that to avoid delaying the UI startup due to server startup processing, I added an extra delay so that the server startup processing only executes once the reactor is up and running. I do something similar in my client and find that it improves the perceived performance of the UI since it lets all initial UI message delivery/display take place before Twisted based code executes in the first few interleaved reactor executions. For the client case, it also let me postpone importing most of the twisted and other larger application modules (which can be a noticeable delay) while providing a plugin-like status display in the client window as everything is initialized. When server.startup() is executing, it can behave just like any Twisted application (it has no knowledge it is executing in a PyObjC/Cocoa environment). The only action for this application is quitting (via menu or a Shutdown button bound to terminate: on FirstResponder). Note that I need to assign self.version a value during the constructor since it is used as a binding and needs to be present in the delegate instance when the UI is being built from the NIB. Hope this helps point you in the right direction. -- David - - - - - - - - - - - - - - - - - - - - - - - - - import os from AppKit import NSApp, NSAlert, NSImage, NSTerminateLater, NSTerminateNow from PyObjCTools import NibClassBuilder, AppHelper from twisted.internet._threadedselect import install reactor = install() from twisted.internet.error import CannotListenError import server from version import __version__ NibClassBuilder.extractClasses("MainMenu") class PlayApplicationDelegate(NibClassBuilder.AutoBaseClass): # # Bindings: # version - Version string (NSTextField) for main window # status - Status message (NSTextField) for main window # Outlets: # logo - NSImageView reference for logo display in main window # window - NSWindow reference for main window # status = 'Server Initializing' def init(self): self = super(PlayApplicationDelegate, self).init() self.version = __version__ return self def awakeFromNib(self): frozen = getattr(sys, 'frozen', None) if frozen == 'macosx_app': root = os.getenv('RESOURCEPATH') else: root = os.path.dirname(__file__) image_fname = os.path.join(root, 'web', 'images', 'PlayLogo.gif') self.logo_image = NSImage.alloc().initByReferencingFile_(image_fname) self.logo.setImage_(self.logo_image) self.window.center() def _cbStartupComplete(self, value): self.status = 'Server running' def _cbStartupFailure(self, failure): title = 'Initialization failure (server must exit):\n%s\n' % \ failure.getErrorMessage() if failure.check(CannotListenError): title += '\nIs a server already running?\n' dlg = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( title, 'Exit', None, None, failure.getTraceback()) dlg.runModal() # Don't terminate from within the callback chain, as I got things stuck # (probably due to it calling applicationShouldTerminate: synchronously) AppHelper.callLater(0, NSApp().terminate_, self) def run(self): d = server.startup() d.addCallback(self._cbStartupComplete) d.addErrback(self._cbStartupFailure) def applicationShouldTerminate_(self, sender): self.status = 'Server shutting down' if reactor.running: reactor.addSystemEventTrigger( 'after', 'shutdown', NSApp().replyToApplicationShouldTerminate_, True) reactor.addSystemEventTrigger( 'after', 'shutdown', AppHelper.stopEventLoop) reactor.stop() return NSTerminateLater return NSTerminateNow def applicationDidFinishLaunching_(self, aNotification): """Create and display a new connection window """ reactor.interleave(AppHelper.callAfter) reactor.callWhenRunning(self.run) if __name__ == "__main__": AppHelper.runEventLoop() |