Skip to content

Commit

Permalink
Reorganize HTTP component (home-assistant#4575)
Browse files Browse the repository at this point in the history
* Move HTTP to own folder

* Break HTTP into middlewares

* Lint

* Split tests per middleware

* Clean up HTTP tests

* Make HomeAssistantViews more stateless

* Lint

* Make HTTP setup async
  • Loading branch information
balloob authored Nov 25, 2016
1 parent 58b85b2 commit 32ffd00
Show file tree
Hide file tree
Showing 35 changed files with 1,318 additions and 1,084 deletions.
6 changes: 3 additions & 3 deletions homeassistant/components/alexa.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class AlexaIntentsView(HomeAssistantView):

def __init__(self, hass, intents):
"""Initialize Alexa view."""
super().__init__(hass)
super().__init__()

intents = copy.deepcopy(intents)
template.attach(hass, intents)
Expand Down Expand Up @@ -150,7 +150,7 @@ def post(self, request):
return None

intent = req.get('intent')
response = AlexaResponse(self.hass, intent)
response = AlexaResponse(request.app['hass'], intent)

if req_type == 'LaunchRequest':
response.add_speech(
Expand Down Expand Up @@ -282,7 +282,7 @@ class AlexaFlashBriefingView(HomeAssistantView):

def __init__(self, hass, flash_briefings):
"""Initialize Alexa view."""
super().__init__(hass)
super().__init__()
self.flash_briefings = copy.deepcopy(flash_briefings)
template.attach(hass, self.flash_briefings)

Expand Down
55 changes: 30 additions & 25 deletions homeassistant/components/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,10 @@ class APIEventStream(HomeAssistantView):
@asyncio.coroutine
def get(self, request):
"""Provide a streaming interface for the event bus."""
# pylint: disable=no-self-use
hass = request.app['hass']
stop_obj = object()
to_write = asyncio.Queue(loop=self.hass.loop)
to_write = asyncio.Queue(loop=hass.loop)

restrict = request.GET.get('restrict')
if restrict:
Expand Down Expand Up @@ -106,7 +108,7 @@ def forward_events(event):
response.content_type = 'text/event-stream'
yield from response.prepare(request)

unsub_stream = self.hass.bus.async_listen(MATCH_ALL, forward_events)
unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events)

try:
_LOGGER.debug('STREAM %s ATTACHED', id(stop_obj))
Expand All @@ -117,7 +119,7 @@ def forward_events(event):
while True:
try:
with async_timeout.timeout(STREAM_PING_INTERVAL,
loop=self.hass.loop):
loop=hass.loop):
payload = yield from to_write.get()

if payload is stop_obj:
Expand Down Expand Up @@ -145,7 +147,7 @@ class APIConfigView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get current configuration."""
return self.json(self.hass.config.as_dict())
return self.json(request.app['hass'].config.as_dict())


class APIDiscoveryView(HomeAssistantView):
Expand All @@ -158,10 +160,11 @@ class APIDiscoveryView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get discovery info."""
needs_auth = self.hass.config.api.api_password is not None
hass = request.app['hass']
needs_auth = hass.config.api.api_password is not None
return self.json({
'base_url': self.hass.config.api.base_url,
'location_name': self.hass.config.location_name,
'base_url': hass.config.api.base_url,
'location_name': hass.config.location_name,
'requires_api_password': needs_auth,
'version': __version__
})
Expand All @@ -176,7 +179,7 @@ class APIStatesView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get current states."""
return self.json(self.hass.states.async_all())
return self.json(request.app['hass'].states.async_all())


class APIEntityStateView(HomeAssistantView):
Expand All @@ -188,7 +191,7 @@ class APIEntityStateView(HomeAssistantView):
@ha.callback
def get(self, request, entity_id):
"""Retrieve state of entity."""
state = self.hass.states.get(entity_id)
state = request.app['hass'].states.get(entity_id)
if state:
return self.json(state)
else:
Expand All @@ -197,6 +200,7 @@ def get(self, request, entity_id):
@asyncio.coroutine
def post(self, request, entity_id):
"""Update state of entity."""
hass = request.app['hass']
try:
data = yield from request.json()
except ValueError:
Expand All @@ -211,15 +215,14 @@ def post(self, request, entity_id):
attributes = data.get('attributes')
force_update = data.get('force_update', False)

is_new_state = self.hass.states.get(entity_id) is None
is_new_state = hass.states.get(entity_id) is None

# Write state
self.hass.states.async_set(entity_id, new_state, attributes,
force_update)
hass.states.async_set(entity_id, new_state, attributes, force_update)

# Read the state back for our response
status_code = HTTP_CREATED if is_new_state else 200
resp = self.json(self.hass.states.get(entity_id), status_code)
resp = self.json(hass.states.get(entity_id), status_code)

resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id))

Expand All @@ -228,7 +231,7 @@ def post(self, request, entity_id):
@ha.callback
def delete(self, request, entity_id):
"""Remove entity."""
if self.hass.states.async_remove(entity_id):
if request.app['hass'].states.async_remove(entity_id):
return self.json_message('Entity removed')
else:
return self.json_message('Entity not found', HTTP_NOT_FOUND)
Expand All @@ -243,7 +246,7 @@ class APIEventListenersView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get event listeners."""
return self.json(async_events_json(self.hass))
return self.json(async_events_json(request.app['hass']))


class APIEventView(HomeAssistantView):
Expand Down Expand Up @@ -271,7 +274,8 @@ def post(self, request, event_type):
if state:
event_data[key] = state

self.hass.bus.async_fire(event_type, event_data, ha.EventOrigin.remote)
request.app['hass'].bus.async_fire(event_type, event_data,
ha.EventOrigin.remote)

return self.json_message("Event {} fired.".format(event_type))

Expand All @@ -285,7 +289,7 @@ class APIServicesView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get registered services."""
return self.json(async_services_json(self.hass))
return self.json(async_services_json(request.app['hass']))


class APIDomainServicesView(HomeAssistantView):
Expand All @@ -300,12 +304,12 @@ def post(self, request, domain, service):
Returns a list of changed states.
"""
hass = request.app['hass']
body = yield from request.text()
data = json.loads(body) if body else None

with AsyncTrackStates(self.hass) as changed_states:
yield from self.hass.services.async_call(domain, service, data,
True)
with AsyncTrackStates(hass) as changed_states:
yield from hass.services.async_call(domain, service, data, True)

return self.json(changed_states)

Expand All @@ -320,6 +324,7 @@ class APIEventForwardingView(HomeAssistantView):
@asyncio.coroutine
def post(self, request):
"""Setup an event forwarder."""
hass = request.app['hass']
try:
data = yield from request.json()
except ValueError:
Expand All @@ -340,14 +345,14 @@ def post(self, request):

api = rem.API(host, api_password, port)

valid = yield from self.hass.loop.run_in_executor(
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(self.hass)
self.event_forwarder = rem.EventForwarder(hass)

self.event_forwarder.async_connect(api)

Expand Down Expand Up @@ -389,7 +394,7 @@ class APIComponentsView(HomeAssistantView):
@ha.callback
def get(self, request):
"""Get current loaded components."""
return self.json(self.hass.config.components)
return self.json(request.app['hass'].config.components)


class APIErrorLogView(HomeAssistantView):
Expand All @@ -402,7 +407,7 @@ class APIErrorLogView(HomeAssistantView):
def get(self, request):
"""Serve error log."""
resp = yield from self.file(
request, self.hass.config.path(ERROR_LOG_FILENAME))
request, request.app['hass'].config.path(ERROR_LOG_FILENAME))
return resp


Expand All @@ -417,7 +422,7 @@ def post(self, request):
"""Render a template."""
try:
data = yield from request.json()
tpl = template.Template(data['template'], self.hass)
tpl = template.Template(data['template'], request.app['hass'])
return tpl.async_render(data.get('variables'))
except (ValueError, TemplateError) as ex:
return self.json_message('Error rendering template: {}'.format(ex),
Expand Down
11 changes: 5 additions & 6 deletions homeassistant/components/camera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED

DOMAIN = 'camera'
DEPENDENCIES = ['http']
Expand All @@ -33,8 +33,8 @@ def async_setup(hass, config):
component = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)

hass.http.register_view(CameraImageView(hass, component.entities))
hass.http.register_view(CameraMjpegStream(hass, component.entities))
hass.http.register_view(CameraImageView(component.entities))
hass.http.register_view(CameraMjpegStream(component.entities))

yield from component.async_setup(config)
return True
Expand Down Expand Up @@ -165,9 +165,8 @@ class CameraView(HomeAssistantView):

requires_auth = False

def __init__(self, hass, entities):
def __init__(self, entities):
"""Initialize a basic camera view."""
super().__init__(hass)
self.entities = entities

@asyncio.coroutine
Expand All @@ -178,7 +177,7 @@ def get(self, request, entity_id):
if camera is None:
return web.Response(status=404)

authenticated = (request.authenticated or
authenticated = (request[KEY_AUTHENTICATED] or
request.GET.get('token') == camera.access_token)

if not authenticated:
Expand Down
12 changes: 5 additions & 7 deletions homeassistant/components/device_tracker/gpslogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

def setup_scanner(hass, config, see):
"""Setup an endpoint for the GPSLogger application."""
hass.http.register_view(GPSLoggerView(hass, see))
hass.http.register_view(GPSLoggerView(see))

return True

Expand All @@ -32,20 +32,18 @@ class GPSLoggerView(HomeAssistantView):
url = '/api/gpslogger'
name = 'api:gpslogger'

def __init__(self, hass, see):
def __init__(self, see):
"""Initialize GPSLogger url endpoints."""
super().__init__(hass)
self.see = see

@asyncio.coroutine
def get(self, request):
"""A GPSLogger message received as GET."""
res = yield from self._handle(request.GET)
res = yield from self._handle(request.app['hass'], request.GET)
return res

@asyncio.coroutine
# pylint: disable=too-many-return-statements
def _handle(self, data):
def _handle(self, hass, data):
"""Handle gpslogger request."""
if 'latitude' not in data or 'longitude' not in data:
return ('Latitude and longitude not specified.',
Expand All @@ -66,7 +64,7 @@ def _handle(self, data):
if 'battery' in data:
battery = float(data['battery'])

yield from self.hass.loop.run_in_executor(
yield from hass.loop.run_in_executor(
None, partial(self.see, dev_id=device,
gps=gps_location, battery=battery,
gps_accuracy=accuracy))
Expand Down
17 changes: 8 additions & 9 deletions homeassistant/components/device_tracker/locative.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

def setup_scanner(hass, config, see):
"""Setup an endpoint for the Locative application."""
hass.http.register_view(LocativeView(hass, see))
hass.http.register_view(LocativeView(see))

return True

Expand All @@ -34,27 +34,26 @@ class LocativeView(HomeAssistantView):
url = '/api/locative'
name = 'api:locative'

def __init__(self, hass, see):
def __init__(self, see):
"""Initialize Locative url endpoints."""
super().__init__(hass)
self.see = see

@asyncio.coroutine
def get(self, request):
"""Locative message received as GET."""
res = yield from self._handle(request.GET)
res = yield from self._handle(request.app['hass'], request.GET)
return res

@asyncio.coroutine
def post(self, request):
"""Locative message received."""
data = yield from request.post()
res = yield from self._handle(data)
res = yield from self._handle(request.app['hass'], data)
return res

@asyncio.coroutine
# pylint: disable=too-many-return-statements
def _handle(self, data):
def _handle(self, hass, data):
"""Handle locative request."""
if 'latitude' not in data or 'longitude' not in data:
return ('Latitude and longitude not specified.',
Expand All @@ -81,19 +80,19 @@ def _handle(self, data):
gps_location = (data[ATTR_LATITUDE], data[ATTR_LONGITUDE])

if direction == 'enter':
yield from self.hass.loop.run_in_executor(
yield from hass.loop.run_in_executor(
None, partial(self.see, dev_id=device,
location_name=location_name,
gps=gps_location))
return 'Setting location to {}'.format(location_name)

elif direction == 'exit':
current_state = self.hass.states.get(
current_state = hass.states.get(
'{}.{}'.format(DOMAIN, device))

if current_state is None or current_state.state == location_name:
location_name = STATE_NOT_HOME
yield from self.hass.loop.run_in_executor(
yield from hass.loop.run_in_executor(
None, partial(self.see, dev_id=device,
location_name=location_name,
gps=gps_location))
Expand Down
Loading

0 comments on commit 32ffd00

Please sign in to comment.