Skip to content

Request: async methods on ServiceListener #1075

@dge8

Description

@dge8

Hello,

I've started using python-zeroconf in an async project, and would like to ask whether it could be made possible for the ServiceListener callbacks (add_service, update_service and remove_service) to be async coroutines instead of sync functions.

To illustrate I've rewritten the service browser example in the Readme using async methods. Note that async_add_service_listener() along with async with really tidy up the main loop, but the sync-async friction makes the ServiceListener a bit messy (plus need to handle the AsyncZeroconf object separately).

(A related question linked to this example: which thread executes the ServiceListener callbacks? Should I be using asyncio.create_task() or asyncio.run_coroutine_threadsafe()?)

Async MWE of service browser in readme
import asyncio, sys
from zeroconf import ServiceListener, Zeroconf
from zeroconf.asyncio import AsyncZeroconf

SERVICE_TYPE = "_http._tcp.local."

class MyListener(ServiceListener):

    def __init__(self, aiozc: AsyncZeroconf):
        self.aiozc = aiozc

    def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"Service {name} updated")

    def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        print(f"Service {name} removed")

    def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        # Should this be asyncio.run_coroutine_threadsafe() for thread safety?
        asyncio.create_task(self.async_add_service(zc, type_, name))

    async def async_add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
        info = await self.aiozc.async_get_service_info(type_, name)
        print(f"Service {name} added, service info: {info}")

async def get_all_services():
    async with AsyncZeroconf() as aiozc:
        listener = MyListener(aiozc)
        await aiozc.async_add_service_listener(SERVICE_TYPE, listener)
        while True:
            await asyncio.sleep(10)

try:
    print("Press Ctrl-C to exit...\n")
    asyncio.run(get_all_services())
except KeyboardInterrupt:
    sys.exit()

It would be much nicer to be able to do the following instead, perhaps via an AsyncServiceListener class:

class MyAsyncListener(AsyncServiceListener):

    async def async_update_service(self, aiozc: AsyncZeroconf, type_: str, name: str) -> None:
        print(f"Service {name} updated")

    async def async_remove_service(self, aiozc: AsyncZeroconf, type_: str, name: str) -> None:
        print(f"Service {name} removed")

    async def async_add_service(self, aiozc: AsyncZeroconf, type_: str, name: str) -> None:
        info = await aiozc.async_get_service_info(type_, name)
        print(f"Service {name} added, service info: {info}")

async def get_all_services():
    async with AsyncZeroconf() as aiozc:
        listener = MyAsyncListener()
        await aiozc.async_add_service_listener(SERVICE_TYPE, listener)
        while True:
            await asyncio.sleep(10)

What do others think? Is there a better way to achieve what I'm after?

Thanks,
Dan.

P.S. Thank you to everyone who's put time into this project!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions