Code styling tweaks to the Alexa integration (#86121)

This commit is contained in:
Franck Nijhof 2023-01-18 00:01:30 +01:00 committed by GitHub
parent b722a7e05b
commit 91aaca6471
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 139 additions and 79 deletions

View File

@ -135,16 +135,16 @@ class AlexaCapability:
def configuration(self):
"""Return the configuration object.
Applicable to the ThermostatController, SecurityControlPanel, ModeController, RangeController,
and EventDetectionSensor.
Applicable to the ThermostatController, SecurityControlPanel, ModeController,
RangeController, and EventDetectionSensor.
"""
return []
def configurations(self):
"""Return the configurations object.
The plural configurations object is different that the singular configuration object.
Applicable to EqualizerController interface.
The plural configurations object is different that the singular configuration
object. Applicable to EqualizerController interface.
"""
return []
@ -196,7 +196,8 @@ class AlexaCapability:
if configuration := self.configuration():
result["configuration"] = configuration
# The plural configurations object is different than the singular configuration object above.
# The plural configurations object is different than the singular
# configuration object above.
if configurations := self.configurations():
result["configurations"] = configurations
@ -757,7 +758,8 @@ class AlexaPlaybackController(AlexaCapability):
def supported_operations(self):
"""Return the supportedOperations object.
Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind, StartOver, Stop
Supported Operations: FastForward, Next, Pause, Play, Previous, Rewind,
StartOver, Stop
"""
supported_features = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
@ -1117,7 +1119,9 @@ class AlexaThermostatController(AlexaCapability):
def configuration(self):
"""Return configuration object.
Translates climate HVAC_MODES and PRESETS to supported Alexa ThermostatMode Values.
Translates climate HVAC_MODES and PRESETS to supported Alexa
ThermostatMode Values.
ThermostatMode Value must be AUTO, COOL, HEAT, ECO, OFF, or CUSTOM.
"""
supported_modes = []
@ -1133,7 +1137,8 @@ class AlexaThermostatController(AlexaCapability):
if thermostat_mode:
supported_modes.append(thermostat_mode)
# Return False for supportsScheduling until supported with event listener in handler.
# Return False for supportsScheduling until supported with event
# listener in handler.
configuration = {"supportsScheduling": False}
if supported_modes:
@ -1270,12 +1275,15 @@ class AlexaSecurityPanelController(AlexaCapability):
class AlexaModeController(AlexaCapability):
"""Implements Alexa.ModeController.
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
The instance property should be a concatenated string of device domain period and single word.
e.g. fan.speed & fan.direction.
The instance property must be unique across ModeController, RangeController,
ToggleController within the same device.
The instance property must not contain words from other instance property strings within the same device.
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
The instance property should be a concatenated string of device domain period
and single word. e.g. fan.speed & fan.direction.
The instance property must not contain words from other instance property
strings within the same device. e.g. Instance property cover.position &
cover.tilt_position will cause the Alexa.Discovery directive to fail.
An instance property string value may be reused for different devices.
@ -1408,8 +1416,8 @@ class AlexaModeController(AlexaCapability):
modes = self.entity.attributes.get(humidifier.ATTR_AVAILABLE_MODES, [])
for mode in modes:
self._resource.add_mode(f"{humidifier.ATTR_MODE}.{mode}", [mode])
# Humidifiers or Fans with a single mode completely break Alexa discovery, add a
# fake preset (see issue #53832).
# Humidifiers or Fans with a single mode completely break Alexa discovery,
# add a fake preset (see issue #53832).
if len(modes) == 1:
self._resource.add_mode(
f"{humidifier.ATTR_MODE}.{PRESET_MODE_NA}", [PRESET_MODE_NA]
@ -1479,12 +1487,15 @@ class AlexaModeController(AlexaCapability):
class AlexaRangeController(AlexaCapability):
"""Implements Alexa.RangeController.
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
The instance property should be a concatenated string of device domain period and single word.
e.g. fan.speed & fan.direction.
The instance property must be unique across ModeController, RangeController,
ToggleController within the same device.
The instance property must not contain words from other instance property strings within the same device.
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
The instance property should be a concatenated string of device domain period
and single word. e.g. fan.speed & fan.direction.
The instance property must not contain words from other instance property
strings within the same device. e.g. Instance property cover.position &
cover.tilt_position will cause the Alexa.Discovery directive to fail.
An instance property string value may be reused for different devices.
@ -1538,7 +1549,8 @@ class AlexaRangeController(AlexaCapability):
raise UnsupportedProperty(name)
# Return None for unavailable and unknown states.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable
# state in a stateReport.
if self.entity.state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
@ -1760,12 +1772,15 @@ class AlexaRangeController(AlexaCapability):
class AlexaToggleController(AlexaCapability):
"""Implements Alexa.ToggleController.
The instance property must be unique across ModeController, RangeController, ToggleController within the same device.
The instance property should be a concatenated string of device domain period and single word.
e.g. fan.speed & fan.direction.
The instance property must be unique across ModeController, RangeController,
ToggleController within the same device.
The instance property must not contain words from other instance property strings within the same device.
e.g. Instance property cover.position & cover.tilt_position will cause the Alexa.Discovery directive to fail.
The instance property should be a concatenated string of device domain period
and single word. e.g. fan.speed & fan.direction.
The instance property must not contain words from other instance property
strings within the same device. e.g. Instance property cover.position
& cover.tilt_position will cause the Alexa.Discovery directive to fail.
An instance property string value may be reused for different devices.
@ -2021,7 +2036,8 @@ class AlexaEventDetectionSensor(AlexaCapability):
state = self.entity.state
# Return None for unavailable and unknown states.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable state in a stateReport.
# Allows the Alexa.EndpointHealth Interface to handle the unavailable
# state in a stateReport.
if state in (STATE_UNAVAILABLE, STATE_UNKNOWN, None):
return None
@ -2089,7 +2105,8 @@ class AlexaEqualizerController(AlexaCapability):
def properties_supported(self):
"""Return what properties this entity supports.
Either bands, mode or both can be specified. Only mode is supported at this time.
Either bands, mode or both can be specified. Only mode is supported
at this time.
"""
return [{"name": "mode"}]

View File

@ -103,7 +103,8 @@ class DisplayCategory:
# Indicates a device that cools the air in interior spaces.
AIR_CONDITIONER = "AIR_CONDITIONER"
# Indicates a device that emits pleasant odors and masks unpleasant odors in interior spaces.
# Indicates a device that emits pleasant odors and masks unpleasant
# odors in interior spaces.
AIR_FRESHENER = "AIR_FRESHENER"
# Indicates a device that improves the quality of air in interior spaces.
@ -143,7 +144,8 @@ class DisplayCategory:
GAME_CONSOLE = "GAME_CONSOLE"
# Indicates a garage door.
# Garage doors must implement the ModeController interface to open and close the door.
# Garage doors must implement the ModeController interface to
# open and close the door.
GARAGE_DOOR = "GARAGE_DOOR"
# Indicates a wearable device that transmits audio directly into the ear.
@ -206,8 +208,8 @@ class DisplayCategory:
# Indicates a security system.
SECURITY_SYSTEM = "SECURITY_SYSTEM"
# Indicates an electric cooking device that sits on a countertop, cooks at low temperatures,
# and is often shaped like a cooking pot.
# Indicates an electric cooking device that sits on a countertop,
# cooks at low temperatures, and is often shaped like a cooking pot.
SLOW_COOKER = "SLOW_COOKER"
# Indicates an endpoint that locks.
@ -243,7 +245,8 @@ class DisplayCategory:
# Indicates a vacuum cleaner.
VACUUM_CLEANER = "VACUUM_CLEANER"
# Indicates a network-connected wearable device, such as an Apple Watch, Fitbit, or Samsung Gear.
# Indicates a network-connected wearable device, such as an Apple Watch,
# Fitbit, or Samsung Gear.
WEARABLE = "WEARABLE"
@ -574,9 +577,10 @@ class FanCapabilities(AlexaEntity):
force_range_controller = False
# AlexaRangeController controls the Fan Speed Percentage.
# For fans which only support on/off, no controller is added. This makes the
# fan impossible to turn on or off through Alexa, most likely due to a bug in Alexa.
# As a workaround, we add a range controller which can only be set to 0% or 100%.
# For fans which only support on/off, no controller is added. This makes
# the fan impossible to turn on or off through Alexa, most likely due
# to a bug in Alexa. As a workaround, we add a range controller which
# can only be set to 0% or 100%.
if force_range_controller or supported & fan.FanEntityFeature.SET_SPEED:
yield AlexaRangeController(
self.entity, instance=f"{fan.DOMAIN}.{fan.ATTR_PERCENTAGE}"

View File

@ -613,9 +613,10 @@ async def async_api_adjust_volume_step(
"""Process an adjust volume step request."""
# media_player volume up/down service does not support specifying steps
# each component handles it differently e.g. via config.
# This workaround will simply call the volume up/Volume down the amount of steps asked for
# When no steps are called in the request, Alexa sends a default of 10 steps which for most
# purposes is too high. The default is set 1 in this case.
# This workaround will simply call the volume up/Volume down the amount of
# steps asked for. When no steps are called in the request, Alexa sends
# a default of 10 steps which for most purposes is too high. The default
# is set 1 in this case.
entity = directive.entity
volume_int = int(directive.payload["volumeSteps"])
is_default = bool(directive.payload["volumeStepsDefault"])
@ -1020,8 +1021,9 @@ async def async_api_disarm(
data = {ATTR_ENTITY_ID: entity.entity_id}
response = directive.response()
# Per Alexa Documentation: If you receive a Disarm directive, and the system is already disarmed,
# respond with a success response, not an error response.
# Per Alexa Documentation: If you receive a Disarm directive, and the
# system is already disarmed, respond with a success response,
# not an error response.
if entity.state == STATE_ALARM_DISARMED:
return response
@ -1136,7 +1138,8 @@ async def async_api_adjust_mode(
Only supportedModes with ordered=True support the adjustMode directive.
"""
# Currently no supportedModes are configured with ordered=True to support this request.
# Currently no supportedModes are configured with ordered=True
# to support this request.
raise AlexaInvalidDirectiveError(DIRECTIVE_NOT_SUPPORTED)
@ -1483,7 +1486,9 @@ async def async_api_changechannel(
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.const.ATTR_MEDIA_CONTENT_ID: channel,
media_player.const.ATTR_MEDIA_CONTENT_TYPE: media_player.const.MEDIA_TYPE_CHANNEL,
media_player.const.ATTR_MEDIA_CONTENT_TYPE: (
media_player.const.MEDIA_TYPE_CHANNEL
),
}
await hass.services.async_call(

View File

@ -6,12 +6,15 @@ class AlexaGlobalCatalog:
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#global-alexa-catalog
You can use the global Alexa catalog for pre-defined names of devices, settings, values, and units.
This catalog is localized into all the languages that Alexa supports.
You can use the global Alexa catalog for pre-defined names of devices, settings,
values, and units.
This catalog is localized into all the languages that Alexa supports.
You can reference the following catalog of pre-defined friendly names.
Each item in the following list is an asset identifier followed by its supported friendly names.
The first friendly name for each identifier is the one displayed in the Alexa mobile app.
Each item in the following list is an asset identifier followed by its
supported friendly names. The first friendly name for each identifier is
the one displayed in the Alexa mobile app.
"""
# Air Purifier, Air Cleaner,Clean Air Machine
@ -23,7 +26,8 @@ class AlexaGlobalCatalog:
# Router, Internet Router, Network Router, Wifi Router, Net Router
DEVICE_NAME_ROUTER = "Alexa.DeviceName.Router"
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning, Window shade, Interior blind
# Shade, Blind, Curtain, Roller, Shutter, Drape, Awning,
# Window shade, Interior blind
DEVICE_NAME_SHADE = "Alexa.DeviceName.Shade"
# Shower
@ -190,10 +194,13 @@ class AlexaGlobalCatalog:
class AlexaCapabilityResource:
"""Base class for Alexa capabilityResources, modeResources, and presetResources objects.
"""Base class for Alexa capabilityResources, modeResources, and presetResources.
Resources objects labels must be unique across all modeResources and
presetResources within the same device. To provide support for all
supported locales, include one label from the AlexaGlobalCatalog in the
labels array.
Resources objects labels must be unique across all modeResources and presetResources within the same device.
To provide support for all supported locales, include one label from the AlexaGlobalCatalog in the labels array.
You cannot use any words from the following list as friendly names:
https://developer.amazon.com/docs/alexa/device-apis/resources-and-assets.html#names-you-cannot-use
@ -211,11 +218,17 @@ class AlexaCapabilityResource:
return self.serialize_labels(self._resource_labels)
def serialize_configuration(self):
"""Return ModeResources, PresetResources friendlyNames serialized for an API response."""
"""Return serialized configuration for an API response.
Return ModeResources, PresetResources friendlyNames serialized.
"""
return []
def serialize_labels(self, resources):
"""Return resource label objects for friendlyNames serialized for an API response."""
"""Return serialized labels for an API response.
Returns resource label objects for friendlyNames serialized.
"""
labels = []
for label in resources:
if label in AlexaGlobalCatalog.__dict__.values():
@ -245,7 +258,10 @@ class AlexaModeResource(AlexaCapabilityResource):
self._supported_modes.append({"value": value, "labels": labels})
def serialize_configuration(self):
"""Return configuration for ModeResources friendlyNames serialized for an API response."""
"""Return serialized configuration for an API response.
Returns configuration for ModeResources friendlyNames serialized.
"""
mode_resources = []
for mode in self._supported_modes:
result = {
@ -260,7 +276,8 @@ class AlexaModeResource(AlexaCapabilityResource):
class AlexaPresetResource(AlexaCapabilityResource):
"""Implements Alexa PresetResources.
Use presetResources with RangeController to provide a set of friendlyNames for each RangeController preset.
Use presetResources with RangeController to provide a set of
friendlyNamesfor each RangeController preset.
https://developer.amazon.com/docs/device-apis/resources-and-assets.html#presetresources
"""
@ -281,7 +298,10 @@ class AlexaPresetResource(AlexaCapabilityResource):
self._presets.append({"value": value, "labels": labels})
def serialize_configuration(self):
"""Return configuration for PresetResources friendlyNames serialized for an API response."""
"""Return serialized configuration for an API response.
Returns configuration for PresetResources friendlyNames serialized.
"""
configuration = {
"supportedRange": {
"minimumValue": self._minimum_value,
@ -309,18 +329,23 @@ class AlexaPresetResource(AlexaCapabilityResource):
class AlexaSemantics:
"""Class for Alexa Semantics Object.
You can optionally enable additional utterances by using semantics. When you use semantics,
you manually map the phrases "open", "close", "raise", and "lower" to directives.
You can optionally enable additional utterances by using semantics. When
you use semantics, you manually map the phrases "open", "close", "raise",
and "lower" to directives.
Semantics is supported for the following interfaces only: ModeController, RangeController, and ToggleController.
Semantics is supported for the following interfaces only: ModeController,
RangeController, and ToggleController.
Semantics stateMappings are only supported for one interface of the same type on the same device. If a device has
multiple RangeControllers only one interface may use stateMappings otherwise discovery will fail.
Semantics stateMappings are only supported for one interface of the same
type on the same device. If a device has multiple RangeControllers only
one interface may use stateMappings otherwise discovery will fail.
You can support semantics actionMappings on different controllers for the same device, however each controller must
support different phrases. For example, you can support "raise" on a RangeController, and "open" on a ModeController,
but you can't support "open" on both RangeController and ModeController. Semantics stateMappings are only supported
for one interface on the same device.
You can support semantics actionMappings on different controllers for the
same device, however each controller must support different phrases.
For example, you can support "raise" on a RangeController, and "open"
on a ModeController, but you can't support "open" on both RangeController
and ModeController. Semantics stateMappings are only supported for one
interface on the same device.
https://developer.amazon.com/docs/device-apis/alexa-discovery.html#semantics-object
"""

View File

@ -412,7 +412,7 @@ async def test_report_fan_speed_state(hass):
async def test_report_humidifier_humidity_state(hass):
"""Test PercentageController, PowerLevelController reports humidifier humidity correctly."""
"""Test PercentageController, PowerLevelController humidifier humidity reporting."""
hass.states.async_set(
"humidifier.dry",
"on",
@ -934,7 +934,10 @@ async def test_report_image_processing(hass):
@pytest.mark.parametrize("domain", ["button", "input_button"])
async def test_report_button_pressed(hass, domain):
"""Test button presses report human presence detection events to trigger routines."""
"""Test button presses report human presence detection events.
For use to trigger routines.
"""
hass.states.async_set(
f"{domain}.test_button", "now", {"friendly_name": "Test button"}
)

View File

@ -114,6 +114,6 @@ async def test_serialize_discovery_recovers(hass, caplog):
assert "Alexa.PowerController" not in interfaces
assert (
f"Error serializing Alexa.PowerController discovery for {hass.states.get('switch.bla')}"
in caplog.text
)
f"Error serializing Alexa.PowerController discovery"
f" for {hass.states.get('switch.bla')}"
) in caplog.text

View File

@ -193,7 +193,9 @@ async def test_intent_launch_request_not_configured(alexa_client):
"new": True,
"sessionId": SESSION_ID,
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00000"
"applicationId": (
"amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00000"
),
},
"attributes": {},
"user": {"userId": "amzn1.account.AM3B00000000000000000000000"},

View File

@ -410,7 +410,8 @@ async def test_fan(hass):
assert appliance["endpointId"] == "fan#test_1"
assert appliance["displayCategories"][0] == "FAN"
assert appliance["friendlyName"] == "Test fan 1"
# Alexa.RangeController is added to make a fan controllable when no other controllers are available
# Alexa.RangeController is added to make a fan controllable when
# no other controllers are available.
capabilities = assert_endpoint_capabilities(
appliance,
"Alexa.RangeController",
@ -466,7 +467,8 @@ async def test_fan2(hass):
assert appliance["endpointId"] == "fan#test_2"
assert appliance["displayCategories"][0] == "FAN"
assert appliance["friendlyName"] == "Test fan 2"
# Alexa.RangeController is added to make a fan controllable when no other controllers are available
# Alexa.RangeController is added to make a fan controllable
# when no other controllers are available
capabilities = assert_endpoint_capabilities(
appliance,
"Alexa.RangeController",
@ -597,7 +599,8 @@ async def test_variable_fan_no_current_speed(hass, caplog):
assert appliance["endpointId"] == "fan#test_3"
assert appliance["displayCategories"][0] == "FAN"
assert appliance["friendlyName"] == "Test fan 3"
# Alexa.RangeController is added to make a van controllable when no other controllers are available
# Alexa.RangeController is added to make a van controllable
# when no other controllers are available
capabilities = assert_endpoint_capabilities(
appliance,
"Alexa.RangeController",
@ -625,9 +628,9 @@ async def test_variable_fan_no_current_speed(hass, caplog):
"fan.percentage",
)
assert (
"Request Alexa.RangeController/AdjustRangeValue error INVALID_VALUE: Unable to determine fan.test_3 current fan speed"
in caplog.text
)
"Request Alexa.RangeController/AdjustRangeValue error "
"INVALID_VALUE: Unable to determine fan.test_3 current fan speed"
) in caplog.text
caplog.clear()

View File

@ -527,8 +527,9 @@ async def test_doorbell_event_fail(hass, aioclient_mock, caplog):
# Check we log the entity id of the failing entity
assert (
"Error when sending DoorbellPress event for binary_sensor.test_doorbell to Alexa: "
"THROTTLING_EXCEPTION: Request could not be processed due to throttling"
"Error when sending DoorbellPress event for binary_sensor.test_doorbell"
" to Alexa: THROTTLING_EXCEPTION: Request could not be processed"
" due to throttling"
) in caplog.text