【问题标题】:How do I replace a decorator at runtime?如何在运行时替换装饰器?
【发布时间】:2019-08-22 01:38:06
【问题描述】:

我正在尝试调整 another StackOverflow answer 有条件地应用装饰器以仅需要登录特定环境(最终是 staging 环境,但 development 直到我得到这个工作)。为此,我从以下内容开始

auth = HTTPDigestAuth()

def login_required(dec, condition):
    def decorator(func):
        if not condition:
            return func
        return dec(func)
    return decorator

@bp.route('/auth')
@login_required(auth.login_required, current_app.config['ENV'] != 'development')
def auth_route():
    return current_app.config['ENV']

当我启动服务器时,我收到 RuntimeError: Working outside of application context 错误。在尝试了这个问题的早期版本的一些建议之后,我让 RuntimeError 消失了,但是当我想要的时候,装饰器仍然没有被正确应用。这是当前版本:

def login_required(dec):
    def decorator(func):
        if not os.environ.get('ENV') != 'development':
            return func
        return dec(func)
    return decorator

@bp.route('/auth')
@login_required(auth.login_required)
def auth_route():
    return current_app.config['ENV']

这永远不会返回 auth.login_reqired 函数。它总是让浏览器无需身份验证即可进入。

所以,我尝试将条件更改为

if not os.environ.get('ENV') is not None:

然后身份验证出现。

是的,我在 shell 中做了一个export ENV=development 并用env 命令确认了它。但即便如此,它也没有像我预期的那样读取环境变量。

也许这只是错误的做法?我的最终目标是要求在一个特定环境中进行身份验证。这可能与我正在走的路吗?有可能吗?

【问题讨论】:

  • 您是否尝试将条件放入装饰器中?还要记住,对全局变量和函数名使用相同的名称并不是最好的主意。
  • @KonradSitarz 感谢您指出全局变量/函数名称冲突。我没有注意到。

标签: python flask flask-httpauth


【解决方案1】:

让我从 Flask 的文档中粘贴一些东西

上下文的生命周期 根据需要创建和销毁应用程序上下文。当 Flask 应用程序开始处理请求时,它会推送应用程序上下文和请求上下文。当请求结束时,它会弹出请求上下文,然后是应用程序上下文。通常,应用程序上下文与请求具有相同的生命周期。

现在让我们考虑一下装饰器是如何工作的。它只是一个语法糖see this answer

所以 login_required 装饰器在模块加载时被调用,并且当前应用程序尚不可用,因为它没有处理请求。

我会这样做,将条件移动到装饰器函数(与您的示例相关)。它将在处理请求时被调用,因此您应该可以访问 current_app。

【讨论】:

  • 我很欣赏这个想法,但我确实尝试过(@login_required(auth.login_required)if not current_app.config...)但得到了相同的结果。
  • 这并不完全正确,当第一次加载包含路由的模块时,装饰器是在应用程序上下文之外应用的
【解决方案2】:

current_app 是一个上下文本地代理,仅在请求期间有意义。这意味着您不能在 请求之前使用它,即作为装饰器的一部分。

使用current_app 通常是一种很好的做法,因为 Flask 允许配置多个应用程序。但是,在您的特定情况下,实际上没有必要。例如,以下内容会起作用,因为它直接使用 app 对象而不是 current_app 代理:

from yourpackage import app

@bp.route('/auth')
@login_required(auth.login_required, app.config['ENV'] != 'development')
def auth():
    return current_app.config['ENV']

【讨论】:

  • 我使用了您的想法进行了一些调整(因为我使用的是应用程序工厂),但是虽然它允许应用程序启动而不会崩溃,但装饰器似乎仍然应用得太早,可能在应用程序之前.config 准备好了吗?即使我将ENVdevelopment 进行比较,并且development 是页面上的输出,但当我进行测试...config['ENV'] is not None 时,授权就会出现。我还尝试将其与os.environ.get('ENV') 进行比较,但没有成功。我会更新问题以反映这些新信息。
  • 老实说,如果是我编写这个应用程序,我会在路由中应用逻辑。例如我的身份验证函数可能会向当前用户会话添加一些值,所以如果 current_app.config['ENV'] == 'dev' 我会添加只是乐观地设置会话状态,就好像用户已成功通过身份验证一样,否则我会进行真正的检查以设置会话状态。
  • 您能详细说明一下吗?我还在学习,但我不知道如何向用户展示 HTTP 授权。我已经阅读了flask-httpauth 源代码,但我没有看到它在哪里做的。是否可以在不是装饰器的函数中做到这一点?我假设是这样,但也许你可以提供一个链接,让我可以学习如何做到这一点。
猜你喜欢
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
  • 2023-04-09
  • 2017-08-20
  • 2013-03-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
相关资源
最近更新 更多