Skip to content

Commit 949f934

Browse files
authored
Fix WebhookHandler.add to able to corp with handler function with varargs in its parameters (#276)
## Problem When we add a function with `*args` to the WebhookHandler, WebhookHandler calls the function with 0 args. It causes some problems when we use ordinary wrapped functions with a decorator. An example of this is as follows. ```python import functools from linebot import WebhookHandler def with_log(f): @functools.wraps(f) def _decorator(*args): log(f"args: {args}") return f(*args) return _decorator handler = WebhookHandler(...) @handler.add(...) @with_log def example_handler(event): pass ``` This program raises the following exception when this `example_handler` is called. ``` TypeError: example_handler() missing 1 required positional argument: 'event' ``` This is because of `WebhookHandler.__get_args_count` does not care about `*args` in the parameters. ## Solution Fix `WebhookHandler.__get_args_count` as follows. If the added handler function has `*args` in its parameters, `__invoke_func` passes two args to it.
1 parent 5fb3d27 commit 949f934

File tree

2 files changed

+98
-9
lines changed

2 files changed

+98
-9
lines changed

linebot/webhook.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -253,26 +253,30 @@ def handle(self, body, signature):
253253
if func is None:
254254
LOGGER.info('No handler of ' + key + ' and no default handler')
255255
else:
256-
args_count = self.__get_args_count(func)
257-
if args_count == 0:
258-
func()
259-
elif args_count == 1:
260-
func(event)
261-
else:
262-
func(event, payload.destination)
256+
self.__invoke_func(func, event, payload)
263257

264258
def __add_handler(self, func, event, message=None):
265259
key = self.__get_handler_key(event, message=message)
266260
self._handlers[key] = func
267261

262+
@classmethod
263+
def __invoke_func(cls, func, event, payload):
264+
(has_varargs, args_count) = cls.__get_args_count(func)
265+
if has_varargs or args_count == 2:
266+
func(event, payload.destination)
267+
elif args_count == 1:
268+
func(event)
269+
else:
270+
func()
271+
268272
@staticmethod
269273
def __get_args_count(func):
270274
if PY3:
271275
arg_spec = inspect.getfullargspec(func)
272-
return len(arg_spec.args)
276+
return (arg_spec.varargs is not None, len(arg_spec.args))
273277
else:
274278
arg_spec = inspect.getargspec(func)
275-
return len(arg_spec.args)
279+
return (arg_spec.varargs is not None, len(arg_spec.args))
276280

277281
@staticmethod
278282
def __get_handler_key(event, message=None):

tests/test_webhook.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import os
1818
import unittest
1919
from builtins import open
20+
import inspect
2021

2122
from linebot import (
2223
SignatureValidator, WebhookParser, WebhookHandler
@@ -29,6 +30,7 @@
2930
LocationMessage, StickerMessage, FileMessage,
3031
SourceUser, SourceRoom, SourceGroup,
3132
DeviceLink, DeviceUnlink, ScenarioResult, ActionResult)
33+
from linebot.utils import PY3
3234

3335

3436
class TestSignatureValidator(unittest.TestCase):
@@ -527,5 +529,88 @@ def test_handler(self):
527529
self.handler.handle(body, 'signature')
528530

529531

532+
class TestInvokeWebhookHandler(unittest.TestCase):
533+
def setUp(self):
534+
def wrap(func):
535+
def wrapper(*args):
536+
if PY3:
537+
arg_spec = inspect.getfullargspec(func)
538+
else:
539+
arg_spec = inspect.getargspec(func)
540+
return func(*args[0:len(arg_spec.args)])
541+
return wrapper
542+
543+
def func_with_0_args():
544+
assert True
545+
546+
def func_with_1_arg(arg):
547+
assert arg
548+
549+
def func_with_2_args(arg1, arg2):
550+
assert arg1 and arg2
551+
552+
def func_with_1_arg_with_default(arg=False):
553+
assert arg
554+
555+
def func_with_2_args_with_default(arg1=False, arg2=False):
556+
assert arg1 and arg2
557+
558+
def func_with_1_arg_and_1_arg_with_default(arg1, arg2=False):
559+
assert arg1 and arg2
560+
561+
@wrap
562+
def wrapped_func_with_0_args():
563+
assert True
564+
565+
@wrap
566+
def wrapped_func_with_1_arg(arg):
567+
assert arg
568+
569+
@wrap
570+
def wrapped_func_with_2_args(arg1, arg2):
571+
assert arg1 and arg2
572+
573+
@wrap
574+
def wrapped_func_with_1_arg_with_default(arg=False):
575+
assert arg
576+
577+
@wrap
578+
def wrapped_func_with_2_args_with_default(arg1=False, arg2=False):
579+
assert arg1 and arg2
580+
581+
@wrap
582+
def wrapped_func_with_1_arg_and_1_arg_with_default(
583+
arg1, arg2=False):
584+
assert arg1 and arg2
585+
586+
self.functions = [
587+
func_with_0_args,
588+
func_with_1_arg,
589+
func_with_2_args,
590+
func_with_1_arg_with_default,
591+
func_with_2_args_with_default,
592+
func_with_1_arg_and_1_arg_with_default,
593+
wrapped_func_with_0_args,
594+
wrapped_func_with_1_arg,
595+
wrapped_func_with_2_args,
596+
wrapped_func_with_1_arg_with_default,
597+
wrapped_func_with_2_args_with_default,
598+
wrapped_func_with_1_arg_and_1_arg_with_default,
599+
]
600+
601+
def test_invoke_func(self):
602+
class PayloadMock(object):
603+
def __init__(self):
604+
self.destination = True
605+
606+
event = True
607+
payload = PayloadMock()
608+
609+
for func in self.functions:
610+
WebhookHandler._WebhookHandler__invoke_func(
611+
func, event, payload
612+
)
613+
614+
530615
if __name__ == '__main__':
531616
unittest.main()

0 commit comments

Comments
 (0)