[tor-commits] [stem/master] Base class for events

atagar at torproject.org atagar at torproject.org
Mon Dec 3 02:35:44 UTC 2012


commit cac0c434ca9a4ed1f9bb6aa940dba1e006b92471
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Nov 4 18:38:28 2012 -0800

    Base class for events
    
    Adding a base class for tor events which does the initial parsing of positional
    and keyword arguments. This is using a similar design to the TorCtl patch I
    wrote for #3679 (but a little better).
---
 stem/control.py                  |    4 +-
 stem/response/__init__.py        |    3 ++
 stem/response/events.py          |   65 ++++++++++++++++++++++++++++++++++++++
 test/integ/control/controller.py |    8 +++--
 4 files changed, 75 insertions(+), 5 deletions(-)

diff --git a/stem/control.py b/stem/control.py
index ca3730b..fdcf972 100644
--- a/stem/control.py
+++ b/stem/control.py
@@ -1410,11 +1410,11 @@ class Controller(BaseController):
   def _handle_event(self, event_message):
     # TODO: parse the event_message into a stem.response.events.Event class
     
-    event_message_type = str(event_message).split()[0]
+    stem.response.convert("EVENT", event_message, arrived_at=time.time())
     
     with self._event_listeners_lock:
       for event_type, event_listeners in self._event_listeners.items():
-        if event_type == event_message_type:
+        if event_type == event_message.type:
           for listener in event_listeners:
             listener(event_message)
 
diff --git a/stem/response/__init__.py b/stem/response/__init__.py
index 050ba02..2e9296d 100644
--- a/stem/response/__init__.py
+++ b/stem/response/__init__.py
@@ -26,6 +26,7 @@ Parses replies from the control socket.
 from __future__ import with_statement
 
 __all__ = [
+  "events",
   "getinfo",
   "getconf",
   "protocolinfo",
@@ -85,6 +86,7 @@ def convert(response_type, message, **kwargs):
       or response_type isn't supported
   """
   
+  import stem.response.events
   import stem.response.getinfo
   import stem.response.getconf
   import stem.response.protocolinfo
@@ -95,6 +97,7 @@ def convert(response_type, message, **kwargs):
     raise TypeError("Only able to convert stem.response.ControlMessage instances")
   
   response_types = {
+    "EVENT": stem.response.events.Event,
     "GETINFO": stem.response.getinfo.GetInfoResponse,
     "GETCONF": stem.response.getconf.GetConfResponse,
     "MAPADDRESS": stem.response.mapaddress.MapAddressResponse,
diff --git a/stem/response/events.py b/stem/response/events.py
new file mode 100644
index 0000000..32dd655
--- /dev/null
+++ b/stem/response/events.py
@@ -0,0 +1,65 @@
+import re
+
+import stem.response
+
+# Matches keyword=value arguments. This can't be a simple "(.*)=(.*)" pattern
+# because some positional arguments, like circuit paths, can have an equal
+# sign.
+
+KW_ARG = re.compile("([A-Za-z0-9_]+)=(.*)")
+
+class Event(stem.response.ControlMessage):
+  """
+  Base for events we receive asynchronously, as described in section 4.1 of the
+  `control-spec
+  <https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt>`_.
+  """
+  
+  _POSITIONAL_ARGS = ()
+  _KEYWORD_ARGS = {}
+  
+  def _parse_message(self, arrived_at):
+    fields = str(self).split()
+    
+    if not fields:
+      raise stem.socket.ProtocolError("Received a blank tor event. Events must at the very least have a type.")
+    
+    self.type = fields.pop(0)
+    self.arrived_at = arrived_at
+    
+    # Tor events contain some number of positional arguments followed by
+    # key/value mappings. Parsing keyword arguments from the end until we hit
+    # something that isn't a key/value mapping. The rest are positional.
+    
+    self.positional_args = []
+    self.keyword_args = {}
+    
+    while fields:
+      kw_match = KW_ARG.match(fields[-1])
+      
+      if kw_match:
+        k, v = kw_match.groups()
+        self.keyword_args[k] = v
+        fields.pop() # remove the field
+      else:
+        # not a key/value mapping, the remaining fields are positional
+        self.positional_args = fields
+        break
+    
+    # Setting attributes for the fields that we recognize. Unrecognized fields
+    # only appear in our 'positional_args' and 'keyword_args' attributes.
+    
+    for i in xrange(len(self._POSITIONAL_ARGS)):
+      attr_name = self._POSITIONAL_ARGS[i]
+      attr_value = self.positional_args[i] if i < len(self.positional_args) else None
+      
+      setattr(self, attr_name, attr_value)
+    
+    for controller_attr_name, attr_name in self._KEYWORD_ARGS.items():
+      setattr(self, attr_name, self.keyword_args.get(controller_attr_name))
+    
+    # if we're a recognized event type then translate ourselves into that subclass
+    
+    #self.__class__ = response_class
+    #self._parse_message()
+
diff --git a/test/integ/control/controller.py b/test/integ/control/controller.py
index e5d7782..8f93d53 100644
--- a/test/integ/control/controller.py
+++ b/test/integ/control/controller.py
@@ -74,9 +74,6 @@ class TestController(unittest.TestCase):
       # BW events occure at the rate of one per second, so wait a bit to let
       # some accumulate.
       
-      # TODO: check that the type of events in event_buffer1 are BandwidthEvent
-      # instances when we have proper event types
-      
       time.sleep(3)
       
       self.assertTrue(len(event_buffer1) >= 2)
@@ -91,6 +88,11 @@ class TestController(unittest.TestCase):
       
       self.assertTrue(len(event_buffer1) >= 4)
       self.assertEqual(buffer2_size, len(event_buffer2))
+      
+      for event in event_buffer1:
+        self.assertTrue(isinstance(event, stem.response.events.Event))
+        self.assertEqual(2, len(event.positional_args))
+        self.assertEqual({}, event.keyword_args)
   
   def test_getinfo(self):
     """





More information about the tor-commits mailing list