| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
A short orientation file for an LLM working in this repo. Skim before making changes; keep edits consistent with what's described here. Read README.rst for the user-facing intro.
python-zeroconf is a pure-Python implementation of multicast DNS service discovery (mDNS / DNS-SD, RFC 6762 + RFC 6763). It is the mDNS engine behind Home Assistant and a long list of other Python projects that need to announce or discover services on the local network. Public API is exported from the top-level zeroconf package; an async API lives at zeroconf.asyncio.
There is no external protocol owner — the on-the-wire format is the mDNS / DNS-SD RFCs. Behaviour changes that affect packet contents or timing should cite the relevant RFC section.
Hot paths (_cache, _dns, _history, _listener, _record_update, _updates, _protocol/{incoming,outgoing}, _handlers/*, _services/*, _utils/{ipaddress,time}) are Cythonized at build time for throughput. They keep working as pure Python — SKIP_CYTHON=1 disables the extension build — but production wheels ship compiled and CodSpeed benchmarks track that path. The authoritative list of cythonized modules lives in build_ext.py (TO_CYTHONIZE).
Docstrings: terse, default to single-line. A docstring is the function's contract, not its narrative. Almost every docstring should be one line — """Summary.""" — describing what the function does and what the caller can pass. Multi-line is the exception, only justified when there is non-obvious caller-visible behaviour the type signature and parameter names don't already convey.
What does NOT belong in docstrings or comments:
Comments: same bar. Default to writing no comments. Add one only when the why is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behaviour that would surprise a reader. RFC citations are useful when the reason for a timing constant or framing decision is "the spec says so" — leave those in. If removing the comment wouldn't confuse a future reader, don't write it.
Don't remove existing comments unless the code they describe is gone — the original author left them for a reason.
Don't pad commits, docstrings, or comments with cross- references to old codepaths or issue numbers unless there's a clear reason a future reader needs that link.
Method order: public API at the top, private helpers (_underscore_prefixed) at the bottom. Modules whose names start with _ (_cache, _dns, _handlers/, etc.) are internal; the supported surface is what zeroconf/__init__.py and zeroconf/asyncio.py re-export.
Line length: 110 (ruff line-length = 110). requires-python = ">=3.10", target-version = "py310" for ruff; pyupgrade runs --py310-plus.
Imports: ruff/isort sorted, profile = "black", known_first_party = ["zeroconf", "tests"]. Prefer from __future__ import annotations so modern type syntax works on 3.10.
Generated .c files are not lint-targets. *.c files next to each cythonized module are Cython output — never hand- edit them. They are excluded from sdist (exclude = ["**/*.c"] in pyproject.toml) and regenerated by the build.
poetry run pytest --durations=20 --timeout=60 -v tests…or make test, which runs the same command. Test discovery defaults from pyproject.toml already pass --cov=zeroconf and pythonpath = ["src"]. pytest-asyncio is used in the default per-test mode (no auto mode); async tests are marked explicitly with @pytest.mark.asyncio.
CodSpeed benchmarks live under tests/benchmarks/ and run in CI through CodSpeedHQ/action. Ad-hoc microbenchmarks for manual profiling live under bench/ — those don't run in CI.
The CI matrix includes CPython 3.10 – 3.14, the free-threaded 3.14t build, and PyPy 3.10. Don't add anything that breaks on the free-threaded build (no module-level mutable globals mutated from multiple threads without locks; no PyDict_Next-style escape hatches in Cython).
Non-obvious traps in the .py + .pxd setup that work fine in pure-Python mode but break or silently misbehave in the shipped Cython wheels. Distilled from the patterns that already exist in this repo's .pxd files and from incidents in sibling Cython- accelerated projects.
cdef-typed module constants are not Python-importable. Declaring cdef unsigned int _ANSWER_STRATEGY_POINTER in query_handler.pxd makes Cython treat _ANSWER_STRATEGY_POINTER = 1 in query_handler.py as a C int assignment; the Python module dict never gets the binding. from zeroconf._handlers.query_handler import _ANSWER_STRATEGY_POINTER succeeds in pure-Python but raises ImportError under Cython. If you need the value visible from Python (e.g. a test wants to assert on it), define both names — a public ANSWER_STRATEGY_POINTER = 1 Python binding plus a cdef-typed _ANSWER_STRATEGY_POINTER = ANSWER_STRATEGY_POINTER alias for hot-path comparisons.
Match the existing unsigned int convention for length, TTL, type/class, and offset fields. _protocol/incoming.pxd, _cache.pxd, and _handlers/*.pxd already declare these as unsigned int end-to-end. Introducing a cdef int return that carries a value originally decoded into unsigned int flips sign for any value with bit 31 set — TTL is a 32-bit DNS field (RFC 1035 §3.2.1, interpreted as unsigned), so a large TTL passed back through a cdef int boundary becomes negative and trips < 0 sentinel branches. Stay with unsigned int across the whole call chain; if you need a real sentinel, return an explicit value (UINT_MAX, a dedicated constant) and check for it by equality.
Module-level Python int constants force PyLong_AsLong on every hot-path comparison. if record.type == _TYPE_PTR compiles to a Python attribute lookup + PyLong_AsLong per call when _TYPE_PTR is just a .py-level binding. The repo already follows the right pattern — _cache.pxd / record_manager.pxd declare cdef unsigned int _TYPE_PTR, _DNS_PTR_MIN_TTL, _MIN_SCHEDULED_RECORD_EXPIRATION, etc. When adding a new size / TTL / type constant from const.py to a cdef hot path in _protocol/, _cache, _handlers/, or _listener, add the cdef-typed alias to the corresponding .pxd at the same time.
Sign-compare warnings in generated C are real. gcc/ clang warns when comparing unsigned int with int because the signed value is implicitly converted to unsigned for the compare — a negative value becomes a huge positive. Match the signedness of compared operands in the .pxd (e.g. if the local is unsigned int, declare the constant as cdef unsigned int; if the local is int, declare it cdef int). The warning predicts the unsigned -> signed overflow class of bug.
CodSpeed regressions only show up in the Cython build. Pure-Python (SKIP_CYTHON=1) tests can pass while production wire-format hot paths regress. Trust the CodSpeed check on PRs that touch any file in TO_CYTHONIZE; rebuild in place with REQUIRE_CYTHON=1 poetry install --only=main,dev before pushing if perf-sensitive code changed.
Suspected security vulnerabilities go through GitHub's private vulnerability reporting, not public issues or pull requests. The policy is spelled out in SECURITY.md. If a user describes what sounds like a vulnerability in chat, point them at that route instead of opening a public issue, PR, or commit that names the bug class and the affected code path.
| Path | What |
|---|---|
| src/zeroconf/__init__.py | Public package — re-exports Zeroconf, ServiceBrowser, ServiceInfo, etc. |
| src/zeroconf/asyncio.py | Async API: AsyncZeroconf, AsyncServiceBrowser, AsyncZeroconfServiceTypes |
| src/zeroconf/_core.py | Zeroconf core — socket setup, send/recv loop, registration/probing |
| src/zeroconf/_engine.py | Asyncio engine driving the listener |
| src/zeroconf/_listener.py | Cython-accelerated packet listener |
| src/zeroconf/_cache.py | DNS record cache (Cythonized) |
| src/zeroconf/_dns.py | DNS record / question classes (Cythonized) |
| src/zeroconf/_history.py | Outgoing-question history for known-answer suppression |
| src/zeroconf/_record_update.py | Record-update dataclass passed to listeners |
| src/zeroconf/_protocol/ | DNSIncoming / DNSOutgoing wire codec (Cythonized) |
| src/zeroconf/_handlers/ | Query / answer / multicast queueing (Cythonized) |
| src/zeroconf/_services/ | ServiceBrowser, ServiceInfo, ServiceRegistry, types |
| src/zeroconf/_updates.py | RecordUpdateListener base class (Cythonized) |
| src/zeroconf/_utils/ | ipaddress, time, net, name, asyncio helpers |
| src/zeroconf/const.py | Timeouts, intervals, multicast group constants |
| src/zeroconf/_exceptions.py | Public exception hierarchy |
| tests/ | Pytest suite |
| tests/benchmarks/ | CodSpeed benchmarks |
| bench/ | Manual microbenchmarks (not run in CI) |
| build_ext.py | TO_CYTHONIZE list + poetry-core build hook |
| Back | FazBrowse Home | New Git URL |