isodendron
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
- The isodieretic split
- Installation
- Building from source
- Quick start (Python)
- Python API
- Persisting models
- Incremental learning
- C API
- The blob format
- Concurrency
- Tests
- What this library is not
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:
- The target is never read during training. Two datasets with identical features and completely different labels produce exactly the same tree.
- Training is deterministic. There is no random seed, no shuffling, no tie-breaking by trial. The same input gives the same bytes, always.
- 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:
- the
ISODENDRON_LIBenvironment variable, if it points at an existing file; - the
isodendronpackage 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_deepandp_minare read from the blob header. The JSON carries only what the blob does not:max_targets,feature_namesand the library version at save time. - No pickle. Nothing in the file is executable; loading is a
structandjsonparse. - Atomic writes. The file is written to a sibling
.tmpand thenos.replaced, so an interrupted save never leaves a corrupt model. max_targetsis remembered, sopartial_fituses 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:
- For every leaf the old distribution is decayed by subtracting
min_count - 1from every entry, which leaves the minimum at 1 and preserves the differences between classes exactly. - The new samples that fall in that leaf are added on top.
- 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)
| File | Size | Uploaded | |
|---|---|---|---|
| isodendron-0.1.0.tar.gz | 126.8 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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