Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Conditionally applying Flask-HTTPAuth's login_required decorator

I'm trying to apply a decorator (Flask-HTTPAuth's login_required) conditionally. If sky_is_blue == True, I want to apply the decorator, if False, to not.

This needs to happen on call, as it could change during the lifetime of the application (actually not so much in practice, but definitely for unit testing purposes, and I'm curious about the cause in any case).

So I wrapped the decorator in a decorator.

Behavior is as expected in the False case (not applying the decorator), but I'm having trouble applying the decorator in the True case. I'm not sure if this is something I've done wrong, or a strange interaction with Flask-HTTPAuth.

The following script demonstrates the problem with two unit tests. test_sky_not_blue passes, but test_sky_blue fails with a stack trace.

from flask import Flask
from flask.ext.httpauth import HTTPBasicAuth
from functools import update_wrapper, wraps
from flask.ext.testing import TestCase
import unittest


app = Flask(__name__)
app.config['TESTING'] = True

sky_is_blue = True
auth = HTTPBasicAuth()


class ConditionalAuth(object):
    def __init__(self, decorator):
        print("ini with {}".format(decorator.__name__))
        self.decorator = decorator
        update_wrapper(self, decorator)

    def __call__(self, func):
        print("__call__: ".format(func.__name__))

        @wraps(func)
        def wrapped(*args, **kwargs):
            print("Wrapped call, function {}".format(func.__name__))
            if sky_is_blue:
                rv = self.decorator(func(*args, **kwargs))
                return rv
            else:
                rv = func(*args, **kwargs)
                return rv
        return wrapped


@app.route('/')
@ConditionalAuth(auth.login_required)
def index():
    """
    Get a token
    """
    return "OK"


class TestSky(TestCase):
    def create_app(self):
        return app

    def test_sky_blue(self):
        global sky_is_blue
        sky_is_blue = True
        response = self.client.get('/')
        self.assert200(response)

    def test_sky_not_blue(self):
        global sky_is_blue
        sky_is_blue = False
        response = self.client.get('/')
        self.assert200(response)


def suite():
    return unittest.makeSuite(TestSky)

if __name__ == '__main__':
    unittest.main(defaultTest='suite')

The full stack trace I get is:

Traceback (most recent call last):
  File "test.py", line 64, in test_sky_blue
    response = self.client.get('/')
  File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 778, in get
    return self.open(*args, **kw)
  File "/usr/local/lib/python2.7/site-packages/flask/testing.py", line 108, in open
    follow_redirects=follow_redirects)
  File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 751, in open
    response = self.run_wsgi_app(environ, buffered=buffered)
  File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 668, in run_wsgi_app
    rv = run_wsgi_app(self.application, environ, buffered=buffered)
  File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 871, in run_wsgi_app
    app_rv = app(environ, start_response)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
    return self.wsgi_app(environ, start_response)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
    response = self.make_response(self.handle_exception(e))
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
    response = self.full_dispatch_request()
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
    rv = self.dispatch_request()
  File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "test.py", line 40, in wrapped
    rv = self.decorator(func(*args, **kwargs))
  File "/usr/local/lib/python2.7/site-packages/flask_httpauth.py", line 48, in login_required
    @wraps(f)
  File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
    setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'str' object has no attribute '__module__'

Tested with Python 2.7.11, Flask-HTTPAuth==2.7.1, Flask==0.10.1, any insights would be greatly appreciated.

like image 404
Alex Forbes Avatar asked Mar 14 '23 07:03

Alex Forbes


1 Answers

It's funny how effective laying a problem out is at helping one solve it.

The problem was the parenthesis in the decorator call:

rv = self.decorator(func(*args, **kwargs))

Changing it to the following fixes it:

rv = self.decorator(func)(*args, **kwargs)

The decorator needs to return a function, but by passing the arguments to func() directly I wasn't giving it a chance to do that.

Breaking it into a separate call would have made this clearer, I think:

decorated_function = self.decorator(func)
return decorated_function(*args, **kwargs))
like image 183
Alex Forbes Avatar answered Apr 08 '23 16:04

Alex Forbes