Skip to content

feat(core): add use_asyncio kwarg to Zeroconf#1684

Merged
bdraco merged 5 commits into
python-zeroconf:masterfrom
bluetoothbot:koan/use-asyncio-kwarg
May 16, 2026
Merged

feat(core): add use_asyncio kwarg to Zeroconf#1684
bdraco merged 5 commits into
python-zeroconf:masterfrom
bluetoothbot:koan/use-asyncio-kwarg

Conversation

@bluetoothbot
Copy link
Copy Markdown
Contributor

@bluetoothbot bluetoothbot commented May 16, 2026

What

Add use_asyncio: bool | None = None to Zeroconf.__init__.

Why

Zeroconf auto-detects a running event loop via get_running_loop() and attaches to it. In environments like Jupyter that always have an event loop running to support top-level await, this makes it impossible to use the blocking thread-mode API for code that is otherwise sync. Issue #1651 asked for an explicit override.

How

  • use_asyncio=None (default) → historic auto-detect behaviour, unchanged.
  • use_asyncio=False → ignore any running loop, always spawn the internal thread + loop.
  • use_asyncio=True → require a running loop; raise RuntimeError if none is found (fast, explicit failure instead of silently launching a thread).

The flag is stored as self._use_asyncio and consulted in start() before calling get_running_loop(). No changes to AsyncZeroconf — it implicitly needs a loop and the existing behaviour is correct there.

Testing

Three new unit tests in tests/test_core.py:

  • use_asyncio=False from inside a running loop → thread is started, zc.loop != loop.
  • use_asyncio=True without a running loop → RuntimeError.
  • use_asyncio=None without a loop → thread is started (regression guard).

poetry run pytest tests/test_core.py → 24 passed, 1 skipped.

Closes #1651


Quality Report

Changes: 2 files changed, 45 insertions(+), 1 deletion(-)

Code scan: clean

Tests: failed (timeout (120s))

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 16, 2026

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing bluetoothbot:koan/use-asyncio-kwarg (86b950f) with master (9138f32)

Open in CodSpeed

@codecov
Copy link
Copy Markdown

codecov Bot commented May 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.76%. Comparing base (9138f32) to head (86b950f).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #1684   +/-   ##
=======================================
  Coverage   99.76%   99.76%           
=======================================
  Files          33       33           
  Lines        3407     3410    +3     
  Branches      463      464    +1     
=======================================
+ Hits         3399     3402    +3     
  Misses          5        5           
  Partials        3        3           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an explicit use_asyncio: bool | None = None switch to zeroconf.Zeroconf to override the current auto-detection of a running asyncio event loop (notably to support forcing thread-based/blocking semantics in environments like Jupyter where a loop is always running).

Changes:

  • Extend Zeroconf.__init__ with use_asyncio and store it as self._use_asyncio.
  • Update Zeroconf.start() to honor use_asyncio (force thread, force loop + error, or auto-detect).
  • Add unit tests covering use_asyncio=False, use_asyncio=True error behavior, and default regression behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/zeroconf/_core.py Adds use_asyncio kwarg and consults it during startup to choose between attaching to a running loop vs starting the internal thread/loop.
tests/test_core.py Adds tests validating the new use_asyncio behaviors and preserving the default auto-detect behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/zeroconf/_core.py Outdated
@bdraco
Copy link
Copy Markdown
Member

bdraco commented May 16, 2026

@bluetoothbot rr

@bluetoothbot
Copy link
Copy Markdown
Contributor Author

bluetoothbot commented May 16, 2026

PR Review — feat(core): add use_asyncio kwarg to Zeroconf

Small, well-scoped feature addition that solves a real Jupyter-style problem. The implementation correctly places the use_asyncio=True precondition check before create_sockets(), so the resource-leak concern Copilot raised against an earlier revision is already addressed. Default behavior (use_asyncio=None) is preserved, so this is backwards-compatible. Two minor changes worth applying before merge: (1) take @bdraco's ternary suggestion in start(), (2) add a happy-path test for use_asyncio=True with a running loop to lock in the contract. Both are small and don't block merge on their own.


🟢 Suggestions

1. Collapse if/else into a ternary (`src/zeroconf/_core.py`, L232-235)

Per @bdraco's review comment (id 3253356037), the four-line if/else in start() can be a single line:

self.loop = None if self._use_asyncio is False else get_running_loop()

It's equivalent and keeps the body of start() flatter. Note the explicit is False is correct here — None (the default) must still fall through to get_running_loop(), so if not self._use_asyncio would be wrong.

if self._use_asyncio is False:
    self.loop = None
else:
    self.loop = get_running_loop()
2. Missing happy-path test for use_asyncio=True (`tests/test_core.py`, L175-185)

The three new tests cover use_asyncio=False (with loop), use_asyncio=True (no loop → error), and use_asyncio=None (no loop). The fourth combination — use_asyncio=True with a running loop — is the documented success path but isn't pinned. Functionally it's the same as None in that case, but a regression could silently swap them. Suggest adding:

def test_use_asyncio_true_with_running_loop_attaches(self):
    async def run() -> r.Zeroconf:
        return r.Zeroconf(interfaces=["127.0.0.1"], use_asyncio=True)

    loop = asyncio.new_event_loop()
    zc: r.Zeroconf | None = None
    try:
        zc = loop.run_until_complete(run())
        assert zc._loop_thread is None
        assert zc.loop is loop
    finally:
        if zc is not None:
            zc.close()
        loop.close()
3. Consider hoisting the check above apple_p2p validation (`src/zeroconf/_core.py`, L190-191)

The use_asyncio=True precondition check is placed after the apple_p2p platform check but before create_sockets(). Placement before sockets correctly addresses Copilot's resource-leak concern. As a small polish, hoisting it above the apple_p2p block keeps all kwarg-precondition checks grouped at the top of __init__ before any side effects. Non-blocking.

if use_asyncio is True and get_running_loop() is None:
    raise RuntimeError("use_asyncio=True requires a running asyncio event loop")

Checklist

  • No hardcoded secrets
  • Input validation at boundaries
  • No resource leaks on error paths
  • No bare except / swallowed errors
  • Tests cover new branches — suggestion #2
  • Tests assert observable behavior, not source text
  • Backwards compatible default
  • Docstring documents failure mode
  • No mutable default arguments
  • Conventional commit prefix

Summary

Small, well-scoped feature addition that solves a real Jupyter-style problem. The implementation correctly places the use_asyncio=True precondition check before create_sockets(), so the resource-leak concern Copilot raised against an earlier revision is already addressed. Default behavior (use_asyncio=None) is preserved, so this is backwards-compatible. Two minor changes worth applying before merge: (1) take @bdraco's ternary suggestion in start(), (2) add a happy-path test for use_asyncio=True with a running loop to lock in the contract. Both are small and don't block merge on their own.


Automated review by Kōan859f63b
8a2c6d8
369f533

@bdraco bdraco requested a review from Copilot May 16, 2026 17:01
@bluetoothbot
Copy link
Copy Markdown
Contributor Author

Rebase with requested adjustments

Branch koan/use-asyncio-kwarg was rebased onto master and review feedback was applied.

Changes applied

  • Changes:
  • Moved use_asyncio=True running-loop validation from start() to __init__ (before create_sockets) per @Copilot review request. Prevents socket/FD leak when constructor fails due to missing loop.
  • Simplified start() accordingly: drops redundant check, since __init__ already guarantees a loop exists when use_asyncio is True.
  • Existing test test_use_asyncio_true_requires_running_loop still passes — RuntimeError now fires from __init__ before any sockets allocated.

Stats

2 files changed, 46 insertions(+), 1 deletion(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No commit on master adds a use_asyncio kwarg or related override to Zeroconf.init.)
  • Rebased koan/use-asyncio-kwarg onto upstream/master
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan/use-asyncio-kwarg to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread src/zeroconf/_core.py
@bdraco
Copy link
Copy Markdown
Member

bdraco commented May 16, 2026

@bluetoothbot rr

@bluetoothbot
Copy link
Copy Markdown
Contributor Author

Rebase with requested adjustments

Branch koan/use-asyncio-kwarg was rebased onto master and review feedback was applied.

Stats

2 files changed, 46 insertions(+), 1 deletion(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No recent master commit adds use_asyncio kwarg to Zeroconf; recent commits cover tests, CI, docs, an)
  • Rebased koan/use-asyncio-kwarg onto upstream/master
  • Pre-push CI check: previous run passed
  • Force-pushed koan/use-asyncio-kwarg to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread src/zeroconf/_core.py Outdated
Comment thread src/zeroconf/_core.py Outdated
Comment thread tests/test_core.py Outdated
@bdraco
Copy link
Copy Markdown
Member

bdraco commented May 16, 2026

@bluetoothbot rr

@bluetoothbot
Copy link
Copy Markdown
Contributor Author

Rebase with requested adjustments

Branch koan/use-asyncio-kwarg was rebased onto master and review feedback was applied.

Changes applied

  • Changes applied:
  • src/zeroconf/_core.py:173-180 — documented use_asyncio=True raises RuntimeError when no loop running, per Copilot review at line 179.
  • src/zeroconf/_core.py:176 — "behaviour" → "behavior" for consistency with rest of file (lines 340, 368), per Copilot review at line 176.
  • tests/test_core.py:180 — "behaviour" → "behavior" for codebase consistency, per Copilot review at line 145.
  • Earlier socket-leak comments (lines 193, 233) already fixed in 8a2c6d8 — check now occurs before create_sockets().

Stats

2 files changed, 47 insertions(+), 1 deletion(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No commit on master adds a use_asyncio kwarg or override for the running-loop auto-detect in Zerocon)
  • Rebased koan/use-asyncio-kwarg onto upstream/master
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan/use-asyncio-kwarg to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@bluetoothbot bluetoothbot force-pushed the koan/use-asyncio-kwarg branch from 42e7c27 to 369f533 Compare May 16, 2026 19:25
Comment thread src/zeroconf/_core.py Outdated
@bdraco
Copy link
Copy Markdown
Member

bdraco commented May 16, 2026

@bluetoothbot rr

Zeroconf currently auto-detects a running event loop and attaches to
it; passing use_asyncio=False forces the historic thread-mode path
even when a loop is running (e.g. Jupyter). use_asyncio=True requires
a running loop and raises otherwise. None (default) preserves the
existing behaviour.

Closes python-zeroconf#1651
@bluetoothbot
Copy link
Copy Markdown
Contributor Author

Rebase with requested adjustments

Branch koan/use-asyncio-kwarg was rebased onto master and review feedback was applied.

Changes applied

  • Done.
  • Summary

  • Collapsed if/else in Zeroconf.start() to ternary per @bdraco and @bluetoothbot review: self.loop = None if self._use_asyncio is False else get_running_loop(). Kept explicit is False so None default still falls through to get_running_loop().
  • Other review items already addressed in prior commits on branch:
  • Spelling "behaviour" → "behavior" (93cf0ef)
  • Documented use_asyncio=True RuntimeError (93cf0ef)
  • Validated running loop before socket allocation (be17aeb)

Stats

2 files changed, 44 insertions(+), 1 deletion(-)
Actions performed
  • Already-solved check: negative (confidence=high, reasoning=No recent commit on master adds a use_asyncio kwarg to Zeroconf.init or otherwise lets callers o)
  • Rebased koan/use-asyncio-kwarg onto upstream/master
  • Applied review feedback
  • Pre-push CI check: previous run passed
  • Force-pushed koan/use-asyncio-kwarg to origin
  • CI check enqueued in ## CI (async)

CI status

CI will be checked asynchronously.


Automated by Kōan

@bluetoothbot bluetoothbot force-pushed the koan/use-asyncio-kwarg branch from 369f533 to 8c133a0 Compare May 16, 2026 19:42
@bdraco bdraco marked this pull request as ready for review May 16, 2026 19:50
@bdraco bdraco merged commit c96a997 into python-zeroconf:master May 16, 2026
40 checks passed
@bluetoothbot bluetoothbot deleted the koan/use-asyncio-kwarg branch May 16, 2026 20:52
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

Successfully merging this pull request may close these issues.

Zeroconf configures itself to use asyncio if an event loop is running

3 participants