fix: type Signal dispatch contract with Protocol#1784
Conversation
Signal._handlers was typed list[Callable[..., None]] and fire accepted arbitrary **kwargs, so a typo at a fire site or a missing parameter at a handler only blew up the moment a real service event dispatched - hours into a run on a quiet network. mypy could not catch dispatch mismatches when the contract shifted. Lock the contract down: define a ServiceStateChangeHandler Protocol describing the (zeroconf, service_type, name, state_change) keyword signature, type Signal._handlers as a list of that Protocol, and make Signal.fire keyword-only with the four named parameters. register_handler / unregister_handler still accept Callable[..., None] for back-compat and cast at the boundary.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1784 +/- ##
==========================================
- Coverage 99.77% 99.74% -0.03%
==========================================
Files 33 33
Lines 3536 3538 +2
Branches 498 499 +1
==========================================
+ Hits 3528 3529 +1
Misses 5 5
- Partials 3 4 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Sorry, something went wrong.
|
Coverage is incomplete. Ci is failing |
Sorry, something went wrong.
bdraco
left a comment
There was a problem hiding this comment.
See above
Sorry, something went wrong.
Sorry, something went wrong.
Rebase with requested adjustmentsBranch StatsActions performed
CI statusCI will be checked asynchronously. Automated by Kōan |
Sorry, something went wrong.
Summary
Signal._handlerswas typedlist[Callable[..., None]]andfire(**kwargs: Any)forwarded arbitrary kwargs, so a typo at a fire site or a missing parameter at a handler only surfaced when a real service event happened to dispatch. mypy could not help catch dispatch mismatches when the contract shifted.Locks the contract down with a
ServiceStateChangeHandlerProtocoland an explicit keyword-onlyfire(*, zeroconf, service_type, name, state_change).register_handler/unregister_handlerkeep acceptingCallable[..., None]for back-compat and cast at the boundary, matching the suggestion in the issue.Closes #1779
Changes
ServiceStateChangeHandlerProtocolinzeroconf._servicesdescribing the(zeroconf, service_type, name, state_change)keyword signature; re-export from the top-levelzeroconfpackage.Signal._handlerstolist[ServiceStateChangeHandler]andSignal.fireto a keyword-only signature with the four named parameters.SignalRegistrationInterface.register_handler/unregister_handleracceptingCallable[..., None]andcast()to the Protocol on the way in.Test plan
SKIP_CYTHON=1 poetry run pytest tests/(full suite green: 396 passed, 2 skipped, 5 xfailed, 3 xpassed).poetry run ruff check src/zeroconf/_services/__init__.py src/zeroconf/__init__.py tests/test_services.py(clean).poetry run ruff format --checkon the same files (clean).REQUIRE_CYTHON=1Cython regeneration succeeds for_services/__init__.py(and every other module inTO_CYTHONIZE), so the build path still works against the new Protocol class.Generated by Kōan
Quality Report
Changes: 3 files changed, 108 insertions(+), 8 deletions(-)
Code scan: clean
Tests: passed (4 PASSED)
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline