diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index f5c4103eaf7..05f86fd5b88 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -124,9 +124,16 @@ class EventBus(object): """ Class that allows code to listen for- and fire events. """ def __init__(self): - self.listeners = defaultdict(list) + self._listeners = defaultdict(list) self.logger = logging.getLogger(__name__) + @property + def listeners(self): + """ List of events that is being listened for. """ + return { key: len(self._listeners[key]) + for key in self._listeners.keys() + if len(self._listeners[key]) > 0 } + def fire(self, event_type, event_data=None): """ Fire an event. """ @@ -142,8 +149,8 @@ class EventBus(object): # We do not use itertools.chain() because some listeners might # choose to remove themselves as a listener while being executed - for listener in self.listeners[ALL_EVENTS] + \ - self.listeners[event.event_type]: + for listener in self._listeners[ALL_EVENTS] + \ + self._listeners[event.event_type]: try: listener(event) @@ -159,7 +166,7 @@ class EventBus(object): To listen to all events specify the constant ``ALL_EVENTS`` as event_type. """ - self.listeners[event_type].append(listener) + self._listeners[event_type].append(listener) def listen_once(self, event_type, listener): """ Listen once for event of a specific type. @@ -181,10 +188,10 @@ class EventBus(object): def remove_listener(self, event_type, listener): """ Removes a listener of a specific event_type. """ try: - self.listeners[event_type].remove(listener) + self._listeners[event_type].remove(listener) - if len(self.listeners[event_type]) == 0: - del self.listeners[event_type] + if len(self._listeners[event_type]) == 0: + del self._listeners[event_type] except ValueError: pass diff --git a/homeassistant/httpinterface.py b/homeassistant/httpinterface.py index de9d16d802d..ff42eb255fd 100644 --- a/homeassistant/httpinterface.py +++ b/homeassistant/httpinterface.py @@ -144,6 +144,7 @@ class RequestHandler(BaseHTTPRequestHandler): '_handle_post_states_category'), # /events + ('GET', '/events', '_handle_get_events'), ('POST', re.compile(r'/events/(?P\w+)'), '_handle_post_events_event_type') ] @@ -300,11 +301,8 @@ class RequestHandler(BaseHTTPRequestHandler): # Describe event bus: write(("")) - for category in sorted(self.server.eventbus.listeners, - key=lambda key: key.lower()): - write("". - format(category, - len(self.server.eventbus.listeners[category]))) + for event_type, count in sorted(self.server.eventbus.listeners.items()): + write("".format(event_type, count)) # Form to allow firing events write("
EventListeners
{}{}
{}{}
") @@ -368,6 +366,10 @@ class RequestHandler(BaseHTTPRequestHandler): self._message("Invalid JSON for attributes", HTTP_UNPROCESSABLE_ENTITY) + def _handle_get_events(self, path_match, data): + """ Handles getting overview of event listeners. """ + self._write_json({'listeners': self.server.eventbus.listeners}) + def _handle_post_events_event_type(self, path_match, data): """ Handles firing of an event. """ event_type = path_match.group('event_type') diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 29b8283a850..a2f4c806def 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -55,6 +55,35 @@ class EventBus(ha.EventBus): self.logger = logging.getLogger(__name__) + @property + def listeners(self): + """ List of events that is being listened for. """ + try: + req = self._call_api(METHOD_GET, hah.URL_API_EVENTS) + + if req.status_code == 200: + data = req.json() + + return data['listeners'] + + else: + raise ha.HomeAssistantException( + "Got unexpected result (3): {}.".format(req.text)) + + except requests.exceptions.ConnectionError: + self.logger.exception("EventBus:Error connecting to server") + raise ha.HomeAssistantException("Error connecting to server") + + except ValueError: # If req.json() can't parse the json + self.logger.exception("EventBus:Got unexpected result") + raise ha.HomeAssistantException( + "Got unexpected result: {}".format(req.text)) + + except KeyError: # If not all expected keys are in the returned JSON + self.logger.exception("EventBus:Got unexpected result (2)") + raise ha.HomeAssistantException( + "Got unexpected result (2): {}".format(req.text)) + def fire(self, event_type, event_data=None): """ Fire an event. """ diff --git a/homeassistant/test.py b/homeassistant/test.py index 4fa61bf5782..3803506ba3c 100644 --- a/homeassistant/test.py +++ b/homeassistant/test.py @@ -34,6 +34,7 @@ def ensure_homeassistant_started(): core = {'eventbus': ha.EventBus()} core['statemachine'] = ha.StateMachine(core['eventbus']) + core['eventbus'].listen('test_event', len) core['statemachine'].set_state('test','a_state') hah.HTTPInterface(core['eventbus'], core['statemachine'], @@ -83,14 +84,14 @@ class TestHTTPInterface(unittest.TestCase): def test_api_password(self): """ Test if we get access denied if we omit or provide a wrong api password. """ - req = requests.post( + req = requests.get( _url(hah.URL_API_STATES_CATEGORY.format("test"))) self.assertEqual(req.status_code, 401) - req = requests.post( + req = requests.get( _url(hah.URL_API_STATES_CATEGORY.format("test")), - data={"api_password":"not the password"}) + params={"api_password":"not the password"}) self.assertEqual(req.status_code, 401) @@ -125,7 +126,7 @@ class TestHTTPInterface(unittest.TestCase): """ Test if the debug interface allows us to get a state. """ req = requests.get( _url(hah.URL_API_STATES_CATEGORY.format("does_not_exist")), - data={"api_password":API_PASSWORD}) + params={"api_password":API_PASSWORD}) self.assertEqual(req.status_code, 422) @@ -227,6 +228,15 @@ class TestHTTPInterface(unittest.TestCase): self.assertEqual(req.status_code, 422) self.assertEqual(len(test_value), 0) + def test_api_get_event_listeners(self): + """ Test if we can get the list of events being listened for. """ + req = requests.get(_url(hah.URL_API_EVENTS), + params={"api_password":API_PASSWORD}) + + data = req.json() + + self.assertEqual(data['listeners'], self.eventbus.listeners) + class TestRemote(unittest.TestCase): """ Test the homeassistant.remote module. """ @@ -273,6 +283,9 @@ class TestRemote(unittest.TestCase): self.assertEqual(state['state'], "set_remotely") self.assertEqual(state['attributes']['test'], 1) + def test_remote_eb_listening_for_same(self): + """ Test if remote EB correctly reports listener overview. """ + self.assertEqual(self.eventbus.listeners, self.remote_eb.listeners) # pylint: disable=invalid-name def test_remote_eb_fire_event_with_no_data(self):