This file provides guidance to AI coding agents when working with code in this repository.
dnspython is a DNS toolkit for Python (import name dns). It provides both
high-level query/resolver APIs and low-level classes for directly manipulating
DNS messages, names, rdata, zones, and transactions. The default installation
has no dependencies outside the standard library; optional features
(DNSSEC, DoH, DoQ, IDNA, Trio, WMI) are gated behind extras and runtime
feature checks (see Architecture below).
This project uses uv for environment/dependency management and its
build backend (uv_build).
- Run the full test suite:
pytest(ormake test) - Run a single test file:
pytest tests/test_name.py - Run a single test case:
pytest tests/test_name.py -k test_bad_escapeorpytest tests/test_name.py::NameTestCase::test_bad_escape(tests areunittest.TestCaseclasses, executed through pytest) - Type check:
pyright dnsandty check dns(ormake pyright,make ty,make type) - Lint:
ruff check dns(ormake ruff) — note lint only coversdns/, nottests/ - Format:
black dns examples tests(ormake black) - Coverage:
make cov(writeshtmlcov/, restricted todns/*) - Build docs:
make doc(Sphinx, output indoc/_build) - Build a wheel/sdist:
make build - Clean build/test artifacts:
make clean
CI (.github/workflows/ci.yml) runs pyright dns, ruff check dns, and
pytest across Python 3.10–3.15-dev and PyPy on Linux and Windows. Match
that: keep new code typed and passing both pyright and ty, and keep
ruff clean.
DNS data flows through a consistent layered model, and most non-trivial features touch several of these layers at once:
dns.name.Name— immutable, absolute-or-relative domain names.dns.rdataclass/dns.rdatatype— enum-like registries for RR classes (IN, CH, ...) and RR types (A, MX, DNSKEY, ...), extensible at runtime viaregister_class/register_typefor private-use types.dns.rdata.Rdata— base class for a single rdata record. Concrete types live underdns/rdtypes/{CLASS}/{TYPE}.py(e.g.dns/rdtypes/IN/A.py) ordns/rdtypes/ANY/{TYPE}.pyfor class-independent types, plus shared mixins indns/rdtypes/*base.py(e.g.dnskeybase.py,svcbbase.py,txtbase.py).dns.rdata.get_rdata_class()dynamically imports the right module by convention (dns.rdtypes.{CLASSTEXT}.{TYPETEXT}, falling back toANY, then a generic implementation) — adding a new RR type means adding a module in the conventional location, not registering it elsewhere.dns.rdataset.Rdataset— an rdata class/type/covers plus a set ofRdata.dns.rrset.RRset— anRdatasetbound to an ownerdns.name.Name.dns.message.Message— a full DNS message (question/answer/authority/ additional sections, EDNS, TSIG);dns.message.make_query,from_text/from_wireandto_text/to_wireare the main entry points.
Every layer generally supports both from_text/to_text (master-file/
presentation format, via dns.tokenizer) and from_wire/to_wire
(binary format, via dns.wire/dns.renderer), and these must round-trip.
Text/wire style formatting is controlled by dns.name.NameStyle /
dns.rdata.RdataStyle and rendered via to_styled_text().
dns.immutable provides an @dns.immutable.immutable class decorator used
throughout (Name, Rdata, frozen Rdataset/RRset variants, etc.) to make
core objects hashable and safe to share/cache. dns.set.Set is the generic
mutable-set base that Rdataset, dns.namedict.NameDict, and zone node
classes build on.
dns.query— synchronous UDP/TCP/TLS/HTTPS (DoH) queries.dns.asyncquery— the async equivalents, dispatched through a pluggable backend abstraction (dns.asyncbackend,dns._asyncio_backend,dns._trio_backend) so the same code paths support bothasyncioandtrio.dns.resolver/dns.asyncresolver— stub resolver on top ofdns.query/dns.asyncquery, with OS-specific system configuration readers (dns/win32util.pyfor Windows,/etc/resolv.confparsing elsewhere) and adns.nameserver.Nameserverabstraction for per-nameserver transport (Do53/DoT/DoH/DoQ) used bydns.resolver.Resolver.resolve.dns.quic— DNS-over-QUIC implementation with the same sync/asyncio/trio split (_sync.py,_asyncio.py,_trio.py) behind_common.py.dns._features.have()gates optional-dependency code paths (cryptography, httpx2/h2, aioquic, idna, trio, wmi) by checking installed package versions at runtime rather than hard-importing them; new optional integrations should follow this pattern instead of adding hard dependencies.
dns.zone.Zoneholds a set of names each mapped to a node of rdatasets, and supports reading/writing master files (dns.zonefile) and comparing zones (dns.zonediff, exposed viazone.py's diff helpers).dns.transaction.Transaction/TransactionManageris the unit-of-work abstraction for reading or atomically mutating a zone;dns.versionedadds a versioned zone implementation with historical version retention on top of it. Both zone-file loading and inbound zone transfers (dns.xfr) go through a transaction, and a "transaction setup" callable (e.g.dns.transaction.TransactionLimiter) can be supplied to constrain what a transaction is allowed to do.dns.update.Updatebuilds RFC 2136 dynamic update messages.dns.xfrimplements inbound AXFR/IXFR handling used by resolver/query helpers and bydns.zonefor zone transfers.
dns.dnssecprovides validation/signing logic (RRSIG verification, DS digest computation, NSEC/NSEC3 handling) over the algorithm plumbing indns.dnssecalgs(per-algorithm modules:rsa.py,dsa.py,ecdsa.py,eddsa.py,mldsa.py, dispatched throughbase.py/cryptography.py), gated by thednssecextra (cryptographypackage) viadns._features.
- Modules prefixed with
_(_features.py,_asyncbackend.py,_immutable_ctx.py,_render_util.py,_tls_util.py,_file_util.py,_ddr.py,_no_ssl.py) are internal implementation details, not part of the public API. - Public modules generally define a
dns.exception.DNSExceptionsubclass (or several) for their own error conditions rather than raising built-in exceptions directly; follow that convention for new error paths. - Tests live in
tests/as pytest orunittest.TestCase-based modules namedtest_*.py, run through pytest; test fixtures/data files (.good,.text,.generic,.pickle, sample zone files, TLS certs undertests/tls/, TSIG keys undertests/tsigkeys/) sit alongside the test code and are also referenced inpyproject.toml'ssource-includefor packaging. New tests that are not augmenting existing test suites may use pytest instead of unittest. - Some of the tests in the test suite are "live" and test against the Internet. If Internet connectivity is not available, the NO_INTERNET environment variable can be defined before running the tests, and the test suite will skip the live tests.
- Dnspython has high test coverage; 94% for the whole project, and many important
modules have 100% coverage or close to it. Code changes should preserve or improve
the coverage whenever reasonable. Coverage should cover all branch paths when
reasonable. Uninteresting failures that are hard to cover may be ignored with
pragma: no cover. - Backwards compatibility is important! Incompatible changes should generally be avoided, but when they need to occur, they should be as small as possible and documented.
- The project supports all CPython 3 releases that have not reached end-of-life, as well as Pypy 3.11. Once a Python release has reached end-of-life, code may be refactored to use features of the new least release.
Documentation is important. All public types, attributes, and APIs should be
documented in the source. Additionally, there is a manual of RST source in
doc/ which includes not only auto-generated documentation extracted from the source,
but additional "big picture" documentation and other helpful references. When updating
code, consider if anything in doc/ needs to be updated as well.
The documentation file doc/rfc.rst is a good starting place for any DNS questions.
When adding or updating code, and you know it is associated with an RFC, add a link
for the RFC to doc/rfc.rst.