apprise: `notify` fails if used while an existing asyncio loop is running

📣 Notification Service(s) Impacted N/A

🐞 Describe the bug notify (incorrectly) assumes that if it has been called synchronously and a plugin is asynchronous that this means there is no asyncio loop running and attempts to create a new one. An error occurs because it tries to run run_until_complete but the loop is already running.

💡 Screenshots and Logs Sample code:

import apprise
import asyncio

url = "macosx://"

a = apprise.Apprise()
a.add(url)

def do_a_thing():
    # Do some regular synchronous code
    a.notify("Test")

async def main():
    while True:
        do_a_thing()
        await asyncio.sleep(1)


asyncio.run(main())

Error Log:

python test.py
Traceback (most recent call last):
  File "/Users/seb/git/unifi-protect-backup/unifi_protect_backup/test.py", line 21, in <module>
    asyncio.run(main())
  File "/Users/seb/.asdf/installs/python/3.9.1/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/Users/seb/.asdf/installs/python/3.9.1/lib/python3.9/asyncio/base_events.py", line 642, in run_until_complete
    return future.result()
  File "/Users/seb/git/unifi-protect-backup/unifi_protect_backup/test.py", line 17, in main
    do_a_thing()
  File "/Users/seb/git/unifi-protect-backup/unifi_protect_backup/test.py", line 12, in do_a_thing
    a.notify("Test")
  File "/Users/seb/Library/Caches/pypoetry/virtualenvs/unifi-protect-backup-A6mUIT2g-py3.9/lib/python3.9/site-packages/apprise/Apprise.py", line 447, in notify
    async_result = loop.run_until_complete(all_cor)
  File "/Users/seb/.asdf/installs/python/3.9.1/lib/python3.9/asyncio/base_events.py", line 618, in run_until_complete
    self._check_running()
  File "/Users/seb/.asdf/installs/python/3.9.1/lib/python3.9/asyncio/base_events.py", line 578, in _check_running
    raise RuntimeError('This event loop is already running')
RuntimeError: This event loop is already running
sys:1: RuntimeWarning: coroutine 'Apprise._async_notify_all' was never awaited
sys:1: RuntimeWarning: coroutine 'NotifyBase.async_notify' was never awaited

💻 Your System Details:

  • OS: MacOS
  • Python Version: 3.9.1

🔮 Additional context The code also tries to change the debug settings of the loop which it shouldn’t do if there is a loop in place already as this may have undesired consequences on the rest of the application.

It is not always possible to use async_notify if, like in the example, the notification needs to be called in a synchronous context. While the above example would be trivial to change to work, my target usecase is much more complicated and using async_notify is simply not an option.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 17 (12 by maintainers)

Most upvoted comments

I just thought I would test out nest_asyncio and that made me think, you could just document this edge case and move the dependency to the project wanting to use apprise in that way.

It’s useful knowledge without a doubt, but the problem is that if you were in a position to modify the original code in the first place, you could just adopt the async_notify() method and integrate with your event loop properly.

We’re in this pickle largely because for the longest time, the synchronous notify() method was the only way to interact with Apprise, so everyone adopted it.

The way nest_asyncio works is that it monkey patches asyncio, so all I needed to do was call it once somewhere once in the part of the code I do control. The rest of the code remained untouched and would fail previously.

In my usecase I am hooking apprise into the logging framework of python so I cant control the way that framework calls into my custom logging handler. But I can call nest_asyncio at the start of my code.

Having said all that, it’s definitely not a silver bullet, something about the combination of nest_asyncio and aiorun (which I use to bootstrap my application) breaks and I can no longer quit the application. Not an issue for apprise per-se but good to know it has drawbacks.

Edit: I just wasn’t calling it early enough it seems