Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

hyrum

Named after Hyrum's law: once you have enough users, every observable behaviour of your code is depended on by somebody. This tool exists to find out who that "somebody" is — by running a proposed dependency change against a fleet of consumer repos before you ship it.

Bulk-run a check (typically lint or unit tests) across many charm repositories, optionally swapping out one of their dependencies first — for example, pointing every charm's ops dependency at a development branch of the operator repo to see which charms it breaks.

The primary use case today is swapping out ops (and its optional testing / tracing companions). The patcher layer is built as an abstraction so other dependencies (e.g. individual charm libraries) can be swapped in later without rewriting the runner.

Two runner backends are supported:

  • tox — runs tox -e <env> in each charm.
  • make — runs make <target> in each charm.

The runner is auto-detected per charm (tox.ini → tox, Makefile → make), with a CLI flag to force a specific one.

Status

Early-stage carve-out from charm-analysis/tools/super-tox.py. Scope during the 26.10 cycle is lint and unit tests only — integration tests are explicitly out of scope.

Non-goals

  • Running integration tests.
  • Acting as a general-purpose CI orchestrator.

Host prerequisites

A non-trivial fraction of charms in the curated list pull C/Rust extensions that pip / uv will build from source if no wheel is available for the host's Python. On a fresh Ubuntu host, missing build tools surface as failed outcomes with messages like "command 'x86_64-linux-gnu-gcc' failed: No such file" or "fatal error: Python.h / ffi.h: No such file", which is noise rather than a charm regression.

To get a clean signal against the curated charm list, install:

sudo apt-get install -y \
    build-essential \
    pkg-config \
    libffi-dev \
    libpq-dev \
    libmariadb-dev \
    python3-dev   # or python3.<minor>-dev matching the Python uv selects

# Poetry is invoked by ~5 % of charms' tox envs; install it if you want
# those to run instead of failing with "No such file or directory:
# 'poetry'".
uv tool install poetry

A handful of charms shell out to other tools such as yq or go from their tox env or Makefile. Those aren't installed up-front since they only affect a few charms in the curated list; they show up as failed (not patcher_error) with a command not found line in the per-charm log. Install the missing tool to surface the underlying charm result. A missing tox or make itself is caught up front by --preflight (on by default) rather than once per charm.

Some charms also pull C/Rust extensions whose latest releases pre-date the host's Python version. PyO3 < 0.23 can't build against Python 3.14 unless you opt in with the stable-ABI escape hatch (unit in testenv:unit.… is the tox env name hyrum invokes via tox -e unit, i.e. the charm's unit-test environment):

export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
export TOX_OVERRIDE='testenv:unit.pass_env+=PYO3_USE_ABI3_FORWARD_COMPATIBILITY'

If you also want -Werror semantics (warnings promoted to errors), inject PYTHONWARNINGS=error via pass_env, not set_env:

export PYTHONWARNINGS=error
export TOX_OVERRIDE='testenv:unit.pass_env+=PYTHONWARNINGS;testenv:unit.pass_env+=PYO3_USE_ABI3_FORWARD_COMPATIBILITY'

The intuitive form set_env+=PYTHONWARNINGS=error looks correct but silently drops anything the charm's [testenv] set via set_env (most commonly PYTHONPATH), so tests that do from charm import … fail at collection with ModuleNotFoundError — a misleading "regression" that isn't a warning at all. pass_env+= doesn't touch set_env, so the charm's PYTHONPATH stays intact and the warning still propagates.

Empirically (Ubuntu Resolute, system Python 3.14, 145 runnable charms in the curated list as of 2026-05): a host with none of these installed passes ~40 %; adding build-essential + python3.14-dev lifts that to ~60 %; the full apt list above gets to ~64 %; the PyO3 forward-compat flag adds ~3 % more, topping out around 67 %. The residual ~33 % is genuine charm-side breakage (test failures, dependencies pinned to versions that don't build on the host Python) and is not something hyrum itself can move.

Usage

# Install the initial pre-release version:
uv tool install --prerelease=allow hyrum

# Grab the curated charm list (or bring your own CSV with the same
# columns and pass it via --source):
mkdir -p charm-list
curl -sSfL -o charm-list/charms.csv \
    https://raw.githubusercontent.com/canonical/hyrum/main/charm-list/charms.csv

# Populate the local cache with every charm in the CSV:
hyrum get-charms

# --repo and --limit narrow that down, for populating the cache
# a slice at a time:
hyrum get-charms --repo '^ubuntu-' --limit 50

# Run `tox -e unit` across every charm in the default cache
# (~/.cache/hyrum/charms), with ops swapped to the `fix/X` branch of
# canonical/operator. Override the charms directory with --charms-dir or
# the HYRUM_CHARMS environment variable.
hyrum check unit --workers 8 --patch 'ops @ canonical:fix/X'

# --patch takes a PEP 508 requirement; may be given multiple times.
# Other accepted forms for ops: a PyPI version (`ops==2.17.0`), a
# `git+<url>[@branch]` reference (`ops @ git+https://…/operator@fix/X`), a
# plain `https://…/operator[@branch]` URL, or a local checkout
# (`ops @ /path/to/operator`, `ops @ ~/operator`,
# `ops @ file:///path/to/operator`). The `owner:branch` shorthand is
# ops-only.
#
# Swap any other dependency the same way, e.g.:
#   hyrum check unit --patch 'requests==2.31.0'
#   hyrum check unit --patch 'requests @ git+https://github.com/psf/requests@main'
# Patching ops is the default if no --patch is given; pass an explicit
# --patch for another package (without ops) to leave ops alone.

# Force the make runner (default is auto-detect: tox.ini -> tox,
# Makefile -> make, fall back to the other if the target is missing):
hyrum check unit --runner make

# Skip the dependency swap; just check how the charms behave as-pinned:
hyrum check unit --no-patch

# Only run for charms that use the Scenario testing framework:
hyrum check unit --framework scenario

# Always exit 0, even if some charms fail (default is exit non-zero on
# any failure):
hyrum check unit --no-fail

# Dump each charm's stdout, stderr, and run metadata to a per-charm
# file under the given directory for offline triage:
hyrum check unit --log-dir ~/hyrum-runs/logs

# Which charms does an ops branch break? Save an unpatched baseline, save
# a patched run, and diff the two:
hyrum check unit --no-patch --save baseline.json
hyrum check unit --patch 'ops @ canonical:some-branch' --save patched.json
hyrum compare baseline.json patched.json

# Track fleet drift over time: save each run and diff any two. Charms are
# keyed by owner/name rather than by cache path, so runs saved on
# different hosts or from different checkouts compare fine:
hyrum check unit --save "runs/$(date +%F).json"
hyrum compare runs/2026-06-28.json runs/2026-07-05.json

# Consecutive runs of the same target are kept automatically, so the
# common case needs no bookkeeping at all:
hyrum check unit
hyrum compare ~/.cache/hyrum/results/unit.auto.prev.json \
              ~/.cache/hyrum/results/unit.auto.json

# CI gate over a stored baseline: exit 1 on a regression (a charm that
# passed in the baseline now fails, or hits a new timeout or patcher
# error), 2 if the runs share no charms and cannot be compared:
hyrum compare baseline.json current.json --fail-on-regression

# The same diff as a markdown table (one row per non-passing charm, with
# one-line failure summaries), or as machine-readable JSON:
hyrum compare baseline.json current.json --format markdown
hyrum compare baseline.json current.json --format json

# Runs leave a .tox (and tool caches, and __pycache__) in every charm
# they touch, which reaches tens of GB across the collection. Reclaim
# that space without throwing away the clones:
hyrum clean --dry-run
hyrum clean

Output statuses:

status meaning
passed the runner exited 0
failed the runner exited non-zero
no_target tox env / make target not present in this charm (skipped, not failed)
timeout killed after --timeout seconds
runner_error tox / make itself could not be launched (a host problem, not a charm)
patcher_error the dependency swap could not be applied (distinct from a tox failure)
skipped filtered out before the run (regex, ignore-list, no runnable target, …)

Dependency-swap scope

Today only the ops family (with optional testing / tracing extras → ops-scenario / ops-tracing) is handled by the built-in patcher. The patcher layer is a Patcher protocol so a future charm-library patcher (vendored lib/charms/…/v<n>/<file>.py swapped from a git source) can plug in without changes elsewhere.

Configuration

hyrum.toml (path overridable via --config) supports an [ignore] table that maps a category to a list of repo paths to skip. Categories are free-form; their name shows up in the run output as the skip reason. Example:

[ignore]
expensive = ["argo-operators", "mysql-router-k8s"]
manual    = ["opensearch-operator"]

License

Apache 2.0. See LICENSE.txt.

Metadata

Release files for hyrum 1.0.0b1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for hyrum 1.0.0b1
File Size Uploaded Provenance
hyrum-1.0.0b1.tar.gz 209.1 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for hyrum 1.0.0b1
File Interpreter ABI Platform
hyrum-1.0.0b1-py3-none-any.whl Python 3 none any Details

Total release size: 310.3 kB

Release files / hyrum-1.0.0b1.tar.gz

Download URL hyrum-1.0.0b1.tar.gz
Size 209.1 kB
Tags Source
SHA-256 checksum
How to use checksums
f268a4f09bc93693697156671a9dabda5a3c2157e5d3d78a1f30f8b23ea079f9
BLAKE2b-256 checksum
How to use checksums
0e4a57716036b10539bbde616bc53104681c3a12da32d945f2d2c72064343b74
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 17, 2026.

Transparency log

Release files / hyrum-1.0.0b1-py3-none-any.whl

Download URL hyrum-1.0.0b1-py3-none-any.whl
Size 101.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
87209715462f185cb7be5763f65624964563ed773eb339f3e336f84bce538914
BLAKE2b-256 checksum
How to use checksums
acda356a5e77960a1c3a1e21526d7798a69075481c6d649004d38104ae82eb23
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 17, 2026.

Transparency log

Security for hyrum 1.0.0b1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for hyrum-1.0.0b1.tar.gz

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 17, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

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