Skip to main content

isodendron

CI PyPI Python License: MIT

A deterministic decision-tree library where the target never influences how the tree is split.

isodendron grows a tree by repeatedly asking a single question: which condition divides the samples most evenly? Nothing else. The class labels are only counted inside the leaves, once the geometry of the tree is already fixed. That makes training fast, reproducible and free of the usual information-gain machinery — and it produces trees that behave very differently from CART or ID3, in ways this README tries to make explicit.

The name comes from the isodieretic split (Greek isos, equal, and dieretos, divided): the process of finding the condition — a categorical value or a numeric threshold — that maximises the equity in the number of resulting samples between the branches.

The library is written in C11 with no dependencies beyond the platform threading API, and ships with a thin Python binding built on ctypes.


Contents


Installation

pip install isodendron

Wheels are published for Windows, Linux and macOS. They already contain the compiled C library, so no compiler is needed. The package requires Python 3.11 or newer.

To work from a checkout:

git clone git@github.com:washedfrere/isodendron.git
cd isodendron
python -m pip install -e ".[dev]"

The isodieretic split

For every candidate feature and every candidate value, the samples are split in two: those equal to the value, and those different from it. The quality of that split is

score = min(n_equal, n_distinct)

which is simply the size of the smaller branch — maximised means as balanced as possible. For each feature the best value is kept, and among the features the one with the highest score wins.

Three consequences follow, and they explain almost all the behaviour you will observe:

  1. The target is never read during training. Two datasets with identical features and completely different labels produce exactly the same tree.
  2. Training is deterministic. There is no random seed, no shuffling, no tie-breaking by trial. The same input gives the same bytes, always.
  3. The tree learns the geometry of the data, not the labels. If the labels happen to correlate with a balanced split, the tree will capture them. If they correlate with something else — a skewed split, a rare value, an imbalanced threshold — it will not, no matter how deep you grow it.

A leaf is closed when any of these holds:

Condition Meaning
depth >= max_deep Tree budget exhausted.
K <= 1 Only one class remains.
n_mode / N >= P_MIN The leaf is already pure enough.

P_MIN is expressed as an exact fraction, not a float: the test is n_mode * p_min_den >= N * p_min_num, so there is no floating-point rounding anywhere in training.

Inside a leaf the mode is the most frequent class. Ties are broken by taking the lower median of the tied classes — deterministic, and biased towards the smaller label. P is n_mode / N, the purity of the leaf.


Building from source

The C library is built with CMake and packaged with scikit-build-core. The normal entry point is:

python -m pip install .
# or, for development
python -m pip install -e ".[dev]"

To build and test the C side on its own:

cmake -S . -B build -DISODENDRON_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure

The sources are plain C11 and compile without warnings under MSVC and gcc with -Wall -Wextra. No compiler extensions are used.

Files:

csrc/src/isodendron.c   tree construction, prediction, partial_fit
csrc/src/arena.c        per-subtree bump allocator
csrc/src/counts.c       open-addressing counter with O(1) increments
csrc/src/blob.c         serialisation of the tree
csrc/src/blob.h         format constants
csrc/include/isodendron.h
csrc/tests/test_isodendron.c

Quick start (Python)

from isodendron import Isodendron

# Each sample is (features, label).
data = [
    ([0, 3, 1], 0),
    ([1, 3, 0], 1),
    ([0, 4, 0], 0),
    ([1, 4, 1], 1),
]

model = Isodendron().fit(data, max_deep=6, p_min=(1, 1))

model.predict([[0, 3, 1], [1, 4, 1]])   # -> [0, 1]
model.predict_one([0, 3, 1])            # -> 0

The binding looks for the shared library in this order:

  1. the ISODENDRON_LIB environment variable, if it points at an existing file;
  2. the isodendron package resource installed by the wheel.

If you get a "library not found" error, reinstall the package (python -m pip install --force-reinstall isodendron) or build it from the source tree with python -m pip install ..


Python API

Isodendron

Isodendron(blob: bytes | None = None, metadata: dict | None = None)

An empty model, or one reconstructed from raw bytes. For files use Isodendron.load() instead.

Method Returns Description
fit(data, max_deep=8, p_min=(1, 1), *, hilos=1, max_targets=10, feature_names=None) self Train from (features, label) pairs.
predict(samples) list[int] One class per sample.
predict_one(sample) int Single sample.
predict_full(samples) list[tuple[int, float, int, int]] (mode, P, n_mode, N) per sample.
predict_leaf(samples) (list[int], list[int]) Predictions and the preorder leaf index of each sample.
partial_fit(data, max_targets=None) Isodendron Accumulate samples into the existing leaves; returns a new model.
partial_fit_diag(data, max_targets=None, capacidad=64) (Isodendron, dict) As above, plus diagnostics.
dump() list[dict] Every leaf: path, counts, mode, P, distribution.
save(path, overwrite=False) Path Write a .isod file.
Isodendron.load(path) Isodendron Class method. Read a .isod file.
Property Description
blob Raw bytes of the tree, or None.
metadata Copy of the model metadata.
n_features Number of features.
n_nodes Total node count.
max_depth The max_deep the model was trained with.
feature_names List of names, or None.
max_targets Payload cap per leaf; inherited by partial_fit.

IsodendronError

Raised for library-level failures — a corrupt blob, an invalid argument, an allocation failure. It carries a code attribute matching the C constants (ISODENDRON_OK, ISODENDRON_ERR_MEM, ISODENDRON_ERR_ARG).

Contract errors are raised as ordinary Python exceptions instead:

Situation Exception
predict / dump / save before fit RuntimeError
Wrong number of features in a sample ValueError
p_min with a zero numerator or denominator ValueError
save over an existing file without overwrite FileExistsError
load on a missing file FileNotFoundError
load on a foreign, truncated or version-mismatched file ValueError

dump() in detail

dump() returns one dictionary per leaf, with the path that reaches it and the full stored distribution:

{
    "path": [("Age", 0, "=="), ("Cholesterol", 3, "!=")],
    "N": 120,
    "K": 3,
    "mode": 1,
    "n_mode": 96,
    "P": 0.8,
    "distribucion": [(1, 96), (0, 18), (2, 6)],
    "total": 120,
}

distribucion holds at most max_targets (target, count) pairs, ordered by count descending and, on ties, by target ascending.


Persisting models

A model and its metadata are stored in a single .isod file:

"ISODFILE"    8 bytes    file magic
uint32                   format version — the same number as the blob's
uint32                   length of the JSON metadata
JSON                     max_targets, feature_names, library version
bytes                    the blob, up to the end of the file
model.save("heart.isod")                    # refuses to overwrite
loaded = Isodendron.load("heart.isod")

loaded.predict(X) == model.predict(X)       # True, byte for byte

Design notes:

  • One version number. The container and the blob share it, so any change in either layer invalidates the file rather than risking a misread. It is checked twice: the container's against the library, and the blob's against the container's.
  • The blob is the only source of truth. n_features, max_deep and p_min are read from the blob header. The JSON carries only what the blob does not: max_targets, feature_names and the library version at save time.
  • No pickle. Nothing in the file is executable; loading is a struct and json parse.
  • Atomic writes. The file is written to a sibling .tmp and then os.replaced, so an interrupted save never leaves a corrupt model.
  • max_targets is remembered, so partial_fit uses it without being told.

Incremental learning

partial_fit adds new samples to an existing tree without rebuilding it. The structure is fixed; only the leaf distributions change.

model = Isodendron().fit(train_data)
model = model.partial_fit(new_data)              # accumulate
model, info = model.partial_fit_diag(new_data)   # with diagnostics

Semantics, precisely:

  1. For every leaf the old distribution is decayed by subtracting min_count - 1 from every entry, which leaves the minimum at 1 and preserves the differences between classes exactly.
  2. The new samples that fall in that leaf are added on top.
  3. Leaves with no old distribution and no new samples keep their N. Nothing is ever emptied.

The decay is what keeps old evidence from dominating forever. It never changes the ordering inside a leaf, and it never makes two distinct counts equal.

partial_fit_diag returns counters plus up to capacidad samples of anomalous leaves, so a huge dataset does not need any per-leaf reservation:

{
    "puntos": 918,
    "hojas": 265,
    "decayadas": 123,
    "vaciadas": 0,          # leaves left without data — should be 0
    "resta_maxima": 36,
    "anomalas": 0,
    "detalle": [],
}

Important. partial_fit is not a way to avoid re-training on new data and then score it honestly. If you accumulate the test set and evaluate on the same rows, you are measuring memorisation. The mechanism is sound; the estimator is not.


C API

Everything is declared in include/isodendron.h and exported from the shared library. All arrays are int32_t, all logical sizes are uint32_t.

const char *isodendron_version(void);

int isodendron_train(const int32_t *X, const int32_t *y,
                     uint32_t n, uint32_t f,
                     uint32_t max_deep,
                     uint32_t p_min_num, uint32_t p_min_den,
                     uint8_t **out_blob, size_t *out_len);

int isodendron_train_mt(const int32_t *X, const int32_t *y,
                        uint32_t n, uint32_t f,
                        uint32_t max_deep,
                        uint32_t p_min_num, uint32_t p_min_den,
                        uint32_t n_threads,
                        uint8_t **out_blob, size_t *out_len);

int isodendron_train_mt_cap(const int32_t *X, const int32_t *y,
                            uint32_t n, uint32_t f,
                            uint32_t max_deep,
                            uint32_t p_min_num, uint32_t p_min_den,
                            uint32_t max_targets,
                            uint32_t n_threads,
                            uint8_t **out_blob, size_t *out_len);

int isodendron_predict(const uint8_t *blob, size_t blob_len,
                       const int32_t *X, uint32_t n, uint32_t f,
                       int32_t *out_pred);

X is row-major: sample i, feature j is X[i * f + j]. Every out_blob is heap allocated and belongs to the caller — free it with isodendron_free, never with free.

Return codes are ISODENDRON_OK (0), ISODENDRON_ERR_MEM (1) and ISODENDRON_ERR_ARG (2). On any non-zero return no output buffer is allocated.

The remaining entry points — predict_full, predict_leaf, partial_fit, partial_fit_diag and free — follow the same conventions; their exact prototypes are in the header.

predict_leaf reports the preorder index of the leaf node, which is the index used inside the blob. It is not a compact leaf number.


The blob format

The blob is a self-describing, little-endian structure. Version 3.

header   32 bytes
nodes    32 bytes each, in preorder
payload  (target int32, count int32) pairs

Header, all uint32:

Offset Field
0 magic 0x444F5349 ("ISOD")
4 version (3)
8 n_features
12 n_nodes
16 max_deep
20 p_min_num
24 p_min_den
28 payload_bytes

Total size is 32 + n_nodes * 32 + payload_bytes.

Node, all int32:

Offset Internal node Leaf
0 is_leaf = 0 is_leaf = 1
4 feature_idx payload_off
8 value payload_len
12 N N
16 K K
20 mode mode
24 n_mode n_mode
28 left_subtree_size 0

The split is feature == value on the left, != on the right. N is the number of samples in the subtree, K the number of distinct classes. The payload holds payload_len pairs of 8 bytes each.

Leaves always carry a payload — including the ones closed by depth, purity or lack of a usable split — so every leaf is readable by the same code path.


Concurrency

isodendron_train_mt and isodendron_train_mt_cap explore disjoint subtrees on separate threads using a worker pool. Training is deterministic across thread counts: the same input produces the same blob with one thread or with sixteen, byte for byte. The test suite asserts this explicitly.

n_threads = 1 takes the sequential path, which is not merely the parallel path with one worker: it uses a single arena and skips the fork logic.


Tests

# Python
python -m pytest

# C
cmake -S . -B build -DISODENDRON_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
ctest --test-dir build -C Release --output-on-failure

Current status: the C suite runs 48 cases / 363 checks, the Python suite 55 cases, both with zero failures.

The Python suite covers the public API, the error contract, the blob layout, pickling, and the .isod round trip including every documented failure mode.

The scripts under examples/ are experiments rather than tests. They illustrate the consequences of the isodieretic split on synthetic data, on MNIST pixels, on an (x, y, colour) cloud of the same digits, and on a tabular heart-disease dataset. Each prints an honest confusion matrix and, in the heart case, a save/load round trip and an incremental update.


What this library is not

It is worth being blunt about the boundaries, because they follow from the central design decision rather than from missing features.

It ignores the target when splitting. If a feature separates the classes but does so unevenly, the tree will not use it. Accuracy on ordinary datasets is therefore far from what a CART would give; the heart-disease example sits around 79%, where gradient-boosted trees reach 88%.

The leaf payload is capped. With more than max_targets classes in one leaf, the rarest ones are dropped from the distribution. The default of 10 fits MNIST exactly; raise it for wider problems.

Deep trees on high-dimensional data are not compact. Balanced splits waste capacity: the MNIST point-cloud experiment needed roughly 4000 nodes to reach 68% on three features. The tree is not the bottleneck there; the representation is.

There is no pruning, no feature importance and no missing-value handling. Categorical inputs must be encoded by the caller, thresholds included — see examples/data/metadata_heart_disease.txt for a worked example.

The mode tie-break favours the smaller label. It is deterministic and documented, but it is a bias, not a neutral choice.


Repository layout

src/isodendron/            Python package
  __init__.py
  _ffi.py                  ctypes declarations and wrappers
  _lib.py                   library loading via importlib.resources
  dataset.py                CSV and Python-sequence normalisation
  tree.py                   Isodendron model, persistence and partial_fit
  py.typed
csrc/                      C library
  include/isodendron.h
  src/                     sources
  tests/test_isodendron.c
tests/test_isodendron.py   Python test suite
examples/                  experiment scripts and small datasets
pyproject.toml             packaging metadata and scikit-build-core config
CMakeLists.txt             C build and CTest

License

MIT. See LICENSE.

This means you can use, modify, redistribute and sell this code, including in closed-source products, as long as the copyright notice and the permission notice travel with it. There is no warranty of any kind.

Metadata

Release files for isodendron 0.1.0

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

Source distribution (sdist)

Source distribution for isodendron 0.1.0
File Size Uploaded
isodendron-0.1.0.tar.gz 126.8 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for isodendron 0.1.0
File
isodendron-0.1.0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
isodendron-0.1.0-py3-none-win32.whl Python 3 none Windows x86-32 Details
isodendron-0.1.0-py3-none-musllinux_1_2_x86_64.whl Python 3 none Linux musl 1.2+ x86-64 Details
isodendron-0.1.0-py3-none-musllinux_1_2_i686.whl Python 3 none Linux musl 1.2+ x86-32 Details
isodendron-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
isodendron-0.1.0-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl Python 3 none Linux glibc 2.5+ x86-32, Linux glibc 2.17+ x86-32 Details
isodendron-0.1.0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
isodendron-0.1.0-py3-none-macosx_10_13_x86_64.whl Python 3 none macOS 10.13+ x86-64 Details

Total release size: 470.5 kB

Release files / isodendron-0.1.0.tar.gz

Download URL isodendron-0.1.0.tar.gz
Size 126.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d69025e8076a518f3b4d51436e524a7870161640c503a73705cba0718ed5ab75
BLAKE2b-256 checksum
How to use checksums
0b45f99c915d2729e1985506beea97f6cab379b91226b8fab7f13f6f778e29dd
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-win_amd64.whl

Download URL isodendron-0.1.0-py3-none-win_amd64.whl
Size 43.1 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
dbd40d920920ace03107288f5d077e8ddb55200c7e8c42f9aa6f28a49537ce01
BLAKE2b-256 checksum
How to use checksums
b8febe840c6270050027db6b56677561e441bb9ada620e24a6624811fa7e5e2c
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-win32.whl

Download URL isodendron-0.1.0-py3-none-win32.whl
Size 41.2 kB
Tags Python 3 Windows x86-32
SHA-256 checksum
How to use checksums
0465576d94635d49fc025da566de34064d0ef9a472516a43e1f9210b2dd7d8f9
BLAKE2b-256 checksum
How to use checksums
a4c6e4ef5160e25ff2b740ea221d378f850291b3fb3c5476811b8ea9f9bebb3e
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-musllinux_1_2_x86_64.whl

Download URL isodendron-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Size 44.1 kB
Tags Linux musl 1.2+ x86-64 Python 3
SHA-256 checksum
How to use checksums
49a3f7b29a7f4f49ec00a8b3b89ed24e7b7d2adfe1d19264b31aa0c58ffb7105
BLAKE2b-256 checksum
How to use checksums
74a186ff098dc04c70f05342c333a8c183496ffe3a46f0c967c6c134950936b2
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-musllinux_1_2_i686.whl

Download URL isodendron-0.1.0-py3-none-musllinux_1_2_i686.whl
Size 44.8 kB
Tags Linux musl 1.2+ x86-32 Python 3
SHA-256 checksum
How to use checksums
36278bdba20da00ebda7effc8542bd57721074d18df81a340387deeb243470ef
BLAKE2b-256 checksum
How to use checksums
64d4ae7aea08fb43473bdf70aeeaf383d4ad917b84d369557c6d0432a6ae327a
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL isodendron-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 43.8 kB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
0cfecf1720fcc09e65c66de4de90e32d785f1b089fe2a6be185683b35c7ad26c
BLAKE2b-256 checksum
How to use checksums
649720c4d6b8652a44abde3b9890a516e167e90d356089a75be83674074c5f44
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl

Download URL isodendron-0.1.0-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
Size 44.4 kB
Tags Linux glibc 2.17+ x86-32 Linux glibc 2.5+ x86-32 Python 3
SHA-256 checksum
How to use checksums
f23aaf59ebb6c6e0557f0744d75bd1ae186ae665de09dc60d6760bd848a751a6
BLAKE2b-256 checksum
How to use checksums
8b0e0f44e99b18f1c75151c4dca7563f05d13275f5d2a139f8260452f9f231e5
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-macosx_11_0_arm64.whl

Download URL isodendron-0.1.0-py3-none-macosx_11_0_arm64.whl
Size 40.9 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
21ca61239ea591faa4b9c716028882ef1f88cc4d315d374254900d340c582ce4
BLAKE2b-256 checksum
How to use checksums
833afce7ee01ee9bedcf6fa4eff9b6f1fdc610963008a3ade11570bd71ea50a8
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 Oct 19, 2026.

Transparency log

Release files / isodendron-0.1.0-py3-none-macosx_10_13_x86_64.whl

Download URL isodendron-0.1.0-py3-none-macosx_10_13_x86_64.whl
Size 41.4 kB
Tags Python 3 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
70143469dac56d50f0069ef75f8e509052dfc518b8f1d5a5f59e1f9418782214
BLAKE2b-256 checksum
How to use checksums
fab32e93b708acdd4602b65627e74343151cdf9b0ddcedb862d58c738c1fbf1f
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 Oct 19, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

9 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