Skip to main content

Async + sync Python client for the Technitium DNS Server HTTP API

Project description

technitiumdns-api

PyPI - Version Python versions License: GPL v3

Async + sync Python client for the Technitium DNS Server HTTP API.

Built originally to power the home-assistant-technitiumdns integration, but works in any Python project.

Features

  • 100% coverage of the documented Technitium DNS Server API (~140 endpoints).
  • Two interchangeable clients: AsyncClient (aiohttp) and Client (httpx).
  • Typed dataclass models for every response shape, with raw payloads always available.
  • Bearer-token auth with backward-compatible ?token= query-string fallback.
  • Automatic Technitium response envelope handling (status, errorMessage, invalid-token, 2fa-required).
  • Optional node="cluster" (or any node domain) on every endpoint that supports clustering.
  • File upload helpers for zone import, settings restore and app install.
  • Binary download helpers for zone export, settings backup and log download.
  • DNS-app query-log discovery (apps.list_dns_loggers()).

Install

pip install technitiumdns-api

Requires Python 3.11+.

Quick start - async

import asyncio
from technitiumdns import AsyncClient

async def main():
    async with AsyncClient("http://dns.local:5380", token="YOUR_API_TOKEN") as api:
        stats = await api.dashboard.stats(type="LastHour", utc=True)
        print(stats.stats.total_queries, "queries in the last hour")

        leases = await api.dhcp.leases_list()
        for lease in leases:
            print(lease.address, lease.hardware_address, lease.host_name)

asyncio.run(main())

Quick start - sync

from technitiumdns import Client

with Client("http://dns.local:5380", token="YOUR_API_TOKEN") as api:
    settings = api.settings.get()
    print("Blocking enabled:", settings.enable_blocking)
    api.settings.temporary_disable_blocking(minutes=5)

Login + token capture

async with AsyncClient("http://dns.local:5380") as api:
    result = await api.login(user="admin", password="admin")
    print("token:", result.token)
    # the client now uses the token automatically for subsequent calls
    print(await api.dashboard.stats(type="LastHour"))

Cluster support

Pass node="cluster" (or a specific node domain) once at construction time and every call will include it automatically:

async with AsyncClient(URL, token=TOKEN, node="cluster") as api:
    aggregate_stats = await api.dashboard.stats(type="LastDay")

Per-call overrides still work: await api.dashboard.stats(type="LastDay", node="server2.example.com").

Home Assistant integration

When using AsyncClient from inside Home Assistant, share HA's pooled aiohttp session to play nicely with the rest of the ecosystem:

from homeassistant.helpers import aiohttp_client
from technitiumdns import AsyncClient

session = aiohttp_client.async_get_clientsession(hass, verify_ssl=True)
api = AsyncClient(
    entry.data["api_url"],
    token=entry.data["token"],
    session=session,
    node="cluster" if entry.options.get("cluster_mode") else None,
)

The client will not close the session on exit when one is injected.

Endpoint namespaces

Namespace Wraps Examples
api.user /api/user/..., /api/sso/status login, check_for_update
api.dashboard /api/dashboard/... stats, metrics_json, get_top
api.zones /api/zones/... list, add_record, sign, get_dnssec_properties
api.cache /api/cache/... list, flush
api.allowed /api/allowed/... list, allow, import_zones
api.blocked /api/blocked/... list, block, import_zones
api.apps /api/apps/... list, install, set_config, list_dns_loggers
api.dns_client /api/dnsClient/... resolve
api.settings /api/settings/... get, set, temporary_disable_blocking, backup
api.dhcp /api/dhcp/... leases_list, scopes_list, set_scope
api.admin /api/admin/... list_users, cluster_state, cluster_init
api.logs /api/logs/... list, query, download, export

Exception model

All exceptions derive from TechnitiumError:

TechnitiumError
├── TransportError          - network/timeout/non-JSON errors
├── InvalidTokenError       - status=='invalid-token' or HTTP 401
├── TwoFactorRequiredError  - status=='2fa-required'
├── PermissionDeniedError   - HTTP 403
├── NotFoundError           - HTTP 404
└── ServerError             - HTTP 5xx

Every exception carries .status, .error_message, .stack_trace, .inner_error_message and the raw .response dict for debugging.

Live testing (optional)

Unit tests mock the Technitium API and run in CI. To exercise a real server locally:

cp .env.example .env   # fill in TECHNITIUM_TEST_URL and TECHNITIUM_TEST_TOKEN
export $(grep -v '^#' .env | xargs)

# Read-only pytest integration suite
pytest tests/test_live_integration.py -v -m integration

# Full endpoint sweep (manual script)
python tests/run_live_all_endpoints.py

Never commit .env or API tokens. For GitHub Actions, add TECHNITIUM_TEST_URL and TECHNITIUM_TEST_TOKEN as repository secrets and run the Live integration (manual) workflow.

Live test probes use the test user token, target user-managed zones (for example api-probe-test.local), and this-server for DNS Client resolve calls (external resolvers such as 1.1.1.1 are blocked by Technitium).

Contributing / branch protection

All changes to main must go through a pull request with passing CI. After creating the GitHub repository, apply the branch ruleset once — see .github/BRANCH_PROTECTION.md.

Releasing

CI (ci.yml) runs on every push/PR to main — it tests and builds but does not publish to PyPI.

Release (release.yml) runs only when you push a version tag:

git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0

That workflow re-runs lint/tests, builds the package, and publishes to PyPI via trusted publishing (after you configure the pending publisher on pypi.org).

To cut a new release:

  1. Bump the version in src/technitiumdns/_version.py.
  2. Update CHANGELOG.md with the new entry.
  3. Commit to main, then tag and push the tag as shown above.
  4. Watch the Release workflow in GitHub Actions.

For local testing of the build:

pip install build twine
python -m build
twine check dist/*

Versioning & license

Released under GPL-3.0-or-later to match the home-assistant-technitiumdns integration. Follows Semantic Versioning once the 1.0.0 stable line is published.

See CHANGELOG.md for release notes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

technitiumdns_api-0.1.1.tar.gz (44.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

technitiumdns_api-0.1.1-py3-none-any.whl (60.3 kB view details)

Uploaded Python 3

File details

Details for the file technitiumdns_api-0.1.1.tar.gz.

File metadata

  • Download URL: technitiumdns_api-0.1.1.tar.gz
  • Upload date:
  • Size: 44.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for technitiumdns_api-0.1.1.tar.gz
Algorithm Hash digest
SHA256 ddb9502636bc70bdcc23f72d6a205b2d0f1e39c4c7dcb4fab014b51e22c93ddd
MD5 6305f5d43679ed1c1c51b2d6e34133d7
BLAKE2b-256 314b3dbb1dc64a9d3dd15ea8b57871f19c949cc7eb897a1a4e99240fc072d66e

See more details on using hashes here.

Provenance

The following attestation bundles were made for technitiumdns_api-0.1.1.tar.gz:

Publisher: release.yml on Amateur-God/technitiumdns-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file technitiumdns_api-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for technitiumdns_api-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6bf7cb78f956d65d006b57b90e3bef3ca43e283bc89349dca522aab9095a3f74
MD5 4889e6d045c612cd44f0154b7f18916b
BLAKE2b-256 0de815cb175fcbeeaecb3db83899f61f4fc4be929b6a18ba6139174c02644d85

See more details on using hashes here.

Provenance

The following attestation bundles were made for technitiumdns_api-0.1.1-py3-none-any.whl:

Publisher: release.yml on Amateur-God/technitiumdns-api

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page