Skip to main content

ordered-btree

A pure-Python, typed, generic ordered collection of unique keys, with a B-tree storage engine. Python 3.12+, including Python 3.14. No runtime dependencies. Licensed under MIT.

The distribution is ordered-btree; the import is ordered_btree. Version 0.1.0 is prepared as the initial public release. This is a pre-1.0 API: pin a tested version and review the changelog before upgrading. Check GitHub releases for completed releases and their PyPI links; a source checkout is not proof of publication. See releasing for the TestPyPI verification and production-approval gates.

Highlights

  • Private mutable nodes; read-only degree; detached frozen diagnostic snapshots.
  • Only < is required. Unhashable keys and keys without usable == work.
  • A single comparison-only search precedes comparator-free updates. Raising comparisons leave contents, structure, size, and iterator validity unchanged.
  • Iterative traversal and bisected range seeking, with fail-fast iterators.
  • One-pass structural validation, including cycles, shared nodes, bounds and size.
  • Tests, fault injection, exhaustive/randomized stress, deterministic benchmarks, wheel/sdist packaging, py.typed, and CI configuration.

Validation: see the validation record for exact executed runtimes, commands, results and limitations. Configured CI is not evidence that a remote run has completed. See contributing for development and security for the security policy.

Installation

After the version is published and verified on PyPI, applications can install it with:

python -m pip install ordered-btree==0.1.0

Until then, install from source or the verified local wheel below. For development, use uv 0.12.15 or newer. The following commands work from the project directory on Windows, Linux and macOS; no shell activation is needed:

uv sync --locked                   # Development environment, Python 3.14 by default
uv run --locked python examples/basic.py

To install just the library into a separate application environment:

uv venv --python 3.14
uv pip install /path/to/ordered-btree
# Or install a locally built wheel without runtime dependencies:
uv pip install --no-deps /path/to/ordered_btree-0.1.0-py3-none-any.whl

Ordinary python -m pip install /path/to/ordered-btree also works: uv is a contributor tool, not a requirement for applications using the library. Python 3.12 and 3.13 remain supported. The project's Python pin selects a development default; it does not raise the minimum version.

Basic usage

from ordered_btree import BTree

numbers = BTree[int](t=32, keys=[10, 20, 5, 6, 12, 30, 7, 17])

assert list(numbers) == [5, 6, 7, 10, 12, 17, 20, 30]
assert list(numbers.range(6, 18)) == [6, 7, 10, 12, 17]  # [start, stop)
assert list(numbers.range(None, 7)) == [5, 6]
assert list(numbers.range(20, None)) == [20, 30]

assert numbers.search(6) == 6       # Stored representative, not a node.
assert numbers.search(999) is None
assert 12 in numbers
assert numbers.insert(6) is False  # Equivalent key already present.
assert numbers.delete(12) is True
assert numbers.delete(999) is False

assert numbers.min() == 5
assert numbers.max() == 30
assert numbers.pop_min() == 5
assert numbers.pop_max() == 30
numbers.validate()

t is the minimum degree, not the maximum number of keys. A node holds at most 2*t-1 keys. The default remains t=2 for compatibility. t=32 in these examples is an explicit starting point to benchmark, not a universally optimal default. Floats (including 2.0), booleans, strings, NaN, and infinity are rejected as degrees. Invalid integer degrees below two raise ValueError.

Custom immutable records

from __future__ import annotations
from dataclasses import dataclass
from ordered_btree import BTree

@dataclass(frozen=True, slots=True)
class Job:
    priority: int
    job_id: int
    description: str = ""

    def __lt__(self, other: Job) -> bool:
        return (self.priority, self.job_id) < (other.priority, other.job_id)

first = Job(1, 42, "Original payload")
jobs = BTree[Job](keys=[first, Job(3, 12, "Later")])
probe = Job(1, 42, "Different payload")

assert probe != first                 # Dataclass equality sees the payload.
assert jobs.insert(probe) is False    # BTree ordering does not.
assert jobs.search(probe) is first     # First equivalent representative wins.
assert jobs.pop_min() is first

Ordering must be a stable strict weak ordering across all stored keys and probes. Keys are equivalent when neither is less than the other. No ==, <=, >, >=, or hashing is used internally. None is reserved for missing results and open range bounds and cannot be stored. NaN keys, partial orders, and inconsistent comparators are unsupported; arbitrary comparator laws are not validated automatically. In particular, this implementation does not promise to recognize and reject every invalid key before insertion into an empty tree.

Frozen dataclasses help, but Python does not enforce Rust-style ownership. Nested values may remain mutable, and the tree retains the exact objects passed in. Never change ordering-relevant state while a key is stored. Use an immutable replacement and an explicit delete/insert operation instead. Mutable payloads are acceptable only when they cannot affect ordering. Range-bound objects must also remain stable until iteration ends.

Mutation and iteration

from ordered_btree import BTree, ConcurrentModificationError

tree = BTree[int](keys=[1, 2, 3])
iterator = iter(tree)           # Version captured immediately.
assert next(iterator) == 1

tree.insert(4)
try:
    next(iterator)
except ConcurrentModificationError:
    print("Create a new iterator, or iterate over a snapshot.")

Successful insertion, deletion, clearing, and extreme removal invalidate active iterators. Duplicate insertion, missing-key deletion, and clearing an empty tree are genuine no-ops and do not invalidate them. Deleting and reinserting the same key still invalidates an iterator even if the size is unchanged.

Mutation before the first next() is detected. Once an iterator has raised StopIteration, it stays exhausted. After a modification error it is also exhausted. Iterator creation/lower-bound comparisons happen eagerly; comparisons against the upper bound happen as results are consumed. An upper-bound comparison exception leaves its candidate unconsumed, so it can be retried after the comparator is repaired. A StopIteration raised by that comparison is wrapped in RuntimeError with the original exception as its cause: comparator failure must not look like normal iterator exhaustion. Other comparison errors propagate.

stable_values = tree.snapshot()  # tuple of keys, detached from future updates
shape = tree.inspect()           # frozen NodeSnapshot; tuples of child snapshots
clone = tree.copy()              # independent mutable nodes, same key objects

for key in tree.snapshot():
    tree.delete(key)             # Safe: iterating over the detached tuple.

Snapshots are structurally immutable, not deep copies. search, min, max, iteration, snapshots, and copy all share key references. There is no public live root, and there is no public mutable node class. Underscore attributes are implementation details, not a security boundary against deliberate tampering.

API at a glance

API Result / behavior
BTree[T](t=2, keys=None) Incrementally consumes keys; keeps first equivalent object
insert(key) True if inserted, otherwise False
delete(key) True if removed, otherwise False
search(key) Stored equivalent key or None; never a node
key in tree, len(tree), bool(tree) Membership by ordering, size, non-emptiness
min(), max() Stored extreme or None
pop_min(), pop_max() Remove/return extreme; KeyError when empty
iter(tree) Ascending, fail-fast iterator
range(start=None, stop=None) Half-open [start, stop); no bounds means full scan
snapshot() Detached sorted tuple; shallow
inspect() Detached frozen recursive NodeSnapshot; shallow keys
copy(), copy.copy(tree) New mutable nodes, shared keys; no comparisons
clear() Empty the tree; returns None
height() Levels, zero when empty
validate() None if valid; ValueError for structural corruption
format_tree(), display(file=...) Diagnostic text / printing
t, min_keys, max_keys Read-only properties

This is not a collections.abc.MutableSet subclass: unordered set equality and hash-based equivalence would suggest semantics this library does not implement. There are no set operators, map values, multiset counts, rank/select operations, key-projection callback, persistence layer, or bulk-transaction API.

Exception and concurrency contracts

For ordinary exceptions raised by comparisons, single-key insert and delete have a strong no-change guarantee: all comparisons finish before mutation. No-op updates are equally non-mutating. Replacement extraction, borrowing, merging, and root contraction do not compare keys. Invalid-type comparison errors propagate.

This is not a transaction against allocation failure, KeyboardInterrupt, process termination, or malicious callbacks changing private state. Commit-time allocation failure/interruption can leave the tree unusable; rebuild it from a trusted external source. Construction is incremental, not a bulk transaction. validate() detects structure errors but cannot prove comparator laws; exceptions from its comparisons propagate rather than being relabeled as corruption.

Public content operations reject same-tree re-entrancy with ReentrantOperationError, including callbacks trying to read the tree during an active comparison. Constant-time metadata (len, degree and occupancy limits) can be read in callbacks. Callbacks may operate on a different tree.

There is no thread-safety guarantee, with or without the GIL. The re-entrancy flag and iterator version are not locks. All callers sharing a mutable instance must use an external lock, including around reads and the entire consumption of an iterator. See the synchronization example.

Complexity and performance

Let h be height, t minimum degree, n stored keys, and k returned keys. Comparison costs are assumed constant; expensive comparators add their own cost.

Operation Cost
Search / membership O(h log t) comparisons; O(1) auxiliary storage
Insert / delete O(h log t) comparisons + O(t h) worst-case list movement; O(h) retained path
Range O(h log t + k) total traversal/comparison work; O(h) cursor storage
Full iteration O(n) total; O(h) cursor storage
Min / max / height O(h)
Pop min / max No comparisons; O(t h) worst-case list movement
Length O(1)
Snapshot / inspect / copy O(n) time and output storage
Validate O(n) time; O(number of nodes) identity tracking
Repr Visits at most nine keys: O(h + 9), plus eight key representations

Splits also use O(t) temporary list storage. The range bound includes amortized successor traversal, not a promise that every individual next() is O(1). Clearing may perform O(n) reference releases, subject to outstanding references and garbage collection. Keys and their callbacks may have additional costs.

Faster asymptotics do not guarantee faster wall-clock performance for every workload. Fail-fast checks and Python iterator dispatch have a cost, particularly for full scans with large nodes. Performance measurements record deterministic inputs, actual runtimes and limitations. Degree should be selected using your own comparator, key sizes, and operation mix.

Development and tests

uv sync --locked
uv run --locked pytest -q -m "not stress"
uv run --locked pytest -q -m stress
uv run --locked python tools/stress.py
uv run --locked python benchmarks/benchmark.py
uv run --locked coverage run -m pytest -q -m "not stress"
uv run --locked coverage report --fail-under=99
uv run --locked mypy
uv run --locked ruff check src examples tests tools benchmarks
uv run --locked ruff format --check src examples tests tools benchmarks
uv run --locked python examples/basic.py
uv run --locked python examples/records.py
uv run --locked python examples/synchronized.py

tools/stress.py --quick is a shorter deterministic run. Hypothesis is installed by the development dependency group and is required for the property tests. Verification tools reject optimized Python (-O / PYTHONOPTIMIZE), use fresh report destinations and record failures rather than reusing stale success data. Raw reports and local agent files are intentionally not committed.

Build into a new directory and independently exercise the artifacts:

uv build --no-create-gitignore --out-dir dist/candidate
uv run --locked twine check --strict dist/candidate/*
uv run --locked python tools/verify_dist.py dist/candidate

The artifact verifier checks metadata, archive contents, py.typed and hashes, installs the wheel in an external temporary uv environment, and independently rebuilds and exercises a wheel from the extracted sdist. Publication is a separate, intentional step; follow the release checklist.

Project map

src/ordered_btree/    Runtime implementation, public exports, py.typed
examples/            Executable basics, immutable records, external locking
tests/               API, mutation, exception safety, independent model checks,
                     property tests, and verification-tool regressions
tools/               Stress, isolated artifact checks and publication preflight
benchmarks/          Deterministic workload measurements with raw samples
docs/                Design, compatibility, validation, performance and releases
.github/             CI, dependency updates and contribution templates

Scope and release status

The library deliberately uses private mutable nodes, shallow immutable snapshots, first-wins ordering equivalence, fail-fast iteration, explicit external locking, None reserved, and Python 3.12+ compatibility. See design and compatibility before depending on pre-1.0 API stability.

The project is MIT licensed. Publication uses the verified wheel/sdist pair from CI, first on TestPyPI and then on PyPI after the owner's production approval. Only completed remote runs and index checks establish publication; local checks do not claim that those steps have happened.

Metadata

Release files for ordered-btree 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 ordered-btree 0.1.0
File Size Uploaded
ordered_btree-0.1.0.tar.gz 139.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ordered-btree 0.1.0
File Interpreter ABI Platform
ordered_btree-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 154.5 kB

Release files / ordered_btree-0.1.0.tar.gz

Download URL ordered_btree-0.1.0.tar.gz
Size 139.3 kB
Tags Source
SHA-256 checksum
How to use checksums
6dd2dcf5068e92c590a096abf288809e99b338cf3d806b2e1d6704e972481caf
BLAKE2b-256 checksum
How to use checksums
d61c05ee6274c752e9ae1a92c393f418a8f64da8ae339ff88a078a241f40ff6c
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 24, 2026.

Transparency log

Release files / ordered_btree-0.1.0-py3-none-any.whl

Download URL ordered_btree-0.1.0-py3-none-any.whl
Size 15.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2f3532455b266cd66f06893da5a26dd3a76f2945be43ed5b8ebba29d607aff1a
BLAKE2b-256 checksum
How to use checksums
ffb34f47d8837df3197630f34e9f9b6be252361dde19c9fe3417014df96329f7
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 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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