Aspects and decorators

I have been using Aspyct in my game to do logging and have been really happy with it. Recently I wanted to try and make executable version of my game by using Py2Exe. For some reason, Py2Exe did not package Aspyct alongside with rest of the code, no matter what I tried, so I had to come up with another way of handling logging.

Aspyct is based on decorators. You inherit Aspect class and override methods that define what happens before the call, after the call and when exception is thrown. Writing simple decorator that does some of this was not too easy (I didn’t really need all the features Aspyct offers):

def logged(fn):
    """
    Decorator to perform logging
    """
    logger_name = str(fn)
    logger = logging.getLogger(logger_name)

    def log(*args, **kwargs):
        """
        Log function call
        """
        call_message = ' '.join([logger_name,
                                 'call',
                                 ':',
                                 str(args),
                                 str(kwargs)])

        logger.debug(call_message)

        result = fn(*args, **kwargs)

        result_message = ' '.join([logger_name,
                                   'return',
                                   ':',
                                   str(result)])

        logger.debug(result_message)

        return result

    return log

Using the logged decorator is simple:

@logged
def receive_event(self, event):
    ...

When receive_event function is defined, logged is evaluated. It will construct a new function called log, which can take any number of parameters. This new function is then wrapped around receive_event function, essentially resulting following construct:

def receive_event(self, event):
    ...
receive_event = log(receive_event)

Another example where I find decorators really shine is in testing:

def observed(fn):
    """
    Decorator to inject observer
    """
    def observe(*args, **kwargs):
        """
        Inject observer
        """
        context = args[0]

        if not hasattr(context, 'observer'):
            context.observer = EventListener()

            if not hasattr(context, 'model'):
                context.model = Model()

            context.model.register_event_listener(context.observer)

        return fn(*args, **kwargs)

    return observe

I’m using decorator to inject test helpers into context being used in behave. This way when I want to examine what kind of events have been raised, I don’t have to repeat the same code multiple times:

@given(u'{character_name} is Adventurer')
@observed
def impl(context, character_name):
    if not hasattr(context, 'characters'):
        context.characters = []
    new_character = Adventurer()
    new_character.name = character_name
    new_character.model = context.model
    context.characters.append(new_character)

When ever there is an Adventurer present in a test, there will be an Observer that will record all the events raised.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s