Skip to content

Commit

Permalink
* Reworked G90Callback.invoke method to wrap regular callback function
Browse files Browse the repository at this point in the history
  into a coroutine and create a task on it afterwards in a unified
  manner, same way as asynchrounous callbacks are done. Also, callback
  exceptions are now reaped via done task's done callback, to avoid
  'Task exception has never been retrieved' error from `asyncio`
  • Loading branch information
hostcc committed Feb 15, 2022
1 parent 805e24d commit eb88c4d
Showing 1 changed file with 35 additions and 14 deletions.
49 changes: 35 additions & 14 deletions src/pyg90alarm/callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"""

import asyncio
import functools
from functools import (partial, wraps)
import logging

_LOGGER = logging.getLogger(__name__)
Expand All @@ -39,22 +39,43 @@ def invoke(callback, *args, **kwargs):
tbd
"""
if not callback:
return None
return

_LOGGER.debug('Attempting to invoke callback %s'
' (args: %s, kwargs: %s)',
callback, args, kwargs)
try:
if asyncio.iscoroutinefunction(callback):
if hasattr(asyncio, 'create_task'):
return asyncio.create_task(callback(*args, **kwargs))
# Python 3.6 has only `ensure_future` method
return asyncio.ensure_future(callback(*args, **kwargs))
return callback(*args, **kwargs)
except Exception as exc: # pylint: disable=broad-except
_LOGGER.error('Got exception when invoking'
' callback: %s', exc)
return None

if not asyncio.iscoroutinefunction(callback):
def async_wrapper(func):
"""
Wraps the regular callback function into coroutine, so it could
later be created as async task.
"""
@wraps(func)
async def wrapper(*args, **kwds):
return func(*args, **kwds)
return wrapper

callback = async_wrapper(callback)

if hasattr(asyncio, 'create_task'):
task = asyncio.create_task(callback(*args, **kwargs))
else:
# Python 3.6 has only `ensure_future` method
task = asyncio.ensure_future(callback(*args, **kwargs))

def reap_callback_exception(task):
"""
Reaps an exception (if any) from the task logging it, to prevent
`asyncio` reporting that task exception was never retrieved.
"""
exc = task.exception()
if exc:
_LOGGER.error('Got exception when invoking'
" callback '%s(...)': %s",
task.get_coro().__qualname__, exc)

task.add_done_callback(reap_callback_exception)

@staticmethod
def invoke_delayed(delay, callback, *args, **kwargs):
Expand All @@ -66,4 +87,4 @@ def invoke_delayed(delay, callback, *args, **kwargs):
else:
# Python 3.6 has no `get_running_loop`, only `get_event_loop`
loop = asyncio.get_event_loop()
loop.call_later(delay, functools.partial(callback, *args, **kwargs))
loop.call_later(delay, partial(callback, *args, **kwargs))

0 comments on commit eb88c4d

Please sign in to comment.