How to use python zeroconf in an asyncio application?

197 views Asked by At

Here's a minimal reproducible example demonstrating the problem:

import asyncio
from zeroconf import Zeroconf, ServiceStateChange
from zeroconf.asyncio import AsyncServiceBrowser

def on_service_state_change(zeroconf, service_type, name, state_change):
    if state_change == ServiceStateChange.Added:
        info = zeroconf.get_service_info(service_type, name)
        print(info)

async def browse_services():
    zeroconf = Zeroconf()
    service_type = "_http._tcp.local."

    browser = AsyncServiceBrowser(zeroconf, service_type, handlers=[on_service_state_change])

    try:
        await asyncio.Event().wait()
    finally:
        zeroconf.close()

if __name__ == "__main__":
    asyncio.run(browse_services())

This fails, like this:

Exception in callback _SelectorDatagramTransport._read_ready()
handle: <Handle _SelectorDatagramTransport._read_ready()>
Traceback (most recent call last):
  File "/usr/lib/python3.10/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/usr/lib/python3.10/asyncio/selector_events.py", line 1035, in _read_ready
    self._protocol.datagram_received(data, addr)
  File "src/zeroconf/_listener.py", line 80, in zeroconf._listener.AsyncListener.datagram_received
  File "src/zeroconf/_listener.py", line 161, in zeroconf._listener.AsyncListener.datagram_received
  File "src/zeroconf/_handlers/record_manager.py", line 162, in zeroconf._handlers.record_manager.RecordManager.async_updates_from_response
  File "src/zeroconf/_handlers/record_manager.py", line 71, in zeroconf._handlers.record_manager.RecordManager.async_updates_complete
  File "src/zeroconf/_services/browser.py", line 441, in zeroconf._services.browser._ServiceBrowserBase.async_update_records_complete
  File "src/zeroconf/_services/browser.py", line 451, in zeroconf._services.browser._ServiceBrowserBase.async_update_records_complete
  File "src/zeroconf/_services/browser.py", line 454, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "src/zeroconf/_services/browser.py", line 454, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "src/zeroconf/_services/browser.py", line 464, in zeroconf._services.browser._ServiceBrowserBase._fire_service_state_changed_event
  File "/home/tkcook/.virtualenvs/veeahub/lib/python3.10/site-packages/zeroconf/_services/__init__.py", line 56, in fire
    h(**kwargs)
  File "/home/tkcook/Scratch/zeroconf_test.py", line 7, in on_service_state_change
    info = zeroconf.get_service_info(service_type, name)
  File "/home/tkcook/.virtualenvs/veeahub/lib/python3.10/site-packages/zeroconf/_core.py", line 270, in get_service_info
    if info.request(self, timeout, question_type):
  File "src/zeroconf/_services/info.py", line 752, in zeroconf._services.info.ServiceInfo.request
RuntimeError: Use AsyncServiceInfo.async_request from the event loop

I can't use async_request() here (and by implication async_get_service_info()) because the service listener is not async and so the resulting coroutine can't be awaited. As far as I can tell, there is no way to add an async service listener. AsyncZeroconf.async_add_service_listener does not result in the callback functions being awaited and therefore doesn't help with this.

Am I missing something? Is there some way to do this that I'm missing?

0

There are 0 answers