From 9046ea3033438e7d01fa25a5525095d8e46cd20a Mon Sep 17 00:00:00 2001 From: Dave Jones Date: Tue, 20 Sep 2016 11:08:52 +0100 Subject: [PATCH] Fix #436 Permit partial, partialmethod, and wraps to be used with event handlers --- gpiozero/mixins.py | 17 +++++++++++++---- tests/test_inputs.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/gpiozero/mixins.py b/gpiozero/mixins.py index 62eed6c..069b6da 100644 --- a/gpiozero/mixins.py +++ b/gpiozero/mixins.py @@ -9,7 +9,7 @@ str = type('') import inspect import weakref -from functools import wraps +from functools import wraps, partial from threading import Event from collections import deque from time import time @@ -255,7 +255,16 @@ class EventsMixin(object): return None elif not callable(fn): raise BadEventHandler('value must be None or a callable') - elif inspect.isbuiltin(fn): + # If fn is wrapped with partial (i.e. partial, partialmethod, or wraps + # has been used to produce it) we need to dig out the "real" function + # that's been wrapped along with all the mandatory positional args + # used in the wrapper so we can test the binding + args = () + wrapped_fn = fn + while isinstance(wrapped_fn, partial): + args = wrapped_fn.args + args + wrapped_fn = wrapped_fn.func + if inspect.isbuiltin(wrapped_fn): # We can't introspect the prototype of builtins. In this case we # assume that the builtin has no (mandatory) parameters; this is # the most reasonable assumption on the basis that pre-existing @@ -267,13 +276,13 @@ class EventsMixin(object): # If this works, assume the function is capable of accepting no # parameters try: - inspect.getcallargs(fn) + inspect.getcallargs(wrapped_fn, *args) return fn except TypeError: try: # If the above fails, try binding with a single parameter # (ourselves). If this works, wrap the specified callback - inspect.getcallargs(fn, self) + inspect.getcallargs(wrapped_fn, *(args + (self,))) @wraps(fn) def wrapper(): return fn(self) diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 41a8e56..85e26a5 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -10,6 +10,7 @@ str = type('') import sys import pytest from threading import Event +from functools import partial from gpiozero.pins.mock import ( MockPin, @@ -79,6 +80,20 @@ def test_input_event_deactivated(): pin.drive_low() assert event.is_set() +def test_input_partial_callback(): + event = Event() + pin = MockPin(2) + def foo(a, b): + event.set() + return a + b + bar = partial(foo, 1) + baz = partial(bar, 2) + with DigitalInputDevice(pin) as device: + device.when_activated = baz + assert not event.is_set() + pin.drive_high() + assert event.is_set() + def test_input_wait_active(): pin = MockPin(2) with DigitalInputDevice(pin) as device: