Skip to content

Commit

Permalink
fix: add retry_async wrapper to automatically retry after failures
Browse files Browse the repository at this point in the history
This will allow platforms to wait for media_player to load before trying 
to load
  • Loading branch information
alandtse committed Sep 4, 2019
1 parent ad9b95b commit 2ce12f4
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 20 deletions.
66 changes: 64 additions & 2 deletions custom_components/alexa_media/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
"""

import logging
from typing import List, Text
from typing import List, Text, Callable
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_component import EntityComponent

_LOGGER = logging.getLogger(__name__)


async def add_devices(account: Text, devices: List[EntityComponent],
async def add_devices(account: Text,
devices: List[EntityComponent],
add_devices_callback: callable,
include_filter: List[Text] = [],
exclude_filter: List[Text] = []) -> bool:
Expand Down Expand Up @@ -56,3 +57,64 @@ async def add_devices(account: Text, devices: List[EntityComponent],
message)

return False


def retry_async(limit: int = 5,
delay: float = 1,
catch_exceptions: bool = True
) -> Callable:
"""Wrap function with retry logic.
The function will retry until true or the limit is reached. It will delay
for the period of time specified exponentialy increasing the delay.
Parameters
----------
limit : int
The max number of retries.
delay : float
The delay in seconds between retries.
catch_exceptions : bool
Whether exceptions should be caught and treated as failures or thrown.
Returns
-------
def
Wrapped function.
"""
def wrap(func) -> Callable:
import functools
import asyncio
@functools.wraps(func)
async def wrapper(*args, **kwargs) -> Callable:
_LOGGER.debug(
"%s: Trying with limit %s delay %s catch_exceptions %s",
func.__name__,
limit,
delay,
catch_exceptions)
retries: int = 0
result: bool = False
while (not result and retries < limit):
if retries != 0:
await asyncio.sleep(delay * 2 ** retries)
retries += 1
try:
result = await func(*args, **kwargs)
except Exception as ex: # pylint: disable=broad-except
if not catch_exceptions:
raise
template = ("An exception of type {0} occurred."
" Arguments:\n{1!r}")
message = template.format(type(ex).__name__, ex.args)
_LOGGER.debug("%s: failure caught due to exception: %s",
func.__name__,
message)
_LOGGER.debug("%s: Try: %s/%s result: %s",
func.__name__,
retries,
limit,
result)
return result
return wrapper
return wrap
15 changes: 11 additions & 4 deletions custom_components/alexa_media/notify.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,24 @@
DOMAIN as ALEXA_DOMAIN,
DATA_ALEXAMEDIA,
hide_email, hide_serial)
from .helpers import retry_async

_LOGGER = logging.getLogger(__name__)

DEPENDENCIES = [ALEXA_DOMAIN]

EVENT_NOTIFY = "notify"


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_get_service(hass, config, discovery_info=None):
# pylint: disable=unused-argument
"""Get the demo notification service."""
for account, account_dict in (
hass.data[DATA_ALEXAMEDIA]['accounts'].items()):
for key, device in account_dict['devices']['media_player'].items():
if key not in account_dict['entities']['media_player']:
_LOGGER.debug(
"%s: Media player %s not loaded yet; delaying load",
hide_email(account),
hide_serial(key))
return False
return AlexaNotificationService(hass)


Expand Down
32 changes: 18 additions & 14 deletions custom_components/alexa_media/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@
hide_email, hide_serial, CONF_EMAIL,
CONF_EXCLUDE_DEVICES, CONF_INCLUDE_DEVICES
)
from .helpers import add_devices
from .helpers import add_devices, retry_async

_LOGGER = logging.getLogger(__name__)


@retry_async(limit=5, delay=2, catch_exceptions=True)
async def async_setup_platform(hass, config, add_devices_callback,
discovery_info=None):
"""Set up the Alexa switch platform."""
_LOGGER.debug("Loading switches")
devices = [] # type: List[DNDSwitch]
SWITCH_TYPES = [
('dnd', DNDSwitch),
Expand All @@ -42,6 +42,8 @@ async def async_setup_platform(hass, config, add_devices_callback,
include_filter = config.get(CONF_INCLUDE_DEVICES, [])
exclude_filter = config.get(CONF_EXCLUDE_DEVICES, [])
account_dict = hass.data[DATA_ALEXAMEDIA]['accounts'][account]
_LOGGER.debug("%s: Loading switches",
hide_email(account))
if 'switch' not in account_dict['entities']:
(hass.data[DATA_ALEXAMEDIA]
['accounts']
Expand All @@ -50,14 +52,10 @@ async def async_setup_platform(hass, config, add_devices_callback,
['switch']) = {}
for key, device in account_dict['devices']['media_player'].items():
if key not in account_dict['entities']['media_player']:
_LOGGER.debug("Media Players not loaded yet; delaying load")
async_call_later(hass, 5, lambda _:
hass.async_create_task(
async_setup_platform(hass,
config,
add_devices_callback,
discovery_info)))
return True
_LOGGER.debug("%s: Media player %s not loaded yet; delaying load",
hide_email(account),
hide_serial(key))
return False
if key not in (hass.data[DATA_ALEXAMEDIA]
['accounts']
[account]
Expand Down Expand Up @@ -87,14 +85,20 @@ async def async_setup_platform(hass, config, add_devices_callback,
[key]
[switch_key]) = alexa_client
else:
_LOGGER.debug("%s: Skipping already added device: %s:%s",
hide_email(account),
key,
alexa_client)
for alexa_client in (hass.data[DATA_ALEXAMEDIA]
['accounts']
[account]
['entities']
['switch']
[key].values()):
_LOGGER.debug("%s: Skipping already added device: %s",
hide_email(account),
alexa_client)
return await add_devices(hide_email(account),
devices, add_devices_callback,
include_filter, exclude_filter)


class AlexaMediaSwitch(SwitchDevice):
"""Representation of a Alexa Media switch."""

Expand Down

0 comments on commit 2ce12f4

Please sign in to comment.