Decorator不会将对象返回给Python中的类方法

时间:2019-08-02 20:29:17

标签: python class flask decorator

因此,我试图将一个对象从装饰器返回到类的方法。基本上,我正在使用装饰器方法的API:

    def token_required(f):
        @wraps(f)
        def decorated(*args, **kwargs):
            token = None

            if 'x-access-token' in request.headers:
                token = request.headers['x-access-token']

            if not token:
                return jsonify({'message' : 'Token is missing!'}), 401

            try:
                data = jwt.decode(token, app.config['SECRET_KEY'])
                current_usuario = Usuarios.query.filter_by(usuarioid = data['usuarioid']).first()
            except:
                return jsonify({'message' : 'Token is invalid!'}), 401

            return f(current_usuario, *args, **kwargs)

        return decorated

问题是我需要在UsuariosView类的方法中返回current_usuario,这是我现在正在尝试的方法:

    class UsuariosView(MethodView):

        @token_required
        def get(self, current_usuario, usuario_id = None):

            if not current_usuario.admin:
                return ({'message' : 'Not admin!'})
            #do stuff

我没有找回current_usuario,所以我无法访问他的属性admin并得到以下错误:

AttributeError:'UsuariosView'对象没有属性'admin'

1 个答案:

答案 0 :(得分:0)

我将其分为以下几类:1)代码为什么失败,2)一个可怕的骇客解决方案,应该解决您当前的问题,以及为什么它将导致未来的问题,3)另一种方法来划分职责,但可能行不通我不知道您的设计细节。

1-实际上,您的装饰器获取current_usuario的值,并将其绑定到您所调用方法的第一个参数。调用该方法时,所有其他参数将在之后分配。这是一个更清楚的例子。

from functools import wraps


def assign_first(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        # do stuff
        first_argument = 10
        return func(first_argument, *args, **kwargs)

    return wrapper


class MyClass(object):
    @assign_first
    def first_method(self, first, second):
        print(f"self: {self}")
        print(f"first: {first}")
        print(f"second: {second}")

输出:

>>> MyClass().first_method(100)
self: 10
first: <__main__.MyClass object at 0x7fdbdebd3400>
second: 100

装饰器将10绑定到方法的第一个参数(即self),因此实例(应绑定到self)现在绑定到第二个参数(称为first)。您收到该错误消息是因为您的UserView对象已绑定到current_usuario,并且没有admin属性。

2)在您的情况下,您可以做一个非常麻烦的想法,将包装器绑定到关键字参数。但是然后,您将需要所有修饰的方法来知道它们需要特定的关键字参数,并且仅使用关键字参数。

def assign_my_kwarg(func):
    @wraps(func)
    def new_func(*args, **kwargs):
        # do stuff
        return func(*args, my_kwarg=3, **kwargs)
    return new_func


class MyClass(object):  
    @assign_my_kwarg
    def second_method(self, *, my_kwarg, second=1):  # a function that only takes keyword arguments
        print(f"self: {self}")
        print(f"my_kwarg: {my_kwarg}")
        print(f"second: {second}")

运行:

>>> MyClass().second_method(second=5)
self: <__main__.MyClass object at 0x7f679eccd400>
my_kwarg: 3
second: 5

因此,您可以对token_required执行相同的操作,返回return f(*args, current_usuario=current_usuario, **kwargs)并将方法签名更改为:def get(self, *, current_usuario, usuario_id = None):。但这将是非常脆弱且非常混乱的。调用该方法并放入current_usuario的任何人都将导致错误,因为它已被分配。关键字拼写有误会导致错误。更改关键字名称将引起无休止的头痛。

3)真正的问题是token_required既在检查令牌,又在获取当前用户。您可以通过拥有一个功能来获取当前用户(引发自定义令牌错误),然后由一个修饰器来处理这些错误,从而避免这种情况。

from functools import wraps


class BadTokenError(ValueError): 
    pass


class MissingTokenError(ValueError): 
    pass


def get_current_user():
    token = get_token()
    try:
        data = jwt.decode(token, app.config['SECRET_KEY'])
        return Usuarios.query.filter_by(usuarioid=data['usuarioid']).first()
    except (SPECIFIC_ERROR, OTHER_SPECIFIC_ERROR):  # do not catch MissingTokenError
        msg = 'bad token'
        raise BadTokenError(msg)


def get_token():
    try:
        token_name = 'x-access-token'
        return request.headers[token_name]
    except KeyError:
        msg = 'missing token'
        raise MissingTokenError(msg)


def catch_token_errors(func):
    @wraps(func)
    def func_with_handler(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except (BadTokenError, MissingTokenError) as error:
            return jsonify({'message': error.args[0]}), 401
    return func_with_handler


class UsuariosView(MethodView):

    @catch_token_errors
    def get(self, usuario_id=None):
        current_usuario = get_current_user()
        if not current_usuario.admin:
            return ({'message': 'Not admin!'})

这可以使职责分得更多,但可能无法满足您的需求。