Repository: juju-solutions/charms.reactive

Issue:
Handler args are dropped if called more than once per hook
Because the args are implemented as generators, any time the list is evaluated after the first it ends up being empty, leading to an "missing arg" error:

```
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined Traceback (most recent call last):
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined   File "/var/lib/juju/agents/unit-nn-0/charm/hooks/namenode-cluster-relation-joined", line 19, in <module>
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined     main()
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined   File "/usr/local/lib/python3.4/dist-packages/charms/reactive/__init__.py", line 73, in main
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined     bus.dispatch()
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined   File "/usr/local/lib/python3.4/dist-packages/charms/reactive/bus.py", line 418, in dispatch
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined     _invoke(other_handlers)
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined   File "/usr/local/lib/python3.4/dist-packages/charms/reactive/bus.py", line 401, in _invoke
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined     handler.invoke()
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined   File "/usr/local/lib/python3.4/dist-packages/charms/reactive/bus.py", line 277, in invoke
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined     self._action(*args)
2016-04-12 15:15:51 INFO namenode-cluster-relation-joined TypeError: report_status() missing 1 required positional argument: 'datanode'
```

Discussion:

Patch:
diff --git a/charms/reactive/bus.py b/charms/reactive/bus.py
index 9676244..885e498 100644
--- a/charms/reactive/bus.py
+++ b/charms/reactive/bus.py
@@ -267,7 +267,10 @@ class Handler(object):
         """
         Lazily evaluate the args.
         """
-        return list(chain.from_iterable(self._args))
+        if not hasattr(self, '_args_evaled'):
+            # cache the args in case handler is re-invoked due to states change
+            self._args_evaled = list(chain.from_iterable(self._args))
+        return self._args_evaled
 
     def invoke(self):
         """


Test patch:
diff --git a/tests/test_decorators.py b/tests/test_decorators.py
index 836753a..fdbb512 100644
--- a/tests/test_decorators.py
+++ b/tests/test_decorators.py
@@ -103,6 +103,11 @@ class TestReactiveDecorators(unittest.TestCase):
         action.assert_called_once_with('rel')
         self.assertEqual(reactive.bus.Handler._CONSUMED_STATES, set(['foo', 'bar', 'qux']))
 
+        action.reset_mock()
+        assert handler.test()
+        handler.invoke()
+        action.assert_called_once_with('rel')
+
     @mock.patch.object(reactive.decorators, 'when_all')
     def test_when(self, when_all):
         @reactive.when('foo', 'bar', 'qux')