Repository: opensistemas-hub/osbrain
Pull request #63 (2017-03-09 09:46:25)

Implement delayed actions
A self-contained change that could be added to master while we keep making other changes.


Patch:
diff --git a/osbrain/agent.py b/osbrain/agent.py
index 70c14a8..f7891b8 100644
--- a/osbrain/agent.py
+++ b/osbrain/agent.py
@@ -22,6 +22,7 @@ from .common import format_exception
 from .common import unbound_method
 from .common import LogLevel
 from .common import repeat
+from .common import after
 from .address import AgentAddress
 from .address import AgentAddressKind
 from .address import address_to_host_port
@@ -300,6 +301,37 @@ class Agent():
         self._timer[alias] = timer
         return alias
 
+    def after(self, delay, method, *args, alias=None, **kwargs):
+        """
+        Execute an action after a delay.
+
+        Parameters
+        ----------
+        delay : float
+            Execute the action after `delay` seconds.
+        method
+            Method (action) to be executed by the agent.
+        alias : str, default is None
+            An alias for the generated timer.
+        *args : tuple
+            Parameters to pass for the method execution.
+        **kwargs : dict
+            Named parameters to pass for the method execution.
+
+        Returns
+        -------
+        str
+            The timer alias or identifier.
+        """
+        if not isinstance(method, str):
+            method = self.set_method(method)
+        timer = after(delay, self._loopback,
+                      'EXECUTE_METHOD', (method, args, kwargs))
+        if not alias:
+            alias = uuid4().hex
+        self._timer[alias] = timer
+        return alias
+
     def stop_all_timers(self):
         """
         Stop all currently running timers.
diff --git a/osbrain/common.py b/osbrain/common.py
index 576ea16..e6db2fa 100644
--- a/osbrain/common.py
+++ b/osbrain/common.py
@@ -77,6 +77,41 @@ def repeat(interval, action, *args):
             delay = interval - (time.time() - starttime)
             if event.wait(delay):
                 break
+
     Thread(target=loop).start()
     event.stop = event.set
+
+    return event
+
+
+def after(delay, action, *args):
+    """
+    Execute an action after a given number of seconds.
+
+    This function is executed in a separate thread.
+
+    Parameters
+    ----------
+    delay : float
+        Number of seconds to delay the action.
+    action
+        To be taken after the interval.
+    args : tuple, default is ()
+        Arguments for the action.
+
+    Returns
+    -------
+    Event
+        A timer object that can be terminated using the `stop()` method.
+    """
+    event = Event()
+
+    def wait():
+        if event.wait(delay):
+            return
+        action(*args)
+
+    Thread(target=wait).start()
+    event.stop = event.set
+
     return event


Test patch:
diff --git a/osbrain/tests/test_timer.py b/osbrain/tests/test_timer.py
index a44bdd5..4dde675 100644
--- a/osbrain/tests/test_timer.py
+++ b/osbrain/tests/test_timer.py
@@ -8,6 +8,7 @@ from osbrain import run_agent
 from osbrain.common import repeat
 
 from common import nsaddr  # pragma: no flakes
+from common import nsproxy  # pragma: no flakes
 
 
 def set_received(agent, message, topic=None):
@@ -200,3 +201,79 @@ def test_stop_all_timers(nsaddr):
     time.sleep(1)
     assert abs(receiver.get_attr('received') - 20) <= 1
     assert len(sender.list_timers()) == 0
+
+
+def test_timer_after(nsproxy):
+    """
+    Test a timer executed once after a time delay.
+    """
+    def event(agent, number):
+        agent.count += number
+
+    agent = run_agent('agent')
+    agent.set_attr(count=0)
+    # Start timer
+    agent.after(1, event, 2)
+    agent.after(2, event, 1)
+    time.sleep(0.9)
+    assert agent.get_attr('count') == 0
+    time.sleep(0.2)
+    assert agent.get_attr('count') == 2
+    time.sleep(1)
+    assert agent.get_attr('count') == 3
+
+
+def test_timer_after_oop(nsproxy):
+    """
+    Test a timer executed once after a time delay (using OOP).
+    """
+    class Foo(Agent):
+        def on_init(self):
+            self.count = 0
+
+        def event(self, number):
+            self.count += number
+
+        def setup_timer(self, delay, number):
+            self.after(delay, 'event', number)
+
+    agent = run_agent('agent', base=Foo)
+    # Start timer
+    agent.setup_timer(0.5, 1)
+    agent.setup_timer(1.0, 2)
+    time.sleep(0.6)
+    assert agent.get_attr('count') == 1
+    time.sleep(0.5)
+    assert agent.get_attr('count') == 3
+
+
+def test_timer_after_stop_uuid(nsaddr):
+    """
+    Test a timer executed once after a time delay and stopped by its UUID.
+    """
+    def event(agent, number):
+        agent.count += number
+
+    agent = run_agent('agent')
+    agent.set_attr(count=0)
+    # Start timer
+    uuid = agent.after(1, event, 2)
+    agent.stop_timer(uuid)
+    time.sleep(1.1)
+    assert agent.get_attr('count') == 0
+
+
+def test_timer_after_stop_alias(nsaddr):
+    """
+    Test a timer executed once after a time delay and stopped by its alias.
+    """
+    def event(agent, number):
+        agent.count += number
+
+    agent = run_agent('agent')
+    agent.set_attr(count=0)
+    # Start timer
+    agent.after(1, event, 2, alias='foo')
+    agent.stop_timer('foo')
+    time.sleep(1.1)
+    assert agent.get_attr('count') == 0