|
| 1 | +# AGENTS.md |
| 2 | + |
| 3 | +This file provides guidance to AI coding agents when working with code in this repository. |
| 4 | + |
| 5 | +## What this project is |
| 6 | + |
| 7 | +`dnspython` is a DNS toolkit for Python (import name `dns`). It provides both |
| 8 | +high-level query/resolver APIs and low-level classes for directly manipulating |
| 9 | +DNS messages, names, rdata, zones, and transactions. The default installation |
| 10 | +has no dependencies outside the standard library; optional features |
| 11 | +(DNSSEC, DoH, DoQ, IDNA, Trio, WMI) are gated behind extras and runtime |
| 12 | +feature checks (see Architecture below). |
| 13 | + |
| 14 | +## Commands |
| 15 | + |
| 16 | +This project uses `uv` for environment/dependency management and its |
| 17 | +build backend (`uv_build`). |
| 18 | + |
| 19 | +- Run the full test suite: `pytest` (or `make test`) |
| 20 | +- Run a single test file: `pytest tests/test_name.py` |
| 21 | +- Run a single test case: `pytest tests/test_name.py -k test_bad_escape` or |
| 22 | + `pytest tests/test_name.py::NameTestCase::test_bad_escape` (tests are |
| 23 | + `unittest.TestCase` classes, executed through pytest) |
| 24 | +- Type check: `pyright dns` and `ty check dns` (or `make pyright`, `make ty`, `make type`) |
| 25 | +- Lint: `ruff check dns` (or `make ruff`) — note lint only covers `dns/`, not `tests/` |
| 26 | +- Format: `black dns examples tests` (or `make black`) |
| 27 | +- Coverage: `make cov` (writes `htmlcov/`, restricted to `dns/*`) |
| 28 | +- Build docs: `make doc` (Sphinx, output in `doc/_build`) |
| 29 | +- Build a wheel/sdist: `make build` |
| 30 | +- Clean build/test artifacts: `make clean` |
| 31 | + |
| 32 | +CI (`.github/workflows/ci.yml`) runs `pyright dns`, `ruff check dns`, and |
| 33 | +`pytest` across Python 3.10–3.15-dev and PyPy on Linux and Windows. Match |
| 34 | +that: keep new code typed and passing both `pyright` and `ty`, and keep |
| 35 | +`ruff` clean. |
| 36 | + |
| 37 | +## Architecture |
| 38 | + |
| 39 | +### Core data model |
| 40 | + |
| 41 | +DNS data flows through a consistent layered model, and most non-trivial |
| 42 | +features touch several of these layers at once: |
| 43 | + |
| 44 | +- `dns.name.Name` — immutable, absolute-or-relative domain names. |
| 45 | +- `dns.rdataclass` / `dns.rdatatype` — enum-like registries for RR classes |
| 46 | + (IN, CH, ...) and RR types (A, MX, DNSKEY, ...), extensible at runtime via |
| 47 | + `register_class`/`register_type` for private-use types. |
| 48 | +- `dns.rdata.Rdata` — base class for a single rdata record. Concrete types |
| 49 | + live under `dns/rdtypes/{CLASS}/{TYPE}.py` (e.g. `dns/rdtypes/IN/A.py`) or |
| 50 | + `dns/rdtypes/ANY/{TYPE}.py` for class-independent types, plus shared mixins |
| 51 | + in `dns/rdtypes/*base.py` (e.g. `dnskeybase.py`, `svcbbase.py`, |
| 52 | + `txtbase.py`). `dns.rdata.get_rdata_class()` dynamically imports the right |
| 53 | + module by convention (`dns.rdtypes.{CLASSTEXT}.{TYPETEXT}`, falling back to |
| 54 | + `ANY`, then a generic implementation) — adding a new RR type means adding a |
| 55 | + module in the conventional location, not registering it elsewhere. |
| 56 | +- `dns.rdataset.Rdataset` — an rdata class/type/covers plus a set of `Rdata`. |
| 57 | +- `dns.rrset.RRset` — an `Rdataset` bound to an owner `dns.name.Name`. |
| 58 | +- `dns.message.Message` — a full DNS message (question/answer/authority/ |
| 59 | + additional sections, EDNS, TSIG); `dns.message.make_query`, |
| 60 | + `from_text`/`from_wire` and `to_text`/`to_wire` are the main entry points. |
| 61 | + |
| 62 | +Every layer generally supports both `from_text`/`to_text` (master-file/ |
| 63 | +presentation format, via `dns.tokenizer`) and `from_wire`/`to_wire` |
| 64 | +(binary format, via `dns.wire`/`dns.renderer`), and these must round-trip. |
| 65 | +Text/wire style formatting is controlled by `dns.name.NameStyle` / |
| 66 | +`dns.rdata.RdataStyle` and rendered via `to_styled_text()`. |
| 67 | + |
| 68 | +### Immutability |
| 69 | + |
| 70 | +`dns.immutable` provides an `@dns.immutable.immutable` class decorator used |
| 71 | +throughout (`Name`, `Rdata`, frozen `Rdataset`/`RRset` variants, etc.) to make |
| 72 | +core objects hashable and safe to share/cache. `dns.set.Set` is the generic |
| 73 | +mutable-set base that `Rdataset`, `dns.namedict.NameDict`, and zone node |
| 74 | +classes build on. |
| 75 | + |
| 76 | +### Query execution: sync, async, and backends |
| 77 | + |
| 78 | +- `dns.query` — synchronous UDP/TCP/TLS/HTTPS (DoH) queries. |
| 79 | +- `dns.asyncquery` — the async equivalents, dispatched through a pluggable |
| 80 | + backend abstraction (`dns.asyncbackend`, `dns._asyncio_backend`, |
| 81 | + `dns._trio_backend`) so the same code paths support both `asyncio` and |
| 82 | + `trio`. |
| 83 | +- `dns.resolver` / `dns.asyncresolver` — stub resolver on top of |
| 84 | + `dns.query`/`dns.asyncquery`, with OS-specific system configuration readers |
| 85 | + (`dns/win32util.py` for Windows, `/etc/resolv.conf` parsing elsewhere) and a |
| 86 | + `dns.nameserver.Nameserver` abstraction for per-nameserver transport |
| 87 | + (Do53/DoT/DoH/DoQ) used by `dns.resolver.Resolver.resolve`. |
| 88 | +- `dns.quic` — DNS-over-QUIC implementation with the same sync/asyncio/trio |
| 89 | + split (`_sync.py`, `_asyncio.py`, `_trio.py`) behind `_common.py`. |
| 90 | +- `dns._features.have()` gates optional-dependency code paths (cryptography, |
| 91 | + httpx2/h2, aioquic, idna, trio, wmi) by checking installed package versions |
| 92 | + at runtime rather than hard-importing them; new optional integrations |
| 93 | + should follow this pattern instead of adding hard dependencies. |
| 94 | + |
| 95 | +### Zones, transactions, and updates |
| 96 | + |
| 97 | +- `dns.zone.Zone` holds a set of names each mapped to a node of rdatasets, |
| 98 | + and supports reading/writing master files (`dns.zonefile`) and comparing |
| 99 | + zones (`dns.zonediff`, exposed via `zone.py`'s diff helpers). |
| 100 | +- `dns.transaction.Transaction`/`TransactionManager` is the unit-of-work |
| 101 | + abstraction for reading or atomically mutating a zone; `dns.versioned` adds |
| 102 | + a versioned zone implementation with historical version retention on top of |
| 103 | + it. Both zone-file loading and inbound zone transfers (`dns.xfr`) go |
| 104 | + through a transaction, and a "transaction setup" callable |
| 105 | + (e.g. `dns.transaction.TransactionLimiter`) can be supplied to constrain |
| 106 | + what a transaction is allowed to do. |
| 107 | +- `dns.update.Update` builds RFC 2136 dynamic update messages. |
| 108 | +- `dns.xfr` implements inbound AXFR/IXFR handling used by resolver/query |
| 109 | + helpers and by `dns.zone` for zone transfers. |
| 110 | + |
| 111 | +### DNSSEC |
| 112 | + |
| 113 | +- `dns.dnssec` provides validation/signing logic (RRSIG verification, DS |
| 114 | + digest computation, NSEC/NSEC3 handling) over the algorithm plumbing in |
| 115 | + `dns.dnssecalgs` (per-algorithm modules: `rsa.py`, `dsa.py`, `ecdsa.py`, |
| 116 | + `eddsa.py`, `mldsa.py`, dispatched through `base.py`/`cryptography.py`), |
| 117 | + gated by the `dnssec` extra (`cryptography` package) via `dns._features`. |
| 118 | + |
| 119 | +### Cross-cutting conventions |
| 120 | + |
| 121 | +- Modules prefixed with `_` (`_features.py`, `_asyncbackend.py`, |
| 122 | + `_immutable_ctx.py`, `_render_util.py`, `_tls_util.py`, `_file_util.py`, |
| 123 | + `_ddr.py`, `_no_ssl.py`) are internal implementation details, not part of |
| 124 | + the public API. |
| 125 | +- Public modules generally define a `dns.exception.DNSException` subclass (or |
| 126 | + several) for their own error conditions rather than raising built-in |
| 127 | + exceptions directly; follow that convention for new error paths. |
| 128 | +- Tests live in `tests/` as pytest or `unittest.TestCase`-based modules named |
| 129 | + `test_*.py`, run through pytest; test fixtures/data files (`.good`, `.text`, |
| 130 | + `.generic`, `.pickle`, sample zone files, TLS certs under `tests/tls/`, |
| 131 | + TSIG keys under `tests/tsigkeys/`) sit alongside the test code and are also |
| 132 | + referenced in `pyproject.toml`'s `source-include` for packaging. New tests |
| 133 | + that are not augmenting existing test suites may use pytest instead of unittest. |
| 134 | +- Some of the tests in the test suite are "live" and test against the Internet. |
| 135 | + If Internet connectivity is not available, the NO_INTERNET environment variable |
| 136 | + can be defined before running the tests, and the test suite will skip the live |
| 137 | + tests. |
| 138 | +- Dnspython has high test coverage; 94% for the whole project, and many important |
| 139 | + modules have 100% coverage or close to it. Code changes should preserve or improve |
| 140 | + the coverage whenever reasonable. Coverage should cover all branch paths when |
| 141 | + reasonable. Uninteresting failures that are hard to cover may be ignored with |
| 142 | + `pragma: no cover`. |
| 143 | +- Backwards compatibility is important! Incompatible changes should generally be |
| 144 | + avoided, but when they need to occur, they should be as small as possible and |
| 145 | + documented. |
| 146 | +- The project supports all CPython 3 releases that have not reached end-of-life, as |
| 147 | + well as Pypy 3.11. Once a Python release has reached end-of-life, code may be |
| 148 | + refactored to use features of the new least release. |
| 149 | + |
| 150 | +## Documentation |
| 151 | + |
| 152 | +Documentation is important. All public types, attributes, and APIs should be |
| 153 | +documented in the source. Additionally, there is a manual of RST source in |
| 154 | +`doc/` which includes not only auto-generated documentation extracted from the source, |
| 155 | +but additional "big picture" documentation and other helpful references. When updating |
| 156 | +code, consider if anything in `doc/` needs to be updated as well. |
| 157 | + |
| 158 | +The documentation file `doc/rfc.rst` is a good starting place for any DNS questions. |
| 159 | +When adding or updating code, and you know it is associated with an RFC, add a link |
| 160 | +for the RFC to `doc/rfc.rst`. |
0 commit comments