After reading my William Reade's post about a small path library he wrote to tame a build script, I thought I'd share a cool trick that has helped me greatly in the current project - the promise pattern.
The code itself is greatly simple:
class FilePromise(object):
def __init__(self, filename, basepath=None):
self.basepath = basepath
self.filename = filename
def fetch(self):
filepath = self.filename
if self.basepath is not None:
filepath = os.path.join(self.basepath, filepath)
return open(filepath)
The cool thing about this is not the code itself, but the amazing flexibility it offers your code when instead of working with paths and files, you work with promises. All you need to do to support arbitrary data sources is create a class with a filename attribute (which might not make sense in some cases, but is useful for debugging) and a fetch method that will return a file-like object.
I have so far implemented additional promises that:
- Fetch files from a CouchDB database
- Decode embedded images in SVG
- Crop and resize images (well not yet really, but plan to)
Why not use a path element like William does? In my case, I really do not care about the file system, I just care about contents. A promise also can easily be replaced for testing reasons - just create a small MemoryPromise that will return data passed in the constructor as a StringIO.
Why not pass around a file-like object? Two reasons - it's hard to debug (how do you print that?) but mainly, the fetch method might be expensive, and should be run near the point the contents will be used, not at construction time. Also, the intent of the code becomes clearer this way, I think.
The idea is stolen from Cocoa's drag-and-drop system, where a drag source can either supply the drag data directly to the drop target, or provide a promise the target will follow-up, if it's expensive to provide the data up-front. Hopefully Apple will not sue me. In drag-and-drop a lot of times a drag is initiated but not completed (user changes his mind) so this way an expensive calculation is avoided.
Something I haven't yet considered is how to embed this in an async program. Presumably an AsyncPromise fetch will return a deferred that will fire when the data is ready. The client code needs to be expecting this though. I've seen twisted's maybeDeferred for this kind of pattern, I'll follow up if I can do this.
Comments
Comment by d'gou , 2 years, 2 months ago :
Interesting idea.
Is the expensive part opening the file or reading the contents?
Also, what about the possibility of the current directory changing? Relative file paths are dicey...
Comment by Jean-Paul , 2 years, 2 months ago :
Promises have been around for a while, and the conventional usage of the term implies a little more than you've covered here. I suggest starting with <http://en.wikipedia.org/wiki/Promise_...> and going from there.
One of the most interesting things about promises is "promise pipelining" (described on the wikipedia page). Since the approach taken in the "FilePromise" example you gave can't really support pipelining, I feel justified picking nits over terminology here. :)
Comment by Orestis Markou , 2 years, 2 months ago :
I knew I suck at naming stuff, this just confirms it. Though from the domain I stole it from, it's not a deferred, it's really a promise that you'll get some data back if you call a method.
Relative paths are specific to this project I'm working on where all media live inside a specific hard-coded directory so this helps. A fuller implementation would use a different approach. The best I've seen is Cocoa's File object (not sure about the exact name) where the instance is updated to point to the file itself, no matter position and name - try it out by opening a video in quicktime and moving it around or renaming it while it plays.
Jean-Paul - thanks for that link, will read those concepts.
This post is older than 30 days and comments have been turned off.

Comment by ferringb , 2 years, 2 months ago :
'Promise' is kind of a crappy name for it- a deferred is a 'promise', this is just a data source abstraction/interface- at least that's the name I used for it in pkgcore.
Not knocking the concept (it's incredibly useful and long since integral to pkgcores internals to allow arbitrary data backing), just saying the name you're labeling it needs some work ;)