views:

614

answers:

4

I'm having trouble using python function decorators in Google's AppEngine. I'm not that familiar with decorators, but they seem useful in web programming when you might want to force a user to login before executing certain functions.

Anyway, I was following along with a flickr login example here that uses django and decorates a function to require the flickr login. I can't seem to get this type of decorator to work in AppEngine.

I've boiled it down to this:

def require_auth(func):
    def check_auth(*args, **kwargs):
        print "Authenticated."
        return func(*args, **kwargs)
    return check_auth

@require_auth
def content():
    print "Release sensitive data!"

content()

This code works from the commandline, but when I run it in GoogleAppEngineLauncher (OS X), I get the following error:

check_auth() takes at least 1 argument (0 given)

And I'm not really sure why...

EDIT to include actual code: @asperous.us I changed the definition of content() to include variable arguments, is that what you meant? @Alex Martelli, 'print' does work within AppEngine, but still a completely fair criticism. Like I said, I'm trying to use the flickr login from the link above. I tried to put it into my app like so:

def require_flickr_auth(view):
    def protected_view(request,*args, **kwargs):
        if 'token' in request.session:
            token = request.session['token']
            log.info('Getting token from session: %s' % token)
        else:
            token = None
            log.info('No token in session')

        f = flickrapi.FlickrAPI(api_key, api_secret,
                                token=token, store_token=False)

        if token:
            # We have a token, but it might not be valid
            log.info('Verifying token')
            try:
                f.auth_checkToken()
            except flickrapi.FlickrError:
                token = None
                del request.session['token']

        if not token:
            # No valid token, so redirect to Flickr
            log.info('Redirecting user to Flickr to get frob')
            url = f.web_login_url(perms='read')
            print "Redirect to %s" % url

        # If the token is valid, we can call the decorated view.
        log.info('Token is valid')
        return view(request,*args, **kwargs)

    return protected_view

@require_flickr_auth
def content(*args, **kwargs):
    print 'Welcome, oh authenticated user!'

def main():
    print 'Content-Type: text/plain'
    content()

if __name__ == "__main__":
    main()

When I remove the @require_flickr_auth decoration, the string 'Welcome ...' prints out just fine. Otherwise I get a big ugly AppEngine exception page with

type 'exceptions.TypeError': protected_view() takes at least 1 argument (0 given)

at the bottom.

A: 

Decorators were added in python 2.4 (I think), maybe googleapp is using an older version? You can also do:

def content():
    print "Release sensitive data!"
    content = require_auth(content)

it will do the same thing as the decorator, it is just a little more work.

asperous.us
Yeah, or at least the syntax changed or something? At any rate, the AppEngine site claims that the python version is 2.5.2.
Owen
No, GAE uses Python 2.5.
Alex Martelli
You would think that I could do that, right? But content = require_auth(content) gives the same error...
Owen
@Owen, see my answer below...!
Alex Martelli
+1  A: 

@Owen, the "takes at least 1 argument" error suggests you're defining content within a class (i.e. as a method) and not as a bare function as you show -- indeed, how exactly are you trying to execute that code in GAE? I.e. what's your app.yaml &c? If you put your code exactly as you gave it in silly.py and in your app.yaml you have:

handlers:
- url: /silly
  script: silly.py

then when you visit yourapp.appspot.com/silly you will see absolutely nothing on either the browser or the logs (besides the "GET /silly HTTP/1.1" 200 - in the latter of course;-): there is no error but the print doesn't DO anything in particular either. So I have to imagine you tried running code different from what you're showing us...!-)

Alex Martelli
A: 

Why

return func(*args, **kwargs)

wouldn't that execute func and then return the result?

If so, you shouldn't give it any arguments, since it doesn't take any. If you edited content() and added the (*args, **kwargs) arguments onto it, will it work?

asperous.us
+3  A: 

You're calling content() without any arguments, but the decorated version protected_view requires the request argument. Either add the argument to content or remove it from protected_view.

If you're getting that error with your simple version then I'd suspect that content is a class method as Alex suggested. Otherwise it looks like you're telling it to expect at least one argument, then not supplying it any.

jtb
Doh - That's totally it. Thanks!
Owen