Skip to content

Commit 3ba78ab

Browse files
committed
Add AGENTS.md
1 parent 7bad91e commit 3ba78ab

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
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

Comments
 (0)