opskit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
opskit/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ """opskit — cross-platform diagnostics for engineers, one toolkit for every OS.
2
+
3
+ This is the package root. Public, category-scoped APIs (e.g. ``opskit.dns``) are added
4
+ per feature; see the project constitution for the API-first design rules.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+
11
+ __version__ = "0.1.0" # x-release-please-version
12
+
13
+ __all__ = ["__version__"]
14
+
15
+ # Good-citizen library logging: silent unless the host app configures a handler.
16
+ logging.getLogger("opskit").addHandler(logging.NullHandler())
opskit/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Enable ``python -m opskit``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from opskit.cli import main
6
+
7
+ if __name__ == "__main__": # pragma: no cover
8
+ main()
opskit/cli.py ADDED
@@ -0,0 +1,49 @@
1
+ """Root command-line interface for opskit.
2
+
3
+ Per the constitution the CLI is a thin presentation layer: it parses arguments and
4
+ renders results, holding no business logic. Categories (e.g. ``dns``) register
5
+ themselves here as Typer sub-apps.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import typer
11
+
12
+ from opskit import __version__
13
+ from opskit.dns.cli import app as dns_app
14
+ from opskit.tls.cli import app as tls_app
15
+
16
+ app = typer.Typer(
17
+ name="opskit",
18
+ help="Cross-platform diagnostics for engineers — one toolkit, every OS.",
19
+ no_args_is_help=True,
20
+ add_completion=True,
21
+ )
22
+ app.add_typer(dns_app, name="dns")
23
+ app.add_typer(tls_app, name="tls")
24
+
25
+
26
+ def _version_callback(value: bool) -> None:
27
+ """Print the version and exit when ``--version`` was requested."""
28
+ if value:
29
+ typer.echo(f"opskit {__version__}")
30
+ raise typer.Exit
31
+
32
+
33
+ @app.callback()
34
+ def root(
35
+ version: bool = typer.Option(
36
+ False,
37
+ "--version",
38
+ "-V",
39
+ help="Show the opskit version and exit.",
40
+ callback=_version_callback,
41
+ is_eager=True,
42
+ ),
43
+ ) -> None:
44
+ """Top-level options shared by every opskit command."""
45
+
46
+
47
+ def main() -> None: # pragma: no cover
48
+ """Console-script entry point (``opskit``)."""
49
+ app()
@@ -0,0 +1,8 @@
1
+ """Shared cross-cutting concerns for opskit commands.
2
+
3
+ Holds the pieces every diagnostic category reuses: the exit-code map, the base exception
4
+ hierarchy, the versioned result envelope, and output rendering. Category packages (e.g.
5
+ ``opskit.dns``) build on these; nothing here is category-specific.
6
+ """
7
+
8
+ from __future__ import annotations
@@ -0,0 +1,171 @@
1
+ """Category-agnostic CLI plumbing shared by every command group.
2
+
3
+ Batch-input reading, outcome collection with per-target failure tolerance, aggregate
4
+ exit-code derivation, JSON-envelope emission, and the ``--watch`` loop. Imports only
5
+ ``opskit.core`` — no category models (constitution Art. VII).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import time
12
+ from collections.abc import Callable, Sequence
13
+ from datetime import datetime
14
+ from pathlib import Path
15
+ from typing import Any, TypeVar
16
+
17
+ import typer
18
+
19
+ from opskit.core.errors import OpskitError, UsageError
20
+ from opskit.core.exit_codes import ExitCode, exit_code_for
21
+ from opskit.core.output import make_console
22
+ from opskit.core.result import to_json
23
+
24
+ _T = TypeVar("_T")
25
+
26
+ # (exit code, change-detection signature) returned by a command's single execution.
27
+ ActionResult = tuple[ExitCode, str]
28
+
29
+
30
+ def read_input_file(path: Path) -> list[str]:
31
+ """Read targets from a file, one per line, ignoring blanks and ``#`` comments."""
32
+ try:
33
+ raw = path.read_text(encoding="utf-8")
34
+ except OSError as exc:
35
+ raise UsageError(f"cannot read input file: {path}") from exc
36
+ targets: list[str] = []
37
+ for line in raw.splitlines():
38
+ stripped = line.strip()
39
+ if stripped and not stripped.startswith("#"):
40
+ targets.append(stripped)
41
+ return targets
42
+
43
+
44
+ def collect_targets(positional: str | None, input_file: Path | None) -> list[str]:
45
+ """Combine a positional target with any from ``--input-file`` (positional first)."""
46
+ targets: list[str] = []
47
+ if positional:
48
+ targets.append(positional)
49
+ if input_file is not None:
50
+ targets.extend(read_input_file(input_file))
51
+ if not targets:
52
+ raise UsageError("provide a target argument or --input-file")
53
+ return targets
54
+
55
+
56
+ def collect_outcomes(
57
+ targets: Sequence[str], run_one: Callable[[str], _T]
58
+ ) -> list[tuple[str, _T | None, OpskitError | None]]:
59
+ """Run ``run_one`` for each target, capturing its result or the raised OpskitError."""
60
+ outcomes: list[tuple[str, _T | None, OpskitError | None]] = []
61
+ for target in targets:
62
+ try:
63
+ outcomes.append((target, run_one(target), None))
64
+ except OpskitError as exc:
65
+ outcomes.append((target, None, exc))
66
+ return outcomes
67
+
68
+
69
+ def echo_failures(
70
+ outcomes: Sequence[tuple[str, object, OpskitError | None]],
71
+ ) -> None:
72
+ """Print each failed target's error to stderr (human mode / no-success bail)."""
73
+ for target, _, error in outcomes:
74
+ if error is not None:
75
+ typer.echo(f"error: {target}: {error.message}", err=True)
76
+
77
+
78
+ def aggregate_exit(codes: Sequence[ExitCode]) -> ExitCode:
79
+ """Batch rule (Art. IX): 0 if all succeed; the class code if uniform; else PARTIAL."""
80
+ if all(code is ExitCode.OK for code in codes):
81
+ return ExitCode.OK
82
+ distinct = {code for code in codes if code is not ExitCode.OK}
83
+ if len(distinct) == 1 and all(code is not ExitCode.OK for code in codes):
84
+ return next(iter(distinct))
85
+ return ExitCode.PARTIAL
86
+
87
+
88
+ def aggregate_outcome_exit(
89
+ outcomes: Sequence[tuple[str, object, OpskitError | None]],
90
+ ) -> ExitCode:
91
+ """:func:`aggregate_exit` over (target, result, error) outcome tuples."""
92
+ return aggregate_exit(
93
+ [
94
+ ExitCode.OK if error is None else exit_code_for(error)
95
+ for _, _, error in outcomes
96
+ ]
97
+ )
98
+
99
+
100
+ def emit_envelopes(envelopes: Sequence[dict[str, Any]], *, jsonl: bool) -> None:
101
+ """Emit JSON envelopes: NDJSON (one per line), a bare object, or an array."""
102
+ if jsonl:
103
+ for envelope in envelopes:
104
+ typer.echo(to_json(envelope, indent=None))
105
+ elif len(envelopes) == 1:
106
+ typer.echo(to_json(envelopes[0]))
107
+ else:
108
+ typer.echo(json.dumps(envelopes, indent=2))
109
+
110
+
111
+ def parse_interval(text: str) -> float:
112
+ """Parse a --watch interval like '5', '5s', '250ms', or '2m' into seconds."""
113
+ value = text.strip().lower()
114
+ try:
115
+ if value.endswith("ms"):
116
+ seconds = float(value[:-2]) / 1000.0
117
+ elif value.endswith("s"):
118
+ seconds = float(value[:-1])
119
+ elif value.endswith("m"):
120
+ seconds = float(value[:-1]) * 60.0
121
+ else:
122
+ seconds = float(value)
123
+ except ValueError as exc:
124
+ raise UsageError(f"invalid --watch interval: {text}") from exc
125
+ if seconds <= 0:
126
+ raise UsageError("--watch interval must be positive")
127
+ return seconds
128
+
129
+
130
+ def watch(
131
+ action: Callable[[], ActionResult],
132
+ *,
133
+ interval: float,
134
+ no_color: bool,
135
+ ) -> ExitCode:
136
+ """Re-run ``action`` every ``interval`` seconds until interrupted, flagging changes."""
137
+ console = make_console(no_color=no_color)
138
+ previous: str | None = None
139
+ code = ExitCode.OK
140
+ try:
141
+ while True:
142
+ code, signature = action()
143
+ if previous is None:
144
+ status = "initial"
145
+ elif signature != previous:
146
+ status = "changed"
147
+ else:
148
+ status = "no change"
149
+ stamp = datetime.now().strftime("%H:%M:%S")
150
+ console.print(
151
+ f"[dim]-- {stamp} - {status} - next in {interval:g}s (Ctrl-C to stop) --[/dim]"
152
+ )
153
+ previous = signature
154
+ time.sleep(interval) # looked up at call time so tests can patch it
155
+ except KeyboardInterrupt:
156
+ return code
157
+
158
+
159
+ def run_or_watch(
160
+ action: Callable[[], ActionResult], *, watch_spec: str | None, no_color: bool
161
+ ) -> None:
162
+ """Run ``action`` once, or repeatedly under --watch; then exit with its code."""
163
+ if watch_spec is not None:
164
+ try:
165
+ interval = parse_interval(watch_spec)
166
+ except UsageError as error:
167
+ typer.echo(f"error: {error.message}", err=True)
168
+ raise typer.Exit(int(ExitCode.USAGE)) from error
169
+ raise typer.Exit(int(watch(action, interval=interval, no_color=no_color)))
170
+ code, _ = action()
171
+ raise typer.Exit(int(code))
opskit/core/errors.py ADDED
@@ -0,0 +1,37 @@
1
+ """Base exception hierarchy for opskit.
2
+
3
+ The library layer raises these; only the CLI catches them and maps them to exit codes
4
+ (see :mod:`opskit.core.exit_codes`). No raw third-party exception should reach the user.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from opskit.core.exit_codes import ExitCode
10
+
11
+
12
+ class OpskitError(Exception):
13
+ """Base class for all opskit errors.
14
+
15
+ Each subclass declares the process ``exit_code`` it maps to, so the CLI's exit-code
16
+ resolution stays category-agnostic (see :func:`opskit.core.exit_codes.exit_code_for`).
17
+
18
+ Attributes:
19
+ message: Human-readable summary of what went wrong.
20
+ hint: Optional actionable next step for the user (e.g. "try a different --server").
21
+ """
22
+
23
+ code: str = "error"
24
+ exit_code: ExitCode = ExitCode.ERROR # generic failure unless a subclass narrows it
25
+
26
+ def __init__(self, message: str, *, hint: str | None = None) -> None:
27
+ """Initialize the error with a message and an optional remediation hint."""
28
+ super().__init__(message)
29
+ self.message = message
30
+ self.hint = hint
31
+
32
+
33
+ class UsageError(OpskitError):
34
+ """Invalid user input (bad name/IP, unknown option). Raised before any network I/O."""
35
+
36
+ code = "usage_error"
37
+ exit_code = ExitCode.USAGE
@@ -0,0 +1,39 @@
1
+ """Structured process exit codes and the exception-to-code mapping.
2
+
3
+ Exit codes are part of the public, SemVer-governed contract (see contracts/cli.md) so scripts
4
+ can branch on the outcome class without parsing text.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from enum import IntEnum
10
+ from typing import TYPE_CHECKING
11
+
12
+ if TYPE_CHECKING:
13
+ from opskit.core.errors import OpskitError
14
+
15
+
16
+ class ExitCode(IntEnum):
17
+ """Documented exit codes returned by the CLI."""
18
+
19
+ OK = 0
20
+ ERROR = 1
21
+ USAGE = 2
22
+ NXDOMAIN = 3
23
+ SERVFAIL = 4
24
+ REFUSED = 5
25
+ TIMEOUT = 6
26
+ PARTIAL = 7
27
+ CONNECT_FAILED = 8
28
+ HANDSHAKE_FAILED = 9
29
+ CERT_INVALID = 10
30
+ CERT_EXPIRING = 11
31
+
32
+
33
+ def exit_code_for(error: OpskitError) -> ExitCode:
34
+ """Return the :class:`ExitCode` an error declares.
35
+
36
+ Each error type owns its ``exit_code`` (set on the class), so this mapping stays
37
+ category-agnostic — adding a new category (net/tls/ad) never touches ``core``.
38
+ """
39
+ return error.exit_code
opskit/core/output.py ADDED
@@ -0,0 +1,19 @@
1
+ """Shared, category-agnostic console setup.
2
+
3
+ ``rich.Console`` auto-detects a TTY (plain when piped) and honors ``NO_COLOR``; ``no_color``
4
+ forces plain output regardless. Category-specific rendering lives with its category (e.g.
5
+ :mod:`opskit.dns.output`) so ``core`` stays free of category models.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from rich.console import Console
11
+
12
+
13
+ def make_console(*, no_color: bool = False) -> Console:
14
+ """Return a console configured for the current output context.
15
+
16
+ ``no_color`` forces plain output when set; when unset, ``Console`` is left to its default
17
+ (which honors the ``NO_COLOR`` environment variable and auto-detects a non-TTY).
18
+ """
19
+ return Console(no_color=True if no_color else None, highlight=False)
opskit/core/result.py ADDED
@@ -0,0 +1,41 @@
1
+ """Versioned JSON output envelope shared by every command.
2
+
3
+ The envelope shape is the public ``--json`` contract (contracts/json-envelope.md); its schema
4
+ changes are governed by SemVer. The current ``schema_version`` is ``"1"``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from typing import Any
11
+
12
+ from opskit.core.errors import OpskitError
13
+
14
+ SCHEMA_VERSION = "1"
15
+
16
+
17
+ def build_envelope(
18
+ *,
19
+ command: str,
20
+ query: dict[str, Any],
21
+ result: dict[str, Any] | None,
22
+ error: OpskitError | None,
23
+ elapsed_ms: float,
24
+ ) -> dict[str, Any]:
25
+ """Assemble the versioned envelope for a single command invocation."""
26
+ error_obj: dict[str, Any] | None = None
27
+ if error is not None:
28
+ error_obj = {"code": error.code, "message": error.message, "hint": error.hint}
29
+ return {
30
+ "schema_version": SCHEMA_VERSION,
31
+ "command": command,
32
+ "query": query,
33
+ "result": result,
34
+ "error": error_obj,
35
+ "elapsed_ms": round(elapsed_ms, 3),
36
+ }
37
+
38
+
39
+ def to_json(envelope: dict[str, Any], *, indent: int | None = 2) -> str:
40
+ """Serialize an envelope to a JSON string (single object)."""
41
+ return json.dumps(envelope, indent=indent)
opskit/dns/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # `opskit dns` — DNS diagnostics
2
+
3
+ Read-only DNS troubleshooting that behaves **identically on Windows, macOS, and Linux** — a single
4
+ replacement for `nslookup` / `dig` / `Resolve-DnsName`. Available both as CLI commands and as an
5
+ importable Python API.
6
+
7
+ > Part of [**opskit**](../../../README.md). See the root README for install and project-wide docs.
8
+
9
+ ---
10
+
11
+ ## Contents
12
+
13
+ - [Commands](#commands)
14
+ - [`opskit dns lookup`](#opskit-dns-lookup)
15
+ - [`opskit dns reverse`](#opskit-dns-reverse)
16
+ - [Modes](#modes) — `--all`, `--diff`, `--trace`, `--watch`
17
+ - [Bulk lookups](#bulk-lookups) — `--input-file`
18
+ - [Output & exit codes](#output--exit-codes)
19
+ - [Use as a Python library](#use-as-a-python-library)
20
+
21
+ ---
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ opskit dns lookup example.com # A records, pretty table
27
+ opskit dns lookup example.com -t MX -t TXT # specific record types
28
+ opskit dns lookup example.com --all # every common record type at once
29
+ opskit dns reverse 8.8.8.8 # PTR (IP → hostname)
30
+ opskit dns lookup example.com --json # machine-readable envelope
31
+ ```
32
+
33
+ Help is grouped into panels (**Query**, **Modes**, **Query controls**, **Output**) with worked
34
+ examples in the footer — think `man`, but organized and copy-pasteable:
35
+
36
+ ```bash
37
+ opskit dns --help # the DNS command group
38
+ opskit dns lookup --help # a specific command (grouped options + examples)
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Commands
44
+
45
+ ### `opskit dns lookup`
46
+
47
+ Forward DNS lookup for one or more hostnames.
48
+
49
+ ```bash
50
+ opskit dns lookup TARGET [OPTIONS]
51
+ ```
52
+
53
+ | Option | Description | Default |
54
+ |---|---|---|
55
+ | `TARGET` | Hostname to resolve (optional if `--input-file` is used) | — |
56
+ | `-t, --type` | Record type(s): `A AAAA MX TXT CNAME NS SOA SRV CAA` (repeatable) | `A` |
57
+ | `--all` | Query **all** common record types at once | off |
58
+ | `--diff` | Query every `--server` and compare/diff answers | off |
59
+ | `--trace` | Show the iterative resolution path (root → authoritative) | off |
60
+ | `-s, --server` | Resolver(s) to query (repeatable); defaults to the system resolver | system |
61
+ | `--transport` | `udp` \| `tcp` \| `auto` (UDP then TCP on truncation) | `auto` |
62
+ | `--timeout` | Per-attempt timeout, seconds | `5.0` |
63
+ | `--retries` | Retry count on timeout | `2` |
64
+ | `--port` | Resolver port | `53` |
65
+ | `-i, --input-file` | File of targets, one per line (`#` comments allowed) | — |
66
+ | `--json` | Emit the versioned JSON envelope | off |
67
+ | `--jsonl` | Emit one JSON envelope per line (NDJSON) | off |
68
+ | `--watch` | Re-run every interval (e.g. `5s`, `2m`, `250ms`) until Ctrl-C | off |
69
+ | `--no-color` | Disable colored output | off |
70
+
71
+ ### `opskit dns reverse`
72
+
73
+ Reverse (PTR) lookup for one or more IP addresses. Accepts IPv4 and IPv6.
74
+
75
+ ```bash
76
+ opskit dns reverse IP [OPTIONS]
77
+ ```
78
+
79
+ Supports the same query controls, output, `--trace`, `--watch`, and `--input-file` options as
80
+ `lookup` (record-type options don't apply).
81
+
82
+ ---
83
+
84
+ ## Modes
85
+
86
+ ### `--all` — one-stop lookup
87
+
88
+ DNS `ANY` queries are deprecated (RFC 8482), so `--all` fans out across every common record type
89
+ and aggregates the results. It's **resilient**: if a resolver refuses one type, that type is
90
+ skipped rather than failing the whole lookup — you still get everything that exists.
91
+
92
+ ```bash
93
+ opskit dns lookup cloudflare.com --all
94
+ ```
95
+
96
+ ### `--diff` — multi-resolver comparison
97
+
98
+ Ask the same question of several resolvers and see **who returns what** — the differing resolver
99
+ is highlighted. Great for propagation lag, split-horizon/GeoDNS, or spotting a broken/poisoned
100
+ resolver.
101
+
102
+ ```bash
103
+ opskit dns lookup example.com --diff -s 1.1.1.1 -s 8.8.8.8 -s 9.9.9.9
104
+ ```
105
+
106
+ Exit code is `0` when all resolvers agree and `7` when they differ (TTLs are ignored — only real
107
+ answer differences count), so you can alert on divergence in scripts.
108
+
109
+ ### `--trace` — the resolution path
110
+
111
+ An iterative walk from the root servers, following each delegation down to the authoritative
112
+ answer — like `dig +trace`, one row per hop.
113
+
114
+ ```bash
115
+ opskit dns lookup www.wikipedia.org --trace
116
+ opskit dns reverse 8.8.8.8 --trace
117
+ ```
118
+
119
+ ### `--watch` — live re-run
120
+
121
+ Re-run on an interval until Ctrl-C, flagging when the **answer changes** (TTL changes are
122
+ ignored). Ideal for watching propagation or failover.
123
+
124
+ ```bash
125
+ opskit dns lookup example.com --watch 30s
126
+ opskit dns lookup example.com --all --watch 10s # modes compose
127
+ ```
128
+
129
+ ---
130
+
131
+ ## Bulk lookups
132
+
133
+ Feed many targets from a file with `-i/--input-file` (one per line; blank lines and `#` comments
134
+ are skipped). Combine with a positional target if you like.
135
+
136
+ ```bash
137
+ opskit dns lookup -i hosts.txt # per-target tables
138
+ opskit dns lookup -i hosts.txt --jsonl | jq . # NDJSON, one object per line
139
+ opskit dns reverse -i ips.txt --json # JSON array
140
+ ```
141
+
142
+ For a batch, the exit code is `0` only if **every** target succeeds; otherwise `7` (partial).
143
+
144
+ ---
145
+
146
+ ## Output & exit codes
147
+
148
+ - **Human** (default): colorized tables, auto-plain when piped; honors `NO_COLOR` and `--no-color`.
149
+ - **`--json`**: a stable, versioned envelope (`schema_version`, `command`, `query`, `result`,
150
+ `error`, `elapsed_ms`); an array for batches.
151
+ - **`--jsonl`**: NDJSON — one envelope per line, ideal for `jq` / streaming.
152
+
153
+ ```json
154
+ {
155
+ "schema_version": "1",
156
+ "command": "dns.lookup",
157
+ "query": { "target": "example.com", "record_types": ["A"], "servers": [], "transport": "auto" },
158
+ "result": { "outcome": "ok", "resolver": "1.1.1.1", "records": [ { "type": "A", "value": "93.184.216.34", "ttl": 300 } ] },
159
+ "error": null,
160
+ "elapsed_ms": 12.3
161
+ }
162
+ ```
163
+
164
+ Exit codes are documented and scriptable:
165
+
166
+ | Code | Meaning |
167
+ |---|---|
168
+ | `0` | success |
169
+ | `1` | generic error (uncategorized failure) |
170
+ | `2` | usage error (bad input; before any network) |
171
+ | `3` | NXDOMAIN (name does not exist) |
172
+ | `4` | SERVFAIL |
173
+ | `5` | REFUSED |
174
+ | `6` | TIMEOUT / no response |
175
+ | `7` | PARTIAL (batch had a failure, or resolvers disagreed) |
176
+
177
+ ---
178
+
179
+ ## Use as a Python library
180
+
181
+ The DNS API is **API-first** — every capability is importable, returns typed results, and raises
182
+ typed exceptions (it never prints or calls `sys.exit`). The package ships `py.typed`.
183
+
184
+ ```python
185
+ from opskit.dns import lookup, reverse, lookup_all, compare, trace
186
+ from opskit.dns import NxDomain, DnsTimeout
187
+
188
+ result = lookup("example.com", ["A", "MX"], server="1.1.1.1", timeout=3)
189
+ if result.ok:
190
+ for record in result: # results are iterable
191
+ print(record.type.value, record.value, record.ttl)
192
+
193
+ everything = lookup_all("example.com") # all record types
194
+ ptr = reverse("8.8.8.8") # PTR
195
+ cmp = compare("example.com", ["1.1.1.1", "8.8.8.8"]) # multi-resolver
196
+ print("consistent:", cmp.consistent)
197
+ hops = trace("www.wikipedia.org") # resolution path
198
+
199
+ try:
200
+ lookup("does-not-exist.invalid")
201
+ except NxDomain as exc:
202
+ print(exc.message, "—", exc.hint)
203
+ ```
204
+
205
+ Results serialize cleanly: `result.to_dict()` / the envelope shape match the CLI's `--json`.
opskit/dns/__init__.py ADDED
@@ -0,0 +1,61 @@
1
+ """DNS diagnostics — importable API and CLI sub-app.
2
+
3
+ Public surface (SemVer-governed): :func:`lookup`, the typed models, and the DNS exception
4
+ hierarchy. Failures raise; nothing here prints or exits the process.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from opskit.dns.api import (
10
+ compare,
11
+ lookup,
12
+ lookup_all,
13
+ reverse,
14
+ reverse_trace,
15
+ trace,
16
+ )
17
+ from opskit.dns.errors import (
18
+ DnsError,
19
+ DnsRefused,
20
+ DnssecError,
21
+ DnsTimeout,
22
+ NxDomain,
23
+ ServerFailure,
24
+ )
25
+ from opskit.dns.models import (
26
+ DnsQuery,
27
+ DnsRecord,
28
+ LookupResult,
29
+ Outcome,
30
+ RecordType,
31
+ Resolver,
32
+ ResolverAnswer,
33
+ ResolverComparison,
34
+ TraceStep,
35
+ Transport,
36
+ )
37
+
38
+ __all__ = [
39
+ "DnsError",
40
+ "DnsQuery",
41
+ "DnsRecord",
42
+ "DnsRefused",
43
+ "DnsTimeout",
44
+ "DnssecError",
45
+ "LookupResult",
46
+ "NxDomain",
47
+ "Outcome",
48
+ "RecordType",
49
+ "Resolver",
50
+ "ResolverAnswer",
51
+ "ResolverComparison",
52
+ "ServerFailure",
53
+ "TraceStep",
54
+ "Transport",
55
+ "compare",
56
+ "lookup",
57
+ "lookup_all",
58
+ "reverse",
59
+ "reverse_trace",
60
+ "trace",
61
+ ]