Skip to content

Commit aaf8a36

Browse files
authored
Add an AsyncZeroconfServiceTypes to mirror ZeroconfServiceTypes to zeroconf.aio (#658)
1 parent 72db0c1 commit aaf8a36

2 files changed

Lines changed: 100 additions & 4 deletions

File tree

tests/test_aio.py

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
"""Unit tests for aio.py."""
66

77
import asyncio
8+
import logging
89
import socket
910
import threading
1011
import unittest.mock
1112

1213
import pytest
1314

14-
from zeroconf.aio import AsyncServiceInfo, AsyncServiceListener, AsyncZeroconf
15+
from zeroconf.aio import AsyncServiceInfo, AsyncServiceListener, AsyncZeroconf, AsyncZeroconfServiceTypes
1516
from zeroconf import Zeroconf
1617
from zeroconf.const import _LISTENER_TIME
1718
from zeroconf._exceptions import BadTypeInNameException, NonUniqueNameException, ServiceNameAlreadyRegistered
@@ -20,8 +21,19 @@
2021

2122
from . import _clear_cache
2223

24+
log = logging.getLogger('zeroconf')
25+
original_logging_level = logging.NOTSET
26+
27+
28+
def setup_module():
29+
global original_logging_level
30+
original_logging_level = log.level
31+
log.setLevel(logging.DEBUG)
2332

24-
from . import _clear_cache
33+
34+
def teardown_module():
35+
if original_logging_level != logging.NOTSET:
36+
log.setLevel(original_logging_level)
2537

2638

2739
@pytest.fixture(autouse=True)
@@ -558,3 +570,37 @@ async def test_async_unregister_all_services() -> None:
558570
await aiozc.async_unregister_all_services()
559571

560572
await aiozc.async_close()
573+
574+
575+
@pytest.mark.asyncio
576+
async def test_async_zeroconf_service_types():
577+
type_ = "_test-srvc-type._tcp.local."
578+
name = "xxxyyy"
579+
registration_name = "%s.%s" % (name, type_)
580+
581+
zeroconf_registrar = AsyncZeroconf(interfaces=['127.0.0.1'])
582+
desc = {'path': '/~paulsm/'}
583+
info = ServiceInfo(
584+
type_,
585+
registration_name,
586+
80,
587+
0,
588+
0,
589+
desc,
590+
"ash-2.local.",
591+
addresses=[socket.inet_aton("10.0.1.2")],
592+
)
593+
task = await zeroconf_registrar.async_register_service(info)
594+
await task
595+
# Ensure we do not clear the cache until after the last broadcast is processed
596+
await asyncio.sleep(0.2)
597+
_clear_cache(zeroconf_registrar.zeroconf)
598+
try:
599+
service_types = await AsyncZeroconfServiceTypes.async_find(interfaces=['127.0.0.1'], timeout=0.5)
600+
assert type_ in service_types
601+
_clear_cache(zeroconf_registrar.zeroconf)
602+
service_types = await AsyncZeroconfServiceTypes.async_find(aiozc=zeroconf_registrar, timeout=0.5)
603+
assert type_ in service_types
604+
605+
finally:
606+
await zeroconf_registrar.async_close()

zeroconf/aio.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,32 @@
2222
import asyncio
2323
import contextlib
2424
from types import TracebackType # noqa # used in type hints
25-
from typing import Awaitable, Callable, Dict, List, Optional, Type, Union
25+
from typing import Awaitable, Callable, Dict, List, Optional, Tuple, Type, Union
2626

2727
from ._core import NotifyListener, Zeroconf
2828
from ._exceptions import NonUniqueNameException
2929
from ._services import ServiceInfo, _ServiceBrowserBase, instance_name_from_service_info
30+
from ._services.types import ZeroconfServiceTypes
3031
from ._utils.aio import wait_condition_or_timeout
3132
from ._utils.net import IPVersion, InterfaceChoice, InterfacesType
3233
from ._utils.time import current_time_millis, millis_to_seconds
33-
from .const import _BROWSER_TIME, _CHECK_TIME, _LISTENER_TIME, _MDNS_PORT, _REGISTER_TIME, _UNREGISTER_TIME
34+
from .const import (
35+
_BROWSER_TIME,
36+
_CHECK_TIME,
37+
_LISTENER_TIME,
38+
_MDNS_PORT,
39+
_REGISTER_TIME,
40+
_SERVICE_TYPE_ENUMERATION_NAME,
41+
_UNREGISTER_TIME,
42+
)
3443

3544

3645
__all__ = [
3746
"AsyncZeroconf",
3847
"AsyncServiceInfo",
3948
"AsyncServiceBrowser",
4049
"AsyncServiceListener",
50+
"AsyncZeroconfServiceTypes",
4151
]
4252

4353

@@ -137,6 +147,7 @@ async def async_cancel(self) -> None:
137147
async def async_run(self) -> None:
138148
"""Run the browser task."""
139149
self.run()
150+
await self.aiozc.zeroconf.async_wait_for_start()
140151
while True:
141152
timeout = self._seconds_to_wait()
142153
if timeout:
@@ -164,6 +175,45 @@ async def async_run(self) -> None:
164175
)
165176

166177

178+
class AsyncZeroconfServiceTypes(ZeroconfServiceTypes):
179+
"""An async version of ZeroconfServiceTypes."""
180+
181+
@classmethod
182+
async def async_find(
183+
cls,
184+
aiozc: Optional['AsyncZeroconf'] = None,
185+
timeout: Union[int, float] = 5,
186+
interfaces: InterfacesType = InterfaceChoice.All,
187+
ip_version: Optional[IPVersion] = None,
188+
) -> Tuple[str, ...]:
189+
"""
190+
Return all of the advertised services on any local networks.
191+
192+
:param aiozc: AsyncZeroconf() instance. Pass in if already have an
193+
instance running or if non-default interfaces are needed
194+
:param timeout: seconds to wait for any responses
195+
:param interfaces: interfaces to listen on.
196+
:param ip_version: IP protocol version to use.
197+
:return: tuple of service type strings
198+
"""
199+
local_zc = aiozc or AsyncZeroconf(interfaces=interfaces, ip_version=ip_version)
200+
listener = cls()
201+
async_browser = AsyncServiceBrowser(
202+
local_zc, _SERVICE_TYPE_ENUMERATION_NAME, listener=listener # type: ignore
203+
)
204+
205+
# wait for responses
206+
await asyncio.sleep(timeout)
207+
208+
await async_browser.async_cancel()
209+
210+
# close down anything we opened
211+
if aiozc is None:
212+
await local_zc.async_close()
213+
214+
return tuple(sorted(listener.found_services))
215+
216+
167217
class AsyncZeroconf:
168218
"""Implementation of Zeroconf Multicast DNS Service Discovery
169219

0 commit comments

Comments
 (0)