I think I'd try to structure the gen-friendly interface as an iterator of Futures (using tornado 3.0's Future support):
for future in (yield collection.find().to_iter()):
document = yield future
print document
The catch is that there's kind of a delicate relationship between the iterator and the futures it yields - if the caller doesn't resolve the futures as they come out of the iterator (which must be synchronous), it can't stay ahead to know when to stop. Using "each" as the primitive, it might look something like this (untested):
@future_wrap
def to_iter(self, callback):
to_yield = collections.deque()
results = collections.deque()
def iter():
while to_yield:
yield to_yield.popleft()
def each(document, error):
first_time = bool(results)
if document is not None or error is not None:
future = Future()
to_yield.append(future)
if error is not None:
results.append(functools.partial(future.set_exception, error))
else:
results.append(functools.partial(future.set_result, document))
if first_time:
callback(iter)
else:
# Resolve the Futures with a one-iteration lag so we know whether the
# iterator should return a new future or raise StopIteration.
results.popleft()()
self.each(callback=each)
One other caveat is that if the loop consuming the documents is asynchronous, the each() method might get ahead of it and you'd end up buffering more results in memory. each() needs some sort of control-flow mechanism to prevent this.
-Ben