Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Copying A Python Function’s Signature

DZone's Guide to

Copying A Python Function’s Signature

· Web Dev Zone ·
Free Resource

Learn how error monitoring with Sentry closes the gap between the product team and your customers. With Sentry, you can focus on what you do best: building and scaling software that makes your users’ lives better.

John Hancock's signature

Like all Python programmers, I’m writing a minimal blogging platform. In my particular case, I’m building my blog using Tornado, MongoDB, and an experimental MongoDB driver I wrote, which I’ll announce soon. Rather than build an admin UI where I can create, edit, and delete blog posts, I rely on MarsEdit. My blog simply implements the portion of the metaWeblog XML-RPC API that MarsEdit uses. To implement this API I use Josh Marshall’s excellent Tornado-RPC package.

With Tornado-RPC, I declare my particular handlers (e.g., the metaWeblog.getRecentPosts handler), and Tornado-RPC introspects my methods’ signatures to check if they’re receiving the right arguments at run time:

args, varargs, varkw, defaults = inspect.getargspec(func)

 This is fantastic. But my XML-RPC handlers tend to all have similar signatures:

def metaWeblog_newPost(self, blogid, user, password, struct, publish):
    pass

def metaWeblog_editPost(self, postid, user, password, struct, publish):
    pass

def metaWeblog_getPost(self, postid, user, password):
    pass

I want to check that the user and password are correct in each handler method, without duplicating a ton of code. The obvious approach is a decorator:

@auth
def metaWeblog_newPost(self, blogid, user, password, struct, publish):
    pass

def auth(fn):
    argspec = inspect.getargspec(fn)

    @functools.wraps(fn)
    def _auth(*args, **kwargs):
        self = args[0]
        user = args[argspec.args.index('user')]
        password = args[argspec.args.index('password')]
        if not check_authentication(user, password):
            self.result(xmlrpclib.Fault(403, 'Bad login/pass combination.'))
        else:
            return fn(*args, **kwargs)

    return _auth

Simple enough, right? My decorated method checks the user and password, and either returns an authentication fault, or executes the wrapped method.

Problem is, a simple functools.wraps() isn’t enough to fool Tornado-RPC when it inspects my handler methods’ signatures using inspect.getargspec(). functools.wraps() can change a wrapper’s module, name, docstring, and __dict__ to the wrapped function’s values, but it doesn’t change the wrapper’s actual method signature.

Inspired by Mock, I found this solution:

def auth(fn):
    argspec = inspect.getargspec(fn)

    def _auth(*args, **kwargs):
        user = args[argspec.args.index('user')]
        password = args[argspec.args.index('password')]
        if not check_authentication(user, password):
            self.result(xmlrpclib.Fault(403, 'Bad login/pass combination.'))
        else:
            return fn(*args, **kwargs)

    # For tornadorpc to think _auth has the same arguments as fn,
    # functools.wraps() isn't enough.
    formatted_args = inspect.formatargspec(*argspec)
    fndef = 'lambda %s: _auth%s' % (
        formatted_args.lstrip('(').rstrip(')'), formatted_args)

    fake_fn = eval(fndef, {'_auth': _auth})
    return functools.wraps(fn)(fake_fn)

 Yes, eval is evil. But for this case, it’s the only way to create a new wrapper function with the same signature as the wrapped function. My decorator formats a string like:

    lambda self, blogid, user, password, struct, publish:\
        _auth(self, blogid, user, password, struct, publish)

 And evals it to create a lambda. This lambda is the final wrapper. It’s what the @auth decorator returns in lieu of the wrapped function. Now when Tornado-RPC does inspect.getargspec() on the wrapped function to check its arguments, it thinks the wrapper has the proper method signature.

What’s the best way to boost the efficiency of your product team and ship with confidence? Check out this ebook to learn how Sentry's real-time error monitoring helps developers stay in their workflow to fix bugs before the user even knows there’s a problem.

Topics:

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}