Remove deprecated remote classes (#7011)

* Remove deprecated remote classes

* Lint

* Fix tests

* Lint
pull/6888/merge
Paulus Schoutsen 2017-04-10 09:04:19 -07:00 committed by GitHub
parent ab247b0f4d
commit d081e5ab3a
5 changed files with 9 additions and 516 deletions

View File

@ -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."""

View File

@ -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'

View File

@ -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:

View File

@ -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()

View File

@ -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))