Clean up Alexa.intent and DialogFlow.intent (#11492)

* Clean up Alexa.intent

* Restructure dialogflow too

* Lint

* Lint
pull/11437/merge
Paulus Schoutsen 2018-01-06 16:39:32 -08:00 committed by Pascal Vizeli
parent c613585100
commit 5c2cecde70
3 changed files with 169 additions and 103 deletions

View File

@ -9,15 +9,16 @@ import asyncio
import enum
import logging
from homeassistant.exceptions import HomeAssistantError
from homeassistant.core import callback
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.helpers import intent
from homeassistant.components import http
from homeassistant.util.decorator import Registry
from .const import DOMAIN, SYN_RESOLUTION_MATCH
INTENTS_API_ENDPOINT = '/api/alexa'
HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
@ -47,6 +48,10 @@ def async_setup(hass):
hass.http.register_view(AlexaIntentsView)
class UnknownRequest(HomeAssistantError):
"""When an unknown Alexa request is passed in."""
class AlexaIntentsView(http.HomeAssistantView):
"""Handle Alexa requests."""
@ -57,57 +62,98 @@ class AlexaIntentsView(http.HomeAssistantView):
def post(self, request):
"""Handle Alexa."""
hass = request.app['hass']
data = yield from request.json()
message = yield from request.json()
_LOGGER.debug('Received Alexa request: %s', data)
_LOGGER.debug('Received Alexa request: %s', message)
req = data.get('request')
try:
response = yield from async_handle_message(hass, message)
return b'' if response is None else self.json(response)
except UnknownRequest as err:
_LOGGER.warning(str(err))
return self.json(intent_error_response(
hass, message, str(err)))
if req is None:
_LOGGER.error('Received invalid data from Alexa: %s', data)
return self.json_message('Expected request value not received',
HTTP_BAD_REQUEST)
except intent.UnknownIntent as err:
_LOGGER.warning(str(err))
return self.json(intent_error_response(
hass, message,
"This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
return self.json(intent_error_response(
hass, message,
"Invalid slot information received for this intent."))
except intent.IntentError as err:
_LOGGER.exception(str(err))
return self.json(intent_error_response(
hass, message, "Error handling intent."))
def intent_error_response(hass, message, error):
"""Return an Alexa response that will speak the error message."""
alexa_intent_info = message.get('request').get('intent')
alexa_response = AlexaResponse(hass, alexa_intent_info)
alexa_response.add_speech(SpeechType.plaintext, error)
return alexa_response.as_dict()
@asyncio.coroutine
def async_handle_message(hass, message):
"""Handle an Alexa intent.
Raises:
- UnknownRequest
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
req_type = req['type']
if req_type == 'SessionEndedRequest':
handler = HANDLERS.get(req_type)
if not handler:
raise UnknownRequest('Received unknown request {}'.format(req_type))
return (yield from handler(hass, message))
@HANDLERS.register('SessionEndedRequest')
@asyncio.coroutine
def async_handle_session_end(hass, message):
"""Handle a session end request."""
return None
@HANDLERS.register('IntentRequest')
@HANDLERS.register('LaunchRequest')
@asyncio.coroutine
def async_handle_intent(hass, message):
"""Handle an intent request.
Raises:
- intent.UnknownIntent
- intent.InvalidSlotInfo
- intent.IntentError
"""
req = message.get('request')
alexa_intent_info = req.get('intent')
alexa_response = AlexaResponse(hass, alexa_intent_info)
if req_type != 'IntentRequest' and req_type != 'LaunchRequest':
_LOGGER.warning('Received unsupported request: %s', req_type)
return self.json_message(
'Received unsupported request: {}'.format(req_type),
HTTP_BAD_REQUEST)
if req_type == 'LaunchRequest':
intent_name = data.get('session', {}) \
if req['type'] == 'LaunchRequest':
intent_name = message.get('session', {}) \
.get('application', {}) \
.get('applicationId')
else:
intent_name = alexa_intent_info['name']
try:
intent_response = yield from intent.async_handle(
hass, DOMAIN, intent_name,
{key: {'value': value} for key, value
in alexa_response.variables.items()})
except intent.UnknownIntent as err:
_LOGGER.warning('Received unknown intent %s', intent_name)
alexa_response.add_speech(
SpeechType.plaintext,
"This intent is not yet configured within Home Assistant.")
return self.json(alexa_response)
except intent.InvalidSlotInfo as err:
_LOGGER.error('Received invalid slot data from Alexa: %s', err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception('Error handling request for %s', intent_name)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
for intent_speech, alexa_speech in SPEECH_MAPPINGS.items():
if intent_speech in intent_response.speech:
@ -121,7 +167,7 @@ class AlexaIntentsView(http.HomeAssistantView):
CardType.simple, intent_response.card['simple']['title'],
intent_response.card['simple']['content'])
return self.json(alexa_response)
return alexa_response.as_dict()
def resolve_slot_synonyms(key, request):

View File

@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.const import HTTP_BAD_REQUEST
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import intent, template
from homeassistant.components.http import HomeAssistantView
@ -33,6 +33,10 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
class DialogFlowError(HomeAssistantError):
"""Raised when a DialogFlow error happens."""
@asyncio.coroutine
def async_setup(hass, config):
"""Set up Dialogflow component."""
@ -51,57 +55,71 @@ class DialogflowIntentsView(HomeAssistantView):
def post(self, request):
"""Handle Dialogflow."""
hass = request.app['hass']
data = yield from request.json()
message = yield from request.json()
_LOGGER.debug("Received Dialogflow request: %s", data)
_LOGGER.debug("Received Dialogflow request: %s", message)
req = data.get('result')
try:
response = yield from async_handle_message(hass, message)
return b'' if response is None else self.json(response)
if req is None:
_LOGGER.error("Received invalid data from Dialogflow: %s", data)
return self.json_message(
"Expected result value not received", HTTP_BAD_REQUEST)
except DialogFlowError as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message, str(err)))
except intent.UnknownIntent as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"This intent is not yet configured within Home Assistant."))
except intent.InvalidSlotInfo as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message,
"Invalid slot information received for this intent."))
except intent.IntentError as err:
_LOGGER.warning(str(err))
return self.json(dialogflow_error_response(
hass, message, "Error handling intent."))
def dialogflow_error_response(hass, message, error):
"""Return a response saying the error message."""
dialogflow_response = DialogflowResponse(message['result']['parameters'])
dialogflow_response.add_speech(error)
return dialogflow_response.as_dict()
@asyncio.coroutine
def async_handle_message(hass, message):
"""Handle a DialogFlow message."""
req = message.get('result')
action_incomplete = req['actionIncomplete']
if action_incomplete:
return None
action = req.get('action')
action = req.get('action', '')
parameters = req.get('parameters')
dialogflow_response = DialogflowResponse(parameters)
if action == "":
_LOGGER.warning("Received intent with empty action")
dialogflow_response.add_speech(
raise DialogFlowError(
"You have not defined an action in your Dialogflow intent.")
return self.json(dialogflow_response)
try:
intent_response = yield from intent.async_handle(
hass, DOMAIN, action,
{key: {'value': value} for key, value
in parameters.items()})
except intent.UnknownIntent as err:
_LOGGER.warning("Received unknown intent %s", action)
dialogflow_response.add_speech(
"This intent is not yet configured within Home Assistant.")
return self.json(dialogflow_response)
except intent.InvalidSlotInfo as err:
_LOGGER.error("Received invalid slot data: %s", err)
return self.json_message('Invalid slot data received',
HTTP_BAD_REQUEST)
except intent.IntentError:
_LOGGER.exception("Error handling request for %s", action)
return self.json_message('Error handling intent', HTTP_BAD_REQUEST)
if 'plain' in intent_response.speech:
dialogflow_response.add_speech(
intent_response.speech['plain']['speech'])
return self.json(dialogflow_response)
return dialogflow_response.as_dict()
class DialogflowResponse(object):

View File

@ -41,7 +41,7 @@ def async_handle(hass, platform, intent_type, slots=None, text_input=None):
handler = hass.data.get(DATA_KEY, {}).get(intent_type)
if handler is None:
raise UnknownIntent()
raise UnknownIntent('Unknown intent {}'.format(intent_type))
intent = Intent(hass, platform, intent_type, slots or {}, text_input)
@ -50,9 +50,11 @@ def async_handle(hass, platform, intent_type, slots=None, text_input=None):
result = yield from handler.async_handle(intent)
return result
except vol.Invalid as err:
raise InvalidSlotInfo from err
raise InvalidSlotInfo(
'Received invalid slot info for {}'.format(intent_type)) from err
except Exception as err:
raise IntentHandleError from err
raise IntentHandleError(
'Error handling {}'.format(intent_type)) from err
class IntentError(HomeAssistantError):