Skip to main content

doctorkit · Python

Health-check engine for CLI tools - declare checks, get structured diagnostics with zero dependencies.

Also available in TypeScript/Node.js -> doctorkit-ts

Table of contents

What is doctorkit?

Every serious CLI eventually ships a doctor subcommand - a self-diagnostic that tells users exactly why something isn't working. The problem: every team reimplements the same mechanics from scratch.

doctorkit is that shared engine. You declare the checks; it handles everything else:

  • Running checks grouped by tag with live terminal output
  • Skipping downstream checks when a dependency fails (no point checking auth if the network is down)
  • Per-check timeouts with configurable retries
  • Parallel wave-based execution across independent checks
  • Flagging checks that pass but take too long
  • Emitting structured JSON or JUnit XML for CI pipelines
  • Consistent exit codes (0 ok, 1 fail, 2 exception in a check)
  • Stopping early when a threshold of failures is reached

It ships zero domain checks and has zero runtime dependencies. It has no opinion about what your tool needs to verify - that's entirely up to you.

[network]
  [GOOD] network-reachable
  [GOOD] dns-lookup

[auth]
  [FAIL] api-key-set: ANTHROPIC_API_KEY is not set (12ms)
    -> Run: export ANTHROPIC_API_KEY=sk-ant-...
  [SKIPPED] api-key-format (depends on 'api-key-set' which failed)

[filesystem]
  [GOOD] config-dir
  [GOOD] tmp-writable

2 ok, 1 fail, 1 skipped - 347ms total

Install

pip install doctorkit-core

Requires Python >= 3.9. Zero runtime dependencies.

Quick start

import os
import sys
from doctorkit import Doctor, CheckResult

doctor = Doctor()


@doctor.check("network-reachable", tag="network", timeout=5)
def check_network_reachable():
    import urllib.request
    try:
        urllib.request.urlopen("https://example.com", timeout=3)
        return CheckResult(status="ok", message="internet reachable")
    except Exception as e:
        return CheckResult(
            status="fail",
            message=f"cannot reach internet: {e}",
            hint="Check your network connection or proxy settings.",
        )


# Only runs if network-reachable passed
@doctor.check("api-key-set", tag="auth", depends_on=["network-reachable"])
def check_api_key():
    key = os.getenv("ANTHROPIC_API_KEY", "")
    if not key:
        return CheckResult(
            status="fail",
            message="ANTHROPIC_API_KEY is not set",
            hint="Run: export ANTHROPIC_API_KEY=sk-ant-...",
        )
    return CheckResult(status="ok", message=f"API key found ({key[:8]}...)")


# warn_only: failure is downgraded to warn, exit code stays 0
@doctor.check("config-dir", tag="filesystem", warn_only=True)
def check_config_dir():
    path = os.path.expanduser("~/.config")
    if not os.path.isdir(path):
        return CheckResult(status="fail", message=f"{path} missing", hint=f"mkdir -p {path}")
    return CheckResult(status="ok", message=f"{path} found")


# Wire into your CLI parser - doctorkit never owns the CLI
if __name__ == "__main__":
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("--json", action="store_true")
    p.add_argument("--only", nargs="+")
    p.add_argument("--verbose", action="store_true")
    p.add_argument("--quiet", action="store_true")
    p.add_argument("--list", action="store_true")
    args = p.parse_args()

    if args.list:
        for info in doctor.list_checks():
            print(f"{info.tag}/{info.name}")
        sys.exit(0)

    sys.exit(
        doctor.run(
            json_output=args.json,
            only=args.only,
            verbose=args.verbose,
            quiet=args.quiet,
        )
    )

How it works

  1. You register checks with @doctor.check(...) (decorator) or doctor.add(...) (programmatic). Each check has a name, a tag (group), optional dependencies, and a callable.
  2. When you call doctor.run(), doctorkit topologically sorts the checks to respect their depends_on relationships, then executes them.
  3. If a check's dependency has fail, error, or skipped status, the check is cascade-skipped automatically.
  4. Results are printed live grouped by tag with colored symbols. At the end, a summary line shows counts and total duration.
  5. The return value of run() is an integer exit code you can pass to sys.exit().

Check registration

Decorator syntax

@doctor.check("my-check", tag="network", timeout=10)
def check_something():
    # return a CheckResult, a string, or None
    return CheckResult(status="ok", message="looking good")

The decorator is transparent: it returns the original function unchanged, so type checkers (Pylance, mypy) retain its full signature.

Programmatic syntax (add)

# Useful inside loops or when generating checks dynamically
for port in [5432, 6379, 9200]:
    doctor.add(
        f"port-{port}",
        make_port_check(port),
        tag="deps",
    )

add() accepts the same keyword arguments as @doctor.check.

Check parameters

Parameter Type Default Description
name str required Unique identifier shown in output
tag str "general" Category for visual grouping and only filter
depends_on list[str] [] Check names that must not have failed before this one runs
warn_only bool False Downgrade a fail result to warn - exit code stays 0
timeout float 5.0 Seconds before the check is abandoned and recorded as fail
retries int 0 Extra attempts on failure (0 = one attempt total)
retry_delay float 1.0 Seconds to wait between retries
slow_threshold_ms float | None None Flag this check as slow if it passes but takes longer than this many ms. Overrides any global threshold.
fix_fn callable | None None Optional repair function. Called automatically when run(fix=True) and this check fails. See Fix callbacks.

Return values

A check function may return:

Return value Recorded as
CheckResult(status="ok", message="...") ok
CheckResult(status="warn", message="...", hint="...") warn
CheckResult(status="fail", message="...", hint="...") fail
A string ok, with that string as message
None ok
Raises an exception error (exit code 2)

The hint field appears indented below the check line - use it for actionable fix instructions.

Running checks

Run parameters

exit_code = doctor.run(
    only=["network", "auth"],      # run only these tags
    skip=["slow-check"],           # skip these check names
    quiet=False,                   # summary only
    verbose=False,                 # full detail including durations
    json_output=False,             # structured JSON output
    junit_xml=False,               # JUnit XML output (for CI)
    json_file="results.json",      # also write JSON to this file
    junit_file="results.xml",      # also write JUnit XML to this file
    fail_fast=False,               # stop after first failure
    max_failures=3,                # stop after 3 cumulative failures
    slow_threshold_ms=500,         # flag checks taking over 500ms
    global_timeout=30.0,           # skip remaining after 30s wall-clock
    max_concurrency=4,             # run up to 4 checks in parallel
    output=sys.stdout,             # where to write (defaults to sys.stdout)
)

Filtering: only and skip

# Run only the "network" and "auth" groups
doctor.run(only=["network", "auth"])

# Skip specific checks by name
doctor.run(skip=["slow-check", "optional-check"])

only matches the check's tag. skip matches the check's name. Both can be combined.

Dependency chains

When check B declares depends_on=["a"], doctorkit:

  1. Automatically runs A before B regardless of registration order.
  2. Cascade-skips B (and anything depending on B) if A has status fail, error, or skipped.
  3. Allows B to run normally if A has status warn.
@doctor.check("db-reachable", tag="database")
def check_db():
    ...

# Only runs if db-reachable passed
@doctor.check("db-migrated", tag="database", depends_on=["db-reachable"])
def check_migrations():
    ...

# Only runs if both above passed
@doctor.check("db-seeded", tag="database", depends_on=["db-migrated"])
def check_seed_data():
    ...

Cycles are silently broken (the involved check still runs).

Timeouts and retries

@doctor.check(
    "flaky-api",
    tag="external",
    timeout=10,       # fail if the check takes more than 10s
    retries=3,        # retry up to 3 times on fail or exception
    retry_delay=2.0,  # wait 2s between retries
)
def check_external_api():
    import urllib.request
    resp = urllib.request.urlopen("https://api.example.com/health", timeout=8)
    if resp.status != 200:
        return CheckResult(status="fail", message=f"HTTP {resp.status}")
    return CheckResult(status="ok", message="API healthy")

On timeout, the check is immediately recorded as fail ("timeout after Xs"). Each retry starts a fresh attempt; if any attempt returns ok or warn, the loop stops early. Timeout is enforced with a background thread - your check function runs in the thread, and the timeout waits on a threading event.

Parallel execution (max_concurrency)

By default, checks run sequentially. Set max_concurrency to run independent checks in parallel:

doctor.run(max_concurrency=8)

doctorkit uses wave-based DAG scheduling backed by concurrent.futures.ThreadPoolExecutor:

  • Wave 0: all checks with no dependencies
  • Wave 1: checks whose dependencies are all in wave 0
  • Wave N: checks whose dependencies are all in earlier waves

All checks within the same wave are independent and run concurrently up to max_concurrency threads. Waves are executed in order, so dependency constraints are always respected.

This is particularly useful when checks involve I/O: network calls, database pings, file-system probes. For CPU-bound checks, the GIL limits true parallelism - use threads for I/O, processes for CPU.

Slow check detection

Flag checks that pass but take too long:

# Global threshold: any check taking over 500ms is flagged
doctor.run(slow_threshold_ms=500)
# Per-check override: takes precedence over the global threshold
@doctor.check("cold-cache-query", tag="database", slow_threshold_ms=2000)
def check_cold_cache():
    ...  # allow up to 2s for this one

Slow checks appear with <- slow and their duration even in non-verbose mode. The summary line includes a slow count.

Stop early: fail_fast and max_failures

# Stop after the very first failure
doctor.run(fail_fast=True)

# Stop after 3 cumulative failures/errors
doctor.run(max_failures=3)

After stopping, all remaining checks are recorded as skipped in the output. fail_fast=True is equivalent to max_failures=1.

Global timeout

Stop scheduling new checks once the total wall-clock time exceeds the budget:

# If the run takes more than 30s, remaining unstarted checks are skipped
doctor.run(global_timeout=30.0)

The first check always runs regardless. Running checks are not interrupted - the timeout only prevents new checks from starting.

File output

Write results to a file independently of what is printed to stdout:

# Write JSON to a file - human output still goes to stdout
doctor.run(json_file="results/checks.json")

# Write JUnit XML to a file for CI artifact upload
doctor.run(junit_file="test-results/doctorkit.xml")

# Both files at once, human output on stdout
doctor.run(json_file="results.json", junit_file="results.xml")

# json_file + json_output stdout are independent
doctor.run(json_output=True, json_file="results.json")

json_file and junit_file are fully independent of the json_output and junit_xml stdout options. All combinations are valid.

Programmatic access: run_detailed()

Use run_detailed() to get structured data from the run instead of just an exit code:

from doctorkit import Doctor, RunResult

result: RunResult = doctor.run_detailed()

print(result.exit_code)       # 0, 1, or 2
print(result.summary.ok)      # count of passing checks
print(result.summary.fail)    # count of failed checks
print(result.total_ms)        # total wall-clock time in ms

for check in result.checks:
    if check.status == "fail":
        print(f"{check.name}: {check.message}")

sys.exit(result.exit_code)

Accepts exactly the same parameters as run(). Both produce identical output - the only difference is what they return.

Output formats

Human output (default)

Checks are printed grouped by tag as they complete. Each check line starts with a colored badge:

Badge Background Meaning
[GOOD] green ok
[WARN] yellow warn
[FAIL] red fail
[ERROR] magenta unexpected exception in the check
[SKIPPED] blue skipped (cascade or early stop)

Badges are colored only when writing to a TTY. In non-TTY mode (CI, pipes, redirects) the plain text label is used.

[network]
  [GOOD] network-reachable
  [FAIL] dns-lookup: NXDOMAIN (34ms)
    -> Check your /etc/resolv.conf

[auth]
  [SKIPPED] api-key-set (depends on 'dns-lookup' which failed)

1 ok, 1 fail, 1 skipped - 201ms total

Verbose mode

verbose=True shows the message and duration for every check, not just failures. Exceptions include the full Python traceback.

[network]
  [GOOD] network-reachable: internet reachable (45ms)
  [FAIL] dns-lookup: NXDOMAIN (34ms)
    -> Check your /etc/resolv.conf
    Traceback (most recent call last):
      ...
    socket.gaierror: [Errno -2] Name or service not known

Quiet mode

quiet=True hides all check detail and prints only the summary line:

1 ok, 1 fail, 1 skipped - 201ms total

JSON output

json_output=True emits a single JSON object to stdout. No human-readable output is produced.

{
  "checks": [
    {
      "name": "api-key-set",
      "status": "fail",
      "message": "ANTHROPIC_API_KEY is not set",
      "hint": "Run: export ANTHROPIC_API_KEY=sk-ant-...",
      "tag": "auth",
      "duration_ms": 12.0,
      "is_slow": false,
      "fix_status": null,
      "fix_message": null
    }
  ],
  "summary": {
    "ok": 1,
    "warn": 0,
    "fail": 1,
    "skipped": 1,
    "slow": 0
  },
  "exit_code": 1
}

JUnit XML output

junit_xml=True emits JUnit-compatible XML - readable by most CI systems (GitHub Actions, Jenkins, GitLab CI, etc.).

<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="auth" tests="2" failures="1" skipped="0" time="0.045">
    <testcase name="api-key-set" classname="auth" time="0.012">
      <failure message="ANTHROPIC_API_KEY is not set"/>
    </testcase>
    <testcase name="api-key-format" classname="auth" time="0.000">
      <skipped message="depends on 'api-key-set' which failed"/>
    </testcase>
  </testsuite>
</testsuites>

Checks are grouped into <testsuite> elements by tag. Skipped checks include the cascade reason. Failed checks include the message and traceback when available.

Exit codes

Code Meaning
0 All checks ok or warn
1 At least one check failed
2 At least one check raised an unexpected exception (bug in the check itself)

Listing checks without running

Inspect registered checks without executing them - useful for --list flags:

infos = doctor.list_checks()
# [
#   CheckInfo(name="network-reachable", tag="network", depends_on=[], timeout=5.0, ...),
#   CheckInfo(name="api-key-set", tag="auth", depends_on=["network-reachable"], ...),
# ]

for info in infos:
    print(f"{info.tag}/{info.name}")

list_checks() returns a list of CheckInfo dataclasses. Mutations to the returned list do not affect the doctor instance.

Fix callbacks

Register a repair function alongside any check. When you call doctor.run(fix=True), doctorkit automatically runs the fix for every check that returned fail or raised an exception.

Fixes always run sequentially after the main check loop, regardless of max_concurrency. This keeps system modifications predictable and output unambiguous.

from doctorkit import Doctor, CheckResult, FixResult

doctor = Doctor()


@doctor.check(
    "api-key-set",
    tag="auth",
    fix_fn=lambda: FixResult(status="fixed", message="Opened .env for editing - paste your key and save"),
)
def check_api_key():
    import os
    if not os.getenv("ANTHROPIC_API_KEY"):
        return CheckResult(
            status="fail",
            message="ANTHROPIC_API_KEY is not set",
            hint="Run: export ANTHROPIC_API_KEY=sk-ant-...",
        )
    return CheckResult(status="ok", message="API key found")


exit_code = doctor.run(fix=True)

Output when the check fails:

[auth]
  [FAIL] api-key-set: ANTHROPIC_API_KEY is not set (2ms)
    -> Run: export ANTHROPIC_API_KEY=sk-ant-...

1 fail - 5ms total

[fixes]
  [FIXED] api-key-set: Opened .env for editing - paste your key and save

Fix function return values:

Return value Displayed as
FixResult(status="fixed", message="...") [FIXED] - green background
FixResult(status="fix_failed", message="...") [FIX FAILED] - red background
A string [FIXED] with that string as message
None [FIXED]
Raises an exception [FIX ERROR] - magenta background

Fix functions do not affect the check's status or the run's exit code. The exit code always reflects the check results only.

In JSON mode (json_output=True), each check entry gains two additional fields:

{
  "name": "api-key-set",
  "status": "fail",
  "fix_status": "fixed",
  "fix_message": "Opened .env for editing - paste your key and save",
  ...
}

fix_status and fix_message are null if no fix was attempted for that check.

The has_fix field on CheckInfo (returned by list_checks()) indicates whether a fix function is registered.

Built-in check library

doctorkit.checks is an optional, zero-dependency library of ready-made check factories built entirely on Python's standard library. Import only what you need - nothing is auto-imported when you import doctorkit.

network

from doctorkit.checks.network import http_check, tcp_check, dns_check

# HTTP HEAD request - verifies URL responds with expected status
doctor.add("api-health", http_check("https://api.example.com/health"), tag="network")
doctor.add("api-v2",     http_check("https://api.example.com/v2", expected_status=401), tag="network")

# TCP connection - verifies a port is open and accepting connections
doctor.add("postgres", tcp_check("localhost", 5432), tag="deps")
doctor.add("redis",    tcp_check("localhost", 6379, timeout=3.0), tag="deps")

# DNS resolution
doctor.add("dns-api", dns_check("api.example.com"), tag="network")

env

from doctorkit.checks.env import env_check, envfile_check, envfile_vars_check

# Verify an env var is set, optionally matching a regex
doctor.add("api-key",        env_check("ANTHROPIC_API_KEY"), tag="env")
doctor.add("api-key-format", env_check("ANTHROPIC_API_KEY", pattern=r"sk-ant-.+"), tag="env")

# Verify a .env file exists and is readable
doctor.add("dotenv", envfile_check(".env"), tag="env")

# Verify all variables defined in .env.example are present in .env or os.environ
doctor.add("env-vars", envfile_vars_check(".env.example", env_file=".env"), tag="env")

filesystem

from doctorkit.checks.filesystem import dir_exists_check, file_exists_check, writable_check

doctor.add("logs-dir",   dir_exists_check("logs"), tag="filesystem")
doctor.add("config",     file_exists_check("config.yaml"), tag="filesystem")
doctor.add("tmp-write",  writable_check("/tmp"), tag="filesystem")

process

from doctorkit.checks.process import command_check

# Verify a command exists on PATH, optionally enforce a minimum version
doctor.add("node",   command_check("node",   min_version="18.0"), tag="tools")
doctor.add("python", command_check("python3", min_version="3.9"), tag="tools")
doctor.add("docker", command_check("docker"), tag="tools")
doctor.add("git",    command_check("git",    min_version="2.0"), tag="tools")

command_check uses shutil.which to locate the command and subprocess to read its version output. No external dependencies required.

CLI: doctorkit init

doctorkit init scans a project directory, detects what it contains, and generates a ready-to-run doctor.py with starter checks already wired up.

pip install doctorkit-core

# Scan current directory and write doctor.py
doctorkit init

# Scan a specific directory
doctorkit init path/to/project

What it detects and generates:

File found Checks generated
package.json node and npm command checks
pyproject.toml / requirements.txt / Pipfile python3 command check
docker-compose.yml / Dockerfile docker command check
.env.example / .env envfile_check and per-variable env_check for recognized variable names (e.g. DATABASE_URL, API_KEY, REDIS_URL)

The generated doctor.py is a starting point - open it and add the checks your project actually needs.

python doctor.py           # run all checks
python doctor.py --json    # JSON output for CI

API reference

Doctor()

Creates a new, empty doctor instance. Each CLI command or test suite typically creates its own.

@doctor.check(name, *, tag, depends_on, warn_only, timeout, retries, retry_delay, slow_threshold_ms, fix_fn)

Decorator that registers a check function. See Check parameters for all options.

@doctor.check(
    "my-check",
    tag                  = "network",
    depends_on           = ["other-check"],
    warn_only            = False,
    timeout              = 5.0,
    retries              = 0,
    retry_delay          = 1.0,
    slow_threshold_ms    = None,
    fix_fn               = None,
)
def my_check():
    return CheckResult(status="ok", message="all good")

doctor.add(name, fn, *, tag, depends_on, warn_only, timeout, retries, retry_delay, slow_threshold_ms, fix_fn)

Programmatic alternative to @doctor.check. Accepts the same keyword arguments.

for port in [5432, 6379]:
    doctor.add(f"port-{port}", make_port_check(port), tag="database")

doctor.list_checks() -> list[CheckInfo]

Returns metadata for all registered checks without running them.

doctor.run(...) -> int

Executes all checks and returns an exit code. Full parameters:

Parameter Type Default Description
only list[str] | None None Run only checks whose tag is in this list
skip list[str] | None None Skip checks whose name is in this list
quiet bool False Print only the summary line
verbose bool False Show full detail on every check; errors include traceback
json_output bool False Emit a JSON object instead of human output
junit_xml bool False Emit JUnit XML instead of human output
fail_fast bool False Stop after the first fail or error
max_failures int | None None Stop after this many cumulative failures/errors
slow_threshold_ms float | None None Flag passing checks that exceed this duration in ms
global_timeout float | None None Stop scheduling new checks after this many seconds
max_concurrency int 1 Maximum checks to run in parallel (wave-based)
fix bool False Run fix callbacks for failed checks after the main loop. See Fix callbacks.
output file sys.stdout Where to write output
json_file str | None None Write JSON results to this file path, independently of stdout format
junit_file str | None None Write JUnit XML to this file path, independently of stdout format

doctor.run_detailed(...) -> RunResult

Same as run() but returns a RunResult dataclass instead of a bare exit code. Accepts the same parameters.

from doctorkit import RunResult

result: RunResult = doctor.run_detailed(verbose=True)
sys.exit(result.exit_code)

RunResult

from doctorkit import RunResult

@dataclass
class RunResult:
    exit_code: int
    summary: RunSummary
    checks: list[CheckRecord]
    total_ms: float

RunSummary

from doctorkit import RunSummary

@dataclass
class RunSummary:
    ok: int
    warn: int
    fail: int
    skipped: int
    slow: int

CheckRecord

from doctorkit import CheckRecord

@dataclass
class CheckRecord:
    name: str
    status: str             # "ok", "warn", "fail", "error", or "skipped"
    message: str
    hint: str | None
    tag: str
    duration_ms: float
    is_slow: bool
    skip_reason: str | None
    fix_status: str | None
    fix_message: str | None

CheckResult

from doctorkit import CheckResult

CheckResult(
    status="ok" | "warn" | "fail",
    message="human-readable description",
    hint="optional actionable fix",  # shown indented below for fail/warn
)

FixResult

from doctorkit import FixResult

FixResult(
    status="fixed" | "fix_failed",
    message="description of what was done or why it failed",
)

Returned by fix functions. A raised exception is automatically caught and recorded as fix_error.

CheckInfo

Returned by list_checks(). Read-only dataclass with all check metadata fields, including has_fix: bool which is True when a fix function is registered for that check.

Also available in TypeScript

The TypeScript implementation is spec-identical: same output format, same JSON structure, same exit codes, same API surface.

-> doctorkit-ts - TypeScript/Node.js package

npm install doctorkit-core

License

BSD 2-Clause License

Metadata

Release files for doctorkit-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)

Source distribution for doctorkit-core 0.1.1
File Size Uploaded
doctorkit_core-0.1.1.tar.gz 32.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for doctorkit-core 0.1.1
File Interpreter ABI Platform
doctorkit_core-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 60.6 kB

Release files / doctorkit_core-0.1.1.tar.gz

Download URL doctorkit_core-0.1.1.tar.gz
Size 32.2 kB
Tags Source
SHA-256 checksum
How to use checksums
55bf5685358182cf99765c89bab3268cfa1e7c53e7cdfedfbc99d24f7b4a76ba
BLAKE2b-256 checksum
How to use checksums
1892bc3613df74f90c70abd20085d56a20594e3f8ba0cbfaa9fb4202245110ee
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 Sep 21, 2026.

Transparency log

Release files / doctorkit_core-0.1.1-py3-none-any.whl

Download URL doctorkit_core-0.1.1-py3-none-any.whl
Size 28.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
72f75121b1fe4b109081a07f4957291d0591973a455b67a0fa6009fbe2a67d57
BLAKE2b-256 checksum
How to use checksums
122edbc3465592daaa71f1ea32d453b898a2b35bfdd9fdf988d4a757cd247cfe
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 Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page