commit cac0c434ca9a4ed1f9bb6aa940dba1e006b92471 Author: Damian Johnson atagar@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): """
tor-commits@lists.torproject.org