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

时间:2019-08-04 20:00:23

标签: python alexa python-decorators

用于编写alexa技能的Python ask-sdk提供了两种编写意图处理程序的方法。一种是从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的回答,装饰器函数必须返回一个函数,但是request_handler()似乎已经是这种情况,正如您在github上看到的那样

装饰器函数确实返回包装函数,因此test_intent_handler应该是函数类型。我想念什么?


编辑

Adam Smith的答案是解决此问题的好方法。

发生这种情况的原因是函数SkillBuilder.request_handler返回一个包装函数,该包装函数不返回任何内容。该包装函数用作处理程序函数的装饰器。由于装饰器的结果已分配给test_intent_handler,并且装饰器(包装器)未返回任何内容,因此结果为NoneType。 因此,在用@sb.request_handler装饰处理程序之后,原来的函数将不再可用。

要解决此问题,包装器函数只需要返回传入的处理函数。遵循亚当·斯密(Adam Smith)的建议,我创建了一个pull request来进行更改,以便“用于Python的Alexa Skills Kit SDK”变得更具可测试性。

1 个答案:

答案 0 :(得分:1)

装饰器的目的是就地修改函数似乎。装饰器不会(没有一些自定义逻辑)保留对调用者的底层函数的引用。但这很好,因为您要测试的不是请求处理程序,而是回调本身。

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