Created
December 3, 2013 08:08
-
-
Save numberoverzero/7765668 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import types | |
import six | |
import sys | |
def handler(func): | |
''' | |
Decorator for creating handlers out of generator functions | |
Similar in usage to the @contextmanager decorator | |
Typical usage: | |
@handler | |
def logger(context): | |
# Before the rest of the handlers execute | |
start_time = time.now() | |
#Process the request | |
yield | |
# Log the request timing | |
end_time = time.now() | |
msg = "Operation {} completed in {} seconds." | |
name = context["operation"].name | |
logger.info(msg.format(name, end_time - start_time)) | |
The rest of the handlers in a chain are executed when control is yielded | |
''' | |
def wrapper(context, next_handler): | |
# Before next handler | |
gen = func(context) | |
# If it's not a generator, don't execute the rest of the chain. | |
# For whatever reason, the handler terminated the request | |
if not isinstance(gen, types.GeneratorType): | |
return | |
try: | |
# `yield` is just permission to continue the chain, don't need value | |
six.advance_iterator(gen) | |
except StopIteration: | |
# If generator didn't yield (or isn't a generator) | |
# then we're not calling the next in the chain | |
return | |
try: | |
next_handler(context) | |
except: | |
# Two notes: | |
# | |
# Why catch here if we're going to throw through the generator? | |
# Because the generator isn't really a generator - it's a | |
# convenience for writing before/after blocks for request handling. | |
# Therefore, we want to let any except/finally/else blocks in the | |
# handler run against whatever was raised in the chain. | |
# | |
# Why catch everything? | |
# Unlikely though it is, the handler may want to do something | |
# with BaseException. It's a little weird that we're injecting | |
# the exception back into the generator, but we don't want to | |
# yield away from the handler, hit an exception down the chain, | |
# and bail from this helper, not letting the handler we're | |
# wrapping process cleanup from the exception. | |
instance = sys.exc_info()[1] | |
gen.throw(instance) | |
try: | |
six.advance_iterator(gen) | |
except StopIteration: | |
# Expected - nothing to yield | |
return | |
else: | |
# Found another yield, which doesn't make sense | |
raise RuntimeError("handler didn't stop") | |
return wrapper |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment