From d081e5ab3a4116c83ed8cfd42b21a5a4e3fbf6d9 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 10 Apr 2017 09:04:19 -0700 Subject: [PATCH] Remove deprecated remote classes (#7011) * Remove deprecated remote classes * Lint * Fix tests * Lint --- homeassistant/components/api.py | 78 +--------- homeassistant/const.py | 1 - homeassistant/remote.py | 250 +------------------------------- tests/components/test_api.py | 75 ---------- tests/test_remote.py | 121 +--------------- 5 files changed, 9 insertions(+), 516 deletions(-) diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index b22bd851190..8beb737ae89 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -17,9 +17,9 @@ from homeassistant.bootstrap import ERROR_LOG_FILENAME from homeassistant.const import ( EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST, HTTP_CREATED, HTTP_NOT_FOUND, - HTTP_UNPROCESSABLE_ENTITY, MATCH_ALL, URL_API, URL_API_COMPONENTS, + MATCH_ALL, URL_API, URL_API_COMPONENTS, URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, - URL_API_EVENT_FORWARD, URL_API_EVENTS, URL_API_SERVICES, + URL_API_EVENTS, URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, URL_API_TEMPLATE, __version__) from homeassistant.exceptions import TemplateError @@ -48,7 +48,6 @@ def setup(hass, config): hass.http.register_view(APIEventView) hass.http.register_view(APIServicesView) hass.http.register_view(APIDomainServicesView) - hass.http.register_view(APIEventForwardingView) hass.http.register_view(APIComponentsView) hass.http.register_view(APITemplateView) @@ -319,79 +318,6 @@ class APIDomainServicesView(HomeAssistantView): return self.json(changed_states) -class APIEventForwardingView(HomeAssistantView): - """View to handle EventForwarding requests.""" - - url = URL_API_EVENT_FORWARD - name = "api:event-forward" - event_forwarder = None - - @asyncio.coroutine - def post(self, request): - """Setup an event forwarder.""" - _LOGGER.warning('Event forwarding is deprecated. ' - 'Will be removed by 0.43') - hass = request.app['hass'] - try: - data = yield from request.json() - except ValueError: - return self.json_message("No data received.", HTTP_BAD_REQUEST) - - try: - host = data['host'] - api_password = data['api_password'] - except KeyError: - return self.json_message("No host or api_password received.", - HTTP_BAD_REQUEST) - - try: - port = int(data['port']) if 'port' in data else None - except ValueError: - return self.json_message("Invalid value received for port.", - HTTP_UNPROCESSABLE_ENTITY) - - api = rem.API(host, api_password, port) - - valid = yield from hass.loop.run_in_executor( - None, api.validate_api) - if not valid: - return self.json_message("Unable to validate API.", - HTTP_UNPROCESSABLE_ENTITY) - - if self.event_forwarder is None: - self.event_forwarder = rem.EventForwarder(hass) - - self.event_forwarder.async_connect(api) - - return self.json_message("Event forwarding setup.") - - @asyncio.coroutine - def delete(self, request): - """Remove event forwarder.""" - try: - data = yield from request.json() - except ValueError: - return self.json_message("No data received.", HTTP_BAD_REQUEST) - - try: - host = data['host'] - except KeyError: - return self.json_message("No host received.", HTTP_BAD_REQUEST) - - try: - port = int(data['port']) if 'port' in data else None - except ValueError: - return self.json_message("Invalid value received for port.", - HTTP_UNPROCESSABLE_ENTITY) - - if self.event_forwarder is not None: - api = rem.API(host, None, port) - - self.event_forwarder.async_disconnect(api) - - return self.json_message("Event forwarding cancelled.") - - class APIComponentsView(HomeAssistantView): """View to handle Components requests.""" diff --git a/homeassistant/const.py b/homeassistant/const.py index bb882ca4ea9..b73f64bfa72 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -361,7 +361,6 @@ URL_API_EVENTS = '/api/events' URL_API_EVENTS_EVENT = '/api/events/{}' URL_API_SERVICES = '/api/services' URL_API_SERVICES_SERVICE = '/api/services/{}/{}' -URL_API_EVENT_FORWARD = '/api/event_forwarding' URL_API_COMPONENTS = '/api/components' URL_API_ERROR_LOG = '/api/error_log' URL_API_LOG_OUT = '/api/log_out' diff --git a/homeassistant/remote.py b/homeassistant/remote.py index 1107eda8742..c0e3d9d6459 100644 --- a/homeassistant/remote.py +++ b/homeassistant/remote.py @@ -7,23 +7,19 @@ HomeAssistantError will be raised. For more details about the Python API, please refer to the documentation at https://home-assistant.io/developers/python_api/ """ -import asyncio -from concurrent.futures import ThreadPoolExecutor from datetime import datetime import enum import json import logging -import time -import threading import urllib.parse from typing import Optional import requests -from homeassistant import setup, core as ha +from homeassistant import core as ha from homeassistant.const import ( - HTTP_HEADER_HA_AUTH, SERVER_PORT, URL_API, URL_API_EVENT_FORWARD, + HTTP_HEADER_HA_AUTH, SERVER_PORT, URL_API, URL_API_EVENTS, URL_API_EVENTS_EVENT, URL_API_SERVICES, URL_API_CONFIG, URL_API_SERVICES_SERVICE, URL_API_STATES, URL_API_STATES_ENTITY, HTTP_HEADER_CONTENT_TYPE, CONTENT_TYPE_JSON) @@ -116,195 +112,6 @@ class API(object): self.base_url, 'yes' if self.api_password is not None else 'no') -class HomeAssistant(ha.HomeAssistant): - """Home Assistant that forwards work.""" - - # pylint: disable=super-init-not-called - def __init__(self, remote_api, local_api=None, loop=None): - """Initalize the forward instance.""" - _LOGGER.warning('Remote instances of Home Assistant are deprecated. ' - 'Will be removed by 0.43') - if not remote_api.validate_api(): - raise HomeAssistantError( - "Remote API at {}:{} not valid: {}".format( - remote_api.host, remote_api.port, remote_api.status)) - - self.remote_api = remote_api - - self.loop = loop or asyncio.get_event_loop() - self.executor = ThreadPoolExecutor(max_workers=5) - self.loop.set_default_executor(self.executor) - self.loop.set_exception_handler(ha.async_loop_exception_handler) - self._pending_tasks = [] - self._pending_sheduler = None - - self.bus = EventBus(remote_api, self) - self.services = ha.ServiceRegistry(self) - self.states = StateMachine(self.bus, self.loop, self.remote_api) - self.config = ha.Config() - # This is a dictionary that any component can store any data on. - self.data = {} - self.state = ha.CoreState.not_running - self.exit_code = None - self.config.api = local_api - - def start(self): - """Start the instance.""" - # Ensure a local API exists to connect with remote - if 'api' not in self.config.components: - if not setup.setup_component(self, 'api'): - raise HomeAssistantError( - 'Unable to setup local API to receive events') - - self.state = ha.CoreState.starting - # pylint: disable=protected-access - ha._async_create_timer(self) - - self.bus.fire(ha.EVENT_HOMEASSISTANT_START, - origin=ha.EventOrigin.remote) - - # Ensure local HTTP is started - self.block_till_done() - self.state = ha.CoreState.running - time.sleep(0.05) - - # Setup that events from remote_api get forwarded to local_api - # Do this after we are running, otherwise HTTP is not started - # or requests are blocked - if not connect_remote_events(self.remote_api, self.config.api): - raise HomeAssistantError(( - 'Could not setup event forwarding from api {} to ' - 'local api {}').format(self.remote_api, self.config.api)) - - def stop(self): - """Stop Home Assistant and shuts down all threads.""" - _LOGGER.info("Stopping") - self.state = ha.CoreState.stopping - - self.bus.fire(ha.EVENT_HOMEASSISTANT_STOP, - origin=ha.EventOrigin.remote) - - # Disconnect master event forwarding - disconnect_remote_events(self.remote_api, self.config.api) - self.state = ha.CoreState.not_running - - -class EventBus(ha.EventBus): - """EventBus implementation that forwards fire_event to remote API.""" - - def __init__(self, api, hass): - """Initalize the eventbus.""" - super().__init__(hass) - self._api = api - - def fire(self, event_type, event_data=None, origin=ha.EventOrigin.local): - """Forward local events to remote target. - - Handles remote event as usual. - """ - # All local events that are not TIME_CHANGED are forwarded to API - if origin == ha.EventOrigin.local and \ - event_type != ha.EVENT_TIME_CHANGED: - - fire_event(self._api, event_type, event_data) - - else: - super().fire(event_type, event_data, origin) - - -class EventForwarder(object): - """Listens for events and forwards to specified APIs.""" - - def __init__(self, hass, restrict_origin=None): - """Initalize the event forwarder.""" - _LOGGER.warning('API forwarding is deprecated. ' - 'Will be removed by 0.43') - - self.hass = hass - self.restrict_origin = restrict_origin - - # We use a tuple (host, port) as key to ensure - # that we do not forward to the same host twice - self._targets = {} - - self._lock = threading.Lock() - self._async_unsub_listener = None - - @ha.callback - def async_connect(self, api): - """Attach to a Home Assistant instance and forward events. - - Will overwrite old target if one exists with same host/port. - """ - if self._async_unsub_listener is None: - self._async_unsub_listener = self.hass.bus.async_listen( - ha.MATCH_ALL, self._event_listener) - - key = (api.host, api.port) - - self._targets[key] = api - - @ha.callback - def async_disconnect(self, api): - """Remove target from being forwarded to.""" - key = (api.host, api.port) - - did_remove = self._targets.pop(key, None) is None - - if len(self._targets) == 0: - # Remove event listener if no forwarding targets present - self._async_unsub_listener() - self._async_unsub_listener = None - - return did_remove - - def _event_listener(self, event): - """Listen and forward all events.""" - with self._lock: - # We don't forward time events or, if enabled, non-local events - if event.event_type == ha.EVENT_TIME_CHANGED or \ - (self.restrict_origin and event.origin != self.restrict_origin): - return - - for api in self._targets.values(): - fire_event(api, event.event_type, event.data) - - -class StateMachine(ha.StateMachine): - """Fire set events to an API. Uses state_change events to track states.""" - - def __init__(self, bus, loop, api): - """Initalize the statemachine.""" - super().__init__(bus, loop) - self._api = api - self.mirror() - - bus.listen(ha.EVENT_STATE_CHANGED, self._state_changed_listener) - - def remove(self, entity_id): - """Remove the state of an entity. - - Returns boolean to indicate if an entity was removed. - """ - return remove_state(self._api, entity_id) - - def set(self, entity_id, new_state, attributes=None, force_update=False): - """Call set_state on remote API.""" - set_state(self._api, entity_id, new_state, attributes, force_update) - - def mirror(self): - """Discard current data and mirrors the remote state machine.""" - self._states = {state.entity_id: state for state - in get_states(self._api)} - - def _state_changed_listener(self, event): - """Listen for state changed events and applies them.""" - if event.data['new_state'] is None: - self._states.pop(event.data['entity_id'], None) - else: - self._states[event.data['entity_id']] = event.data['new_state'] - - class JSONEncoder(json.JSONEncoder): """JSONEncoder that supports Home Assistant objects.""" @@ -352,59 +159,6 @@ def validate_api(api): return APIStatus.CANNOT_CONNECT -def connect_remote_events(from_api, to_api): - """Setup from_api to forward all events to to_api.""" - _LOGGER.warning('Event forwarding is deprecated. ' - 'Will be removed by 0.43') - data = { - 'host': to_api.host, - 'api_password': to_api.api_password, - 'port': to_api.port - } - - try: - req = from_api(METHOD_POST, URL_API_EVENT_FORWARD, data) - - if req.status_code == 200: - return True - else: - _LOGGER.error( - "Error setting up event forwarding: %s - %s", - req.status_code, req.text) - - return False - - except HomeAssistantError: - _LOGGER.exception("Error setting up event forwarding") - return False - - -def disconnect_remote_events(from_api, to_api): - """Disconnect forwarding events from from_api to to_api.""" - _LOGGER.warning('Event forwarding is deprecated. ' - 'Will be removed by 0.43') - data = { - 'host': to_api.host, - 'port': to_api.port - } - - try: - req = from_api(METHOD_DELETE, URL_API_EVENT_FORWARD, data) - - if req.status_code == 200: - return True - else: - _LOGGER.error( - "Error removing event forwarding: %s - %s", - req.status_code, req.text) - - return False - - except HomeAssistantError: - _LOGGER.exception("Error removing an event forwarder") - return False - - def get_event_listeners(api): """List of events that is being listened for.""" try: diff --git a/tests/components/test_api.py b/tests/components/test_api.py index e2d93c9cce7..8d6041b49c1 100644 --- a/tests/components/test_api.py +++ b/tests/components/test_api.py @@ -337,81 +337,6 @@ class TestAPI(unittest.TestCase): self.assertEqual(400, req.status_code) - def test_api_event_forward(self): - """Test setting up event forwarding.""" - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - headers=HA_HEADERS) - self.assertEqual(400, req.status_code) - - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({'host': '127.0.0.1'}), - headers=HA_HEADERS) - self.assertEqual(400, req.status_code) - - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({'api_password': 'bla-di-bla'}), - headers=HA_HEADERS) - self.assertEqual(400, req.status_code) - - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({ - 'api_password': 'bla-di-bla', - 'host': '127.0.0.1', - 'port': 'abcd' - }), - headers=HA_HEADERS) - self.assertEqual(422, req.status_code) - - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({ - 'api_password': 'bla-di-bla', - 'host': '127.0.0.1', - 'port': get_test_instance_port() - }), - headers=HA_HEADERS) - self.assertEqual(422, req.status_code) - - # Setup a real one - req = requests.post( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({ - 'api_password': API_PASSWORD, - 'host': '127.0.0.1', - 'port': SERVER_PORT - }), - headers=HA_HEADERS) - self.assertEqual(200, req.status_code) - - # Delete it again.. - req = requests.delete( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({}), - headers=HA_HEADERS) - self.assertEqual(400, req.status_code) - - req = requests.delete( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({ - 'host': '127.0.0.1', - 'port': 'abcd' - }), - headers=HA_HEADERS) - self.assertEqual(422, req.status_code) - - req = requests.delete( - _url(const.URL_API_EVENT_FORWARD), - data=json.dumps({ - 'host': '127.0.0.1', - 'port': SERVER_PORT - }), - headers=HA_HEADERS) - self.assertEqual(200, req.status_code) - def test_stream(self): """Test the stream.""" listen_count = self._listen_count() diff --git a/tests/test_remote.py b/tests/test_remote.py index eec7b4cf98d..41011794914 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,9 +1,6 @@ """Test Home Assistant remote methods and classes.""" # pylint: disable=protected-access -import asyncio -import threading import unittest -from unittest.mock import patch from homeassistant import remote, setup, core as ha import homeassistant.components.http as http @@ -11,18 +8,17 @@ from homeassistant.const import HTTP_HEADER_HA_AUTH, EVENT_STATE_CHANGED import homeassistant.util.dt as dt_util from tests.common import ( - get_test_instance_port, get_test_home_assistant, get_test_config_dir) + get_test_instance_port, get_test_home_assistant) API_PASSWORD = 'test1234' MASTER_PORT = get_test_instance_port() -SLAVE_PORT = get_test_instance_port() BROKEN_PORT = get_test_instance_port() HTTP_BASE_URL = 'http://127.0.0.1:{}'.format(MASTER_PORT) HA_HEADERS = {HTTP_HEADER_HA_AUTH: API_PASSWORD} broken_api = remote.API('127.0.0.1', "bladybla", port=get_test_instance_port()) -hass, slave, master_api = None, None, None +hass, master_api = None, None def _url(path=''): @@ -32,8 +28,8 @@ def _url(path=''): # pylint: disable=invalid-name def setUpModule(): - """Initalization of a Home Assistant server and Slave instance.""" - global hass, slave, master_api + """Initalization of a Home Assistant server instance.""" + global hass, master_api hass = get_test_home_assistant() @@ -51,30 +47,10 @@ def setUpModule(): master_api = remote.API('127.0.0.1', API_PASSWORD, MASTER_PORT) - # Start slave - loop = asyncio.new_event_loop() - - # FIXME: should not be a daemon - threading.Thread(name='SlaveThread', daemon=True, - target=loop.run_forever).start() - - slave = remote.HomeAssistant(master_api, loop=loop) - slave.async_track_tasks() - slave.config.config_dir = get_test_config_dir() - slave.config.skip_pip = True - setup.setup_component( - slave, http.DOMAIN, - {http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD, - http.CONF_SERVER_PORT: SLAVE_PORT}}) - - with patch.object(ha, '_async_create_timer', return_value=None): - slave.start() - # pylint: disable=invalid-name def tearDownModule(): - """Stop the Home Assistant server and slave.""" - slave.stop() + """Stop the Home Assistant server.""" hass.stop() @@ -83,7 +59,6 @@ class TestRemoteMethods(unittest.TestCase): def tearDown(self): """Stop everything that was started.""" - slave.block_till_done() hass.block_till_done() def test_validate_api(self): @@ -228,89 +203,3 @@ class TestRemoteMethods(unittest.TestCase): now = dt_util.utcnow() self.assertEqual(now.isoformat(), ha_json_enc.default(now)) - - -class TestRemoteClasses(unittest.TestCase): - """Test the homeassistant.remote module.""" - - def tearDown(self): - """Stop everything that was started.""" - slave.block_till_done() - hass.block_till_done() - - def test_home_assistant_init(self): - """Test HomeAssistant init.""" - # Wrong password - self.assertRaises( - ha.HomeAssistantError, remote.HomeAssistant, - remote.API('127.0.0.1', API_PASSWORD + 'A', 8124)) - - # Wrong port - self.assertRaises( - ha.HomeAssistantError, remote.HomeAssistant, - remote.API('127.0.0.1', API_PASSWORD, BROKEN_PORT)) - - def test_statemachine_init(self): - """Test if remote.StateMachine copies all states on init.""" - self.assertEqual(sorted(hass.states.all()), - sorted(slave.states.all())) - - def test_statemachine_set(self): - """Test if setting the state on a slave is recorded.""" - slave.states.set("remote.test", "remote.statemachine test") - - # Wait till slave tells master - slave.block_till_done() - # Wait till master gives updated state - hass.block_till_done() - - self.assertEqual("remote.statemachine test", - slave.states.get("remote.test").state) - - def test_statemachine_remove_from_master(self): - """Remove statemachine from master.""" - hass.states.set("remote.master_remove", "remove me!") - hass.block_till_done() - slave.block_till_done() - - self.assertIn('remote.master_remove', slave.states.entity_ids()) - - hass.states.remove("remote.master_remove") - hass.block_till_done() - slave.block_till_done() - - self.assertNotIn('remote.master_remove', slave.states.entity_ids()) - - def test_statemachine_remove_from_slave(self): - """Remove statemachine from slave.""" - hass.states.set("remote.slave_remove", "remove me!") - hass.block_till_done() - - self.assertIn('remote.slave_remove', slave.states.entity_ids()) - - self.assertTrue(slave.states.remove("remote.slave_remove")) - slave.block_till_done() - hass.block_till_done() - - self.assertNotIn('remote.slave_remove', slave.states.entity_ids()) - - def test_eventbus_fire(self): - """Test if events fired from the eventbus get fired.""" - hass_call = [] - slave_call = [] - - hass.bus.listen("test.event_no_data", lambda _: hass_call.append(1)) - slave.bus.listen("test.event_no_data", lambda _: slave_call.append(1)) - slave.bus.fire("test.event_no_data") - - # Wait till slave tells master - slave.block_till_done() - # Wait till master gives updated event - hass.block_till_done() - - self.assertEqual(1, len(hass_call)) - self.assertEqual(1, len(slave_call)) - - def test_get_config(self): - """Test the return of the configuration.""" - self.assertEqual(hass.config.as_dict(), remote.get_config(master_api))