mirror of https://github.com/caronc/apprise
Fix notify() failing when being called from an existing asynchronous event loop (#624)
parent
0be8f90244
commit
b34051ccaf
|
@ -424,7 +424,7 @@ class Apprise(object):
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# No notifications sent, and there was an internal error.
|
# No notifications sent, and there was an internal error.
|
||||||
return py3compat.asyncio.toasyncwrap(False)
|
return py3compat.asyncio.toasyncwrapvalue(False)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if len(coroutines) > 0:
|
if len(coroutines) > 0:
|
||||||
|
@ -433,7 +433,7 @@ class Apprise(object):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# No notifications sent.
|
# No notifications sent.
|
||||||
return py3compat.asyncio.toasyncwrap(None)
|
return py3compat.asyncio.toasyncwrapvalue(None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _notifyhandler(server, **kwargs):
|
def _notifyhandler(server, **kwargs):
|
||||||
|
@ -470,7 +470,7 @@ class Apprise(object):
|
||||||
# Send the notification immediately, and wrap the result in a
|
# Send the notification immediately, and wrap the result in a
|
||||||
# coroutine.
|
# coroutine.
|
||||||
status = Apprise._notifyhandler(server, **kwargs)
|
status = Apprise._notifyhandler(server, **kwargs)
|
||||||
return py3compat.asyncio.toasyncwrap(status)
|
return py3compat.asyncio.toasyncwrapvalue(status)
|
||||||
|
|
||||||
def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO,
|
def _notifyall(self, handler, body, title='', notify_type=NotifyType.INFO,
|
||||||
body_format=None, tag=MATCH_ALL_TAG, match_always=True,
|
body_format=None, tag=MATCH_ALL_TAG, match_always=True,
|
||||||
|
|
|
@ -63,8 +63,21 @@ def tosync(cor, debug=False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ASYNCIO_RUN_SUPPORT:
|
if ASYNCIO_RUN_SUPPORT:
|
||||||
|
try:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
|
except RuntimeError:
|
||||||
|
# There is no existing event loop, so we can start our own.
|
||||||
return asyncio.run(cor, debug=debug)
|
return asyncio.run(cor, debug=debug)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Enable debug mode
|
||||||
|
loop.set_debug(debug)
|
||||||
|
|
||||||
|
# Run the coroutine and wait for the result.
|
||||||
|
task = loop.create_task(cor)
|
||||||
|
return asyncio.ensure_future(task, loop=loop)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# The Deprecated Way (<= Python v3.6)
|
# The Deprecated Way (<= Python v3.6)
|
||||||
try:
|
try:
|
||||||
|
@ -85,7 +98,7 @@ def tosync(cor, debug=False):
|
||||||
return loop.run_until_complete(cor)
|
return loop.run_until_complete(cor)
|
||||||
|
|
||||||
|
|
||||||
async def toasyncwrap(v): # noqa: E999
|
async def toasyncwrapvalue(v): # noqa: E999
|
||||||
"""
|
"""
|
||||||
Create a coroutine that, when run, returns the provided value.
|
Create a coroutine that, when run, returns the provided value.
|
||||||
"""
|
"""
|
||||||
|
@ -93,6 +106,14 @@ async def toasyncwrap(v): # noqa: E999
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
async def toasyncwrap(fn): # noqa: E999
|
||||||
|
"""
|
||||||
|
Create a coroutine that, when run, executes the provided function.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return fn()
|
||||||
|
|
||||||
|
|
||||||
class AsyncNotifyBase(URLBase):
|
class AsyncNotifyBase(URLBase):
|
||||||
"""
|
"""
|
||||||
asyncio wrapper for the NotifyBase object
|
asyncio wrapper for the NotifyBase object
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
import six
|
||||||
import sys
|
import sys
|
||||||
import pytest
|
import pytest
|
||||||
from apprise import Apprise
|
from apprise import Apprise
|
||||||
|
@ -32,6 +33,9 @@ from apprise import NotifyFormat
|
||||||
|
|
||||||
from apprise.plugins import SCHEMA_MAP
|
from apprise.plugins import SCHEMA_MAP
|
||||||
|
|
||||||
|
if not six.PY2:
|
||||||
|
import apprise.py3compat.asyncio as py3aio
|
||||||
|
|
||||||
# Disable logging for a cleaner testing output
|
# Disable logging for a cleaner testing output
|
||||||
import logging
|
import logging
|
||||||
logging.disable(logging.CRITICAL)
|
logging.disable(logging.CRITICAL)
|
||||||
|
@ -106,3 +110,54 @@ def test_apprise_asyncio_runtime_error():
|
||||||
finally:
|
finally:
|
||||||
# Restore our event loop (in the event the above test failed)
|
# Restore our event loop (in the event the above test failed)
|
||||||
asyncio.set_event_loop(loop)
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(sys.version_info.major <= 2 or sys.version_info < (3, 7),
|
||||||
|
reason="Requires Python 3.7+")
|
||||||
|
def test_apprise_works_in_async_loop():
|
||||||
|
"""
|
||||||
|
API: Apprise() can execute synchronously in an existing event loop
|
||||||
|
|
||||||
|
"""
|
||||||
|
class GoodNotification(NotifyBase):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super(GoodNotification, self).__init__(
|
||||||
|
notify_format=NotifyFormat.HTML, **kwargs)
|
||||||
|
|
||||||
|
def url(self, **kwargs):
|
||||||
|
# Support URL
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def send(self, **kwargs):
|
||||||
|
# Pretend everything is okay
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_url(url, *args, **kwargs):
|
||||||
|
# always parseable
|
||||||
|
return NotifyBase.parse_url(url, verify_host=False)
|
||||||
|
|
||||||
|
# Store our good notification in our schema map
|
||||||
|
SCHEMA_MAP['good'] = GoodNotification
|
||||||
|
|
||||||
|
# Create ourselves an Apprise object
|
||||||
|
a = Apprise()
|
||||||
|
|
||||||
|
# Add a few entries
|
||||||
|
for _ in range(25):
|
||||||
|
a.add('good://')
|
||||||
|
|
||||||
|
# To ensure backwards compatibility, it should be possible to call
|
||||||
|
# asynchronous Apprise methods from code that already uses an event loop,
|
||||||
|
# even when using the synchronous notify() method.
|
||||||
|
# see https://github.com/caronc/apprise/issues/610
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
def try_notify():
|
||||||
|
a.notify(title="title", body="body")
|
||||||
|
|
||||||
|
# Convert to a coroutine to run asynchronously.
|
||||||
|
cor = py3aio.toasyncwrap(try_notify)
|
||||||
|
|
||||||
|
# Should execute successfully.
|
||||||
|
asyncio.run(cor)
|
||||||
|
|
Loading…
Reference in New Issue