为 Python 调用 ASK SDK 的修饰请求处理程序

Calling a decorated request handler of the ASK SDK for Python

用于编写alexa技能的Pythonask-sdk提供了两种编写intent handlers的方法。一种是从 AbstractRequestHandler 派生并实现两个函数 can_handle()handle()。另一个使用函数装饰器 (@SkillBuilder.request_handler()).

使用装饰器的第二种方式我无法直接调用处理函数(在单元测试中)。尝试访问解释器显示错误的函数 TypeError: 'NoneType' object is not callable.

以下是意图处理程序和测试代码的最小示例(其工作方式类似于 this github issue 中的建议)。

意图处理程序

sb = SkillBuilder()
# Built-in Intent Handlers
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))
def test_intent_handler(handler_input):
    return "Hello, test handler"

测试函数

def test_intent():
    request = new_request('TestIntent', {})
    # THE FOLLOWING LINE THROWS THE ERROR
    response = test_intent_handler(request)

    expected_response = "Hello, test handler"
    assert response == expected_response

根据 this question, the decorator function has to return a function, but this seems to be the case for request_handler() already as you can see on github

的回答

装饰函数return是一个包装函数,所以test_intent_handler应该是一个函数类型。我错过了什么?


编辑

的答案很好地解决了这个问题。

发生这种情况的原因是函数 SkillBuilder.request_handler return 是一个包装函数,它没有 return 任何东西。此包装函数用作处理函数的装饰器。由于装饰器的结果被分配给 test_intent_handler 而装饰器(包装器)没有 return 任何东西,所以结果是 NoneType。 因此,在用 @sb.request_handler 装饰处理程序后,原始函数不再可访问。

为了解决这个包装函数只需要 return 传入的处理函数。根据 Adam Smith 的建议,我创建了一个 pull request 来改变它,这样 "Alexa Skills Kit SDK for Python" 就可以变得更容易测试。

装饰器的目的是就地表面上修改函数。装饰器不会(没有一些自定义逻辑)将对其底层函数的引用保留给调用者。但这没关系,因为您要测试的不是请求处理程序——它是回调本身。

ask-SDK 不太可能有一些用于编写处理程序单元测试的框架,但如果没有,请为自己保存回调的引用。

# handler code

sb = SkillBuilder()

def _test_intent_handler(handler_input):
    return "Hello, test handler"

# Built-in Intent Handlers
@sb.request_handler(can_handle_func=is_request_type("LaunchRequest"))

test_intent_handler = sb.request_handler(
    can_handle_func=is_request_type("LaunchRequest"))(_test_intent_handler)
# test suite

def test_intent():
    request = new_request('TestIntent', {})
    response = _test_intent_handler(request)

    expected_response = "Hello, test handler"
    assert response == expected_response

如果这让您感到困扰(我不会责怪您 -- 它非常丑陋),您可以编写自己的装饰器来保留我上面提到的自定义逻辑。

import functools

def meta_decorator(dec, *args, **kwargs):
    @functools.wraps(dec)
    def wrapper(f):
        @functools.wraps(f)
        def wrapped(*args, **kwargs):
            return f(*args, **kwargs)
        wrapped._original_function = f
        return dec(*args, **kwargs)(wrapped)
    return wrapper

sb = SkillBuilder()

@meta_decorator(sb.request_handler, can_handle_func=is_request_type("LaunchRequest"))
def test_intent_handler(handler_input):
    return "Hello, test handler"
# Test suite 2

def test_intent():
    request = new_request('Test Intent', {})
    response = test_intent_handler._original_function(request)
    expected_response = "Hello, test handler"
    assert response == expected_response