py-doh-core
Python bindings for doh-core,
a Rust DNS resolver library that only speaks secure transports — DNS-over-HTTPS
(RFC 8484), DNS-over-TLS (RFC 7858), and DNS-over-QUIC (RFC 9250) — with no
fallback to classic plaintext DNS. If a query can't be answered securely, you
get a clear exception instead of a silent, unencrypted lookup.
pip install py-doh-core
Quickstart
import py_doh_core as doh
transport = doh.DohTransport("https://dns.google/dns-query")
response = transport.resolve("example.com", "A")
for answer in response.answers:
print(answer.name, answer.ttl, answer.rdata)
Every resolve() has an async def-compatible aresolve() twin:
import asyncio
import py_doh_core as doh
async def main() -> None:
transport = doh.DotTransport("dns.google")
response = await transport.aresolve("example.com", "AAAA")
print(response.answers[0].rdata)
asyncio.run(main())
resolve() blocks the calling thread but releases the GIL for the network
call, so other Python threads keep running; aresolve() returns a normal
awaitable. Both forms exist on all three transports below.
Transports
| Class | Protocol | Constructor |
|---|---|---|
DohTransport |
DNS-over-HTTPS | DohTransport(server_url, method=None) — server_url e.g. "https://dns.google/dns-query" (must be https://); method is "get" (default) or "post" |
DotTransport |
DNS-over-TLS | DotTransport(server_addr) — server_addr is host[:port], default port 853 |
DoqTransport |
DNS-over-QUIC | DoqTransport(server_addr) — same host[:port] form; the QUIC connection is pooled and reused across every resolve()/aresolve() call on the instance |
All three share the same API: resolve, aresolve, resolve_many,
aresolve_many. Pick a transport, not a different way of calling it.
Responses are typed, not dicts
resolve()/aresolve() return a ParsedResponse with real attributes —
response.answers[0].rdata, not response["answers"][0]["rdata"] — mirroring
every field of the underlying doh_core::ParsedResponse (header flags,
question, answer/authority/additional sections, wire size). op_code and
response_code are OpCode/ResponseCode enums, not magic strings or ints:
if response.response_code == doh.ResponseCode.NXDOMAIN:
... # the name doesn't exist -- this is a successful response, not an error
See py_doh_core.pyi
for the full type signatures.
Handling errors
Anything that isn't a clean "no error" or "name does not exist" response —
a bad server URL, a connection failure, SERVFAIL, REFUSED, a malformed
reply — raises doh.DohError rather than returning a half-empty result:
try:
response = transport.resolve("dnssec-failed.org", "A")
except doh.DohError as exc:
print("lookup failed:", exc)
str(exc) carries the same message the underlying Rust library produces.
Multiple record types in one call
resolve_many()/aresolve_many() query several record types for one name
against a single transport instance. Queries run in turn, reusing the
connection where that matters (DoqTransport's pooled connection in
particular), and one type's failure doesn't abort the rest — each entry in
the returned list is a QueryResult with exactly one of response/error
set:
for result in transport.resolve_many("example.com", ["A", "AAAA", "MX"]):
if result.error is not None:
print(result.record_type, "failed:", result.error)
else:
print(result.record_type, [a.rdata for a in result.response.answers])
An unparseable record type string is the one exception: it raises DohError
immediately, before any query in the batch is sent.
Logging
Verbose/debug output uses Python's standard logging module — no separate
init call needed:
import logging
logging.basicConfig(level=logging.DEBUG)
Logger names follow the Rust module path, e.g. doh_core.transport.doh,
doh_core.transport.doq, py_doh_core.transport. DEBUG shows one line per
query (server, method, connection reuse, response codes); a small amount of
extra detail (e.g. response sizes) logs at level 5, below logging.DEBUG
(10) — pass level=5 to see it. Scope to just this library with
logging.getLogger("doh_core").setLevel(logging.DEBUG).
Only doh_core/py_doh_core targets are bridged to Python — dependency
crates (reqwest, h2, rustls, quinn) are deliberately not, since their
logging runs on long-lived background threads that can outlive a single
resolve() call and, in rare cases, still be active as the Python
interpreter shuts down.
Development
py-doh-core is a PyO3 cdylib extension module, built with
maturin. It's intentionally excluded from the
main Cargo workspace (see the root Cargo.toml) since it needs maturin's
linker setup to resolve Python symbols at import time — plain
cargo build/cargo test --workspace can't link it.
Build from source
cd py-doh-core
python3 -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop
Tests
pip install pytest pytest-asyncio
pytest
tests/test_resolve.py runs live against real public resolvers (no mocking
layer, same approach the Rust side uses). DoT/DoQ cases skip automatically
if port 853 is unreachable on the current network.
Metadata
Release files for py-doh-core 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| py_doh_core-0.1.1.tar.gz | 82.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| py_doh_core-0.1.1-cp39-abi3-win_amd64.whl | CPython 3.9 | abi3 | Windows x86-64 | Details |
| py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.9 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| py_doh_core-0.1.1-cp39-abi3-macosx_11_0_arm64.whl | CPython 3.9 | abi3 | macOS 11.0+ ARM64 | Details |
| py_doh_core-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl | CPython 3.9 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 18.0 MB
Release files / py_doh_core-0.1.1.tar.gz
| Download URL | py_doh_core-0.1.1.tar.gz |
|---|---|
| Size | 82.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
469166eac33639a08abf4b809ef1499c70185ea401e0c8340b224111d13a6684
|
|
BLAKE2b-256 checksum How to use checksums |
82c1ca80dec7fb696e8a9e2a2bfba7d9fe77b4a4220eacaf1e87ae84311911d0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / py_doh_core-0.1.1-cp39-abi3-win_amd64.whl
| Download URL | py_doh_core-0.1.1-cp39-abi3-win_amd64.whl |
|---|---|
| Size | 3.1 MB |
| Tags | CPython 3.9 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
297d83e182c306796c9ab889b35aaefaeef13c55bba6902529d80e5f550ba67e
|
|
BLAKE2b-256 checksum How to use checksums |
a605c723b966676b5891ec4b82b4cff1d9ff5fe0f3bd5878f0018985f5d2c055
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
ff49d082aeda195ef7d52b52de4a8b9a7476c339b95cbc29e8e09db0933473db
|
|
BLAKE2b-256 checksum How to use checksums |
262c6d2064f43a8bfe92bd2d944a062ee3117fb3217e356d0fa7bb905d65ca00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | py_doh_core-0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | CPython 3.9 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
f7c31b03ed2fa3db3b7325c2c8ac23bcd1ca54be07bc8dcf8cab322a4c0ab879
|
|
BLAKE2b-256 checksum How to use checksums |
44d3d9a38b29d9a302bd113dbf8a805850edfed601f5760bedbe0e30eb6248e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / py_doh_core-0.1.1-cp39-abi3-macosx_11_0_arm64.whl
| Download URL | py_doh_core-0.1.1-cp39-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.5 MB |
| Tags | CPython 3.9 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
fba80694184d11f6e906931ec466c8b0cc2cba98f8547ac7db2059cb316a6d2c
|
|
BLAKE2b-256 checksum How to use checksums |
60910bbaec28e2b04f4e5ea3f389ea6abde04874b55e05086acf86b44687e790
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency logRelease files / py_doh_core-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl
| Download URL | py_doh_core-0.1.1-cp39-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | CPython 3.9 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
a05f47e3b1f6fe5c7f3ca30f5aa9b2eb99e6813535b1fbaec17f67505496e874
|
|
BLAKE2b-256 checksum How to use checksums |
0e918eeed73d9f3d897e63d1a3472d60802af31fc5a2862c26d5bd335cc4f982
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 18, 2026.
Transparency log