【问题标题】:Decorator isn't returning the object to the class methods in Python装饰器没有将对象返回给 Python 中的类方法
【发布时间】:2019-12-11 11:18:26
【问题描述】:

所以我试图将一个对象从装饰器返回到我的类的方法。基本上我正在做一个使用装饰器方法的 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

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

    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'

【问题讨论】:

    标签: python class flask decorator


    【解决方案1】:

    我将把它分成,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!'})
    

    这会进一步分散职责,但可能无法满足您的需要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-12
      • 1970-01-01
      • 2012-02-09
      • 2017-03-13
      • 1970-01-01
      • 2021-09-17
      • 2018-07-14
      相关资源
      最近更新 更多