Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ServiceBrowser handler argument overwrites zeroconf name #1092

Open
nils-werner opened this issue Sep 13, 2022 · 1 comment
Open

ServiceBrowser handler argument overwrites zeroconf name #1092

nils-werner opened this issue Sep 13, 2022 · 1 comment

Comments

@nils-werner
Copy link

In the default pattern

import zeroconf

instance = zeroconf.Zeroconf()
browser = zeroconf.ServiceBrowser(
    instance,
    ['_osc._udp.local.'],
    handlers=[handler]
)

def handler(zeroconf, service_type, name, state_change):
    # I cannot use zeroconf.ServiceStateChange.Added here, because zeroconf is not the import but the argument
    pass

we have the problem that handler() is receiving a named argument zeroconf that overloads import zeroconf and that we cannot change. If I defined

def handler(zc, service_type, name, state_change):
    if state_change is zeroconf.ServiceStateChange.Added:
        print(name)

I would receive

TypeError: handler() got an unexpected keyword argument 'zeroconf'

To avoid it I would have to alias the import, which is a bit ugly:

import zeroconf as imported_zeroconf

def handler(zeroconf, service_type, name, state_change):
    if state_change is imported_zeroconf.ServiceStateChange.Added:
        print(name)

It would be nice if the arguments were to be passed in as positional arguments, or named differently, so that the names didn't clash.

@maxtruxa
Copy link

maxtruxa commented Feb 1, 2023

Quick workaround using a decorator:

from functools import wraps

def zeroconf_handler(handler):
    # `wrapper`'s argument names are important!
    @wraps(handler)
    def wrapper(zeroconf, service_type, name, state_change):
        handler(zeroconf, service_type, name, state_change)
    return wrapper

# ...

@zeroconf_handler
def handler(zc, type_, name, new_state):
    pass

browser = zeroconf.ServiceBrowser(
    instance,
    ['_osc._udp.local.'],
    handlers=[handler]
)

Just for completeness sake, this can easily be extended to support async callbacks, which prevents incorrect usage of ensure_future()/create_task() demonstrated in all the examples:

import asyncio
from functools import wraps

_zeroconf_pending_handler_tasks = set()
def async_zeroconf_handler(handler):
    # `wrapper`'s argument names are important!
    @wraps(handler)
    def wrapper(zeroconf, service_type, name, state_change):
        # Keep a reference to the task around until it's done, to prevent it from being garbage collected.
        task = asyncio.create_task(handler(zeroconf, service_type, name, state_change))
        _zeroconf_pending_handler_tasks.add(task)
        task.add_done_callback(_zeroconf_pending_handler_tasks.discard)
    return wrapper

# ...

@async_zeroconf_handler
async def handler(zc, type_, name, new_state):
    await asyncio.sleep(1)

browser = zeroconf.ServiceBrowser(
    instance,
    ['_osc._udp.local.'],
    handlers=[handler]
)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants