Skip to content

Commit 2d8a27a

Browse files
authored
Move asyncio utils into zeroconf.utils.aio (#530)
1 parent 3f1a5a7 commit 2d8a27a

6 files changed

Lines changed: 136 additions & 31 deletions

File tree

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ flake8:
2929
flake8 --max-line-length=$(MAX_LINE_LENGTH) setup.py examples zeroconf
3030

3131
pylint:
32-
pylint zeroconf/__init__.py zeroconf/aio.py zeroconf/asyncio.py
32+
pylint zeroconf
3333

3434
.PHONY: black_check
3535
black_check:
3636
black --check setup.py examples zeroconf
3737

3838
mypy:
39-
mypy examples/*.py zeroconf/*.py
39+
# --no-warn-redundant-casts --no-warn-unused-ignores is needed since we support multiple python versions
40+
# We should be able to drop this once python 3.6 goes away
41+
mypy --no-warn-redundant-casts --no-warn-unused-ignores examples/*.py zeroconf
4042

4143
test:
4244
pytest -v tests

tests/utils/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
""" Multicast DNS Service Discovery for Python, v0.14-wmcbrine
2+
Copyright 2003 Paul Scott-Murphy, 2014 William McBrine
3+
4+
This module provides a framework for the use of DNS Service Discovery
5+
using IP multicast.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20+
USA
21+
"""

tests/utils/test_aio.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
5+
"""Unit tests for zeroconf.utils.aio."""
6+
7+
import asyncio
8+
9+
import pytest
10+
11+
from zeroconf.utils import aio as aioutils
12+
13+
14+
@pytest.mark.asyncio
15+
async def test_get_running_loop_from_async() -> None:
16+
"""Test we can get the event loop."""
17+
assert isinstance(aioutils.get_running_loop(), asyncio.AbstractEventLoop)
18+
19+
20+
def test_get_running_loop_no_loop() -> None:
21+
"""Test we get None when there is no loop running."""
22+
assert aioutils.get_running_loop() is None

zeroconf/aio.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
instance_name_from_service_info,
4747
millis_to_seconds,
4848
)
49+
from .utils.aio import wait_condition_or_timeout
4950

5051

5152
def _get_best_available_queue() -> queue.Queue:
@@ -55,35 +56,6 @@ def _get_best_available_queue() -> queue.Queue:
5556
return queue.Queue()
5657

5758

58-
# Switch to asyncio.wait_for once https://bugs.python.org/issue39032 is fixed
59-
async def wait_condition_or_timeout(condition: asyncio.Condition, timeout: float) -> None:
60-
"""Wait for a condition or timeout."""
61-
loop = asyncio.get_event_loop()
62-
future = loop.create_future()
63-
64-
def _handle_timeout() -> None:
65-
if not future.done():
66-
future.set_result(None)
67-
68-
timer_handle = loop.call_later(timeout, _handle_timeout)
69-
condition_wait = loop.create_task(condition.wait())
70-
71-
def _handle_wait_complete(_: asyncio.Task) -> None:
72-
if not future.done():
73-
future.set_result(None)
74-
75-
condition_wait.add_done_callback(_handle_wait_complete)
76-
77-
try:
78-
await future
79-
finally:
80-
timer_handle.cancel()
81-
if not condition_wait.done():
82-
condition_wait.cancel()
83-
with contextlib.suppress(asyncio.CancelledError):
84-
await condition_wait
85-
86-
8759
class _AsyncSender(threading.Thread):
8860
"""A thread to handle sending DNSOutgoing for asyncio."""
8961

zeroconf/utils/__init__.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
""" Multicast DNS Service Discovery for Python, v0.14-wmcbrine
2+
Copyright 2003 Paul Scott-Murphy, 2014 William McBrine
3+
4+
This module provides a framework for the use of DNS Service Discovery
5+
using IP multicast.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20+
USA
21+
"""

zeroconf/utils/aio.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
""" Multicast DNS Service Discovery for Python, v0.14-wmcbrine
2+
Copyright 2003 Paul Scott-Murphy, 2014 William McBrine
3+
4+
This module provides a framework for the use of DNS Service Discovery
5+
using IP multicast.
6+
7+
This library is free software; you can redistribute it and/or
8+
modify it under the terms of the GNU Lesser General Public
9+
License as published by the Free Software Foundation; either
10+
version 2.1 of the License, or (at your option) any later version.
11+
12+
This library is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
Lesser General Public License for more details.
16+
17+
You should have received a copy of the GNU Lesser General Public
18+
License along with this library; if not, write to the Free Software
19+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20+
USA
21+
"""
22+
23+
import asyncio
24+
import contextlib
25+
from typing import Optional, cast
26+
27+
28+
# Switch to asyncio.wait_for once https://bugs.python.org/issue39032 is fixed
29+
async def wait_condition_or_timeout(condition: asyncio.Condition, timeout: float) -> None:
30+
"""Wait for a condition or timeout."""
31+
loop = asyncio.get_event_loop()
32+
future = loop.create_future()
33+
34+
def _handle_timeout() -> None:
35+
if not future.done():
36+
future.set_result(None)
37+
38+
timer_handle = loop.call_later(timeout, _handle_timeout)
39+
condition_wait = loop.create_task(condition.wait())
40+
41+
def _handle_wait_complete(_: asyncio.Task) -> None:
42+
if not future.done():
43+
future.set_result(None)
44+
45+
condition_wait.add_done_callback(_handle_wait_complete)
46+
47+
try:
48+
await future
49+
finally:
50+
timer_handle.cancel()
51+
if not condition_wait.done():
52+
condition_wait.cancel()
53+
with contextlib.suppress(asyncio.CancelledError):
54+
await condition_wait
55+
56+
57+
# Remove the call to _get_running_loop once we drop python 3.6 support
58+
def get_running_loop() -> Optional[asyncio.AbstractEventLoop]:
59+
"""Check if an event loop is already running."""
60+
with contextlib.suppress(RuntimeError):
61+
if hasattr(asyncio, "get_running_loop"):
62+
return cast(
63+
asyncio.AbstractEventLoop,
64+
asyncio.get_running_loop(), # type: ignore # pylint: disable=no-member # noqa
65+
)
66+
return asyncio._get_running_loop() # pylint: disable=no-member,protected-access
67+
return None

0 commit comments

Comments
 (0)