release

BXM Canon 0.3.0 Adds Python, With Ints, Floats and Bytes Kept Apart and the Same Hash as Every Other Build

BXM Canon 0.3.0 adds a Python package. It’s the same Rust core again, this time called through PyO3, with wheels for Linux, macOS and Windows and any Python from 3.9 up. A value hashed in Python gives the hash the command line, the C library and the JavaScript package give, because all of them run the same code.

Python turned out to be the most natural fit so far. JavaScript has one number type, so a JavaScript caller has to think about whether 1 should be an integer or a float. Python already keeps int and float apart, and it has a real bytes type, which is exactly what Canon distinguishes. So the Python package can take a value straight from your program, no text step in between.

import bxm_canon as canon

canon.hash_value({"b": 1, "a": 2}) == canon.hash_value({"a": 2, "b": 1})  # True
data = canon.encode({"n": 3, "x": 1.0, "raw": b"\xca\xfe"})
canon.decode(data)  # {'n': 3, 'raw': b'\xca\xfe', 'x': 1.0}

The same value hashed with the bxm-canon command and from Python gives the same SHA-256, and both pass the 60 conformance vectors

3 and 3.0 encode differently, True doesn’t turn into 1, and dicts come back with their keys in byte order, whatever order they went in. Anything Canon can’t represent says so: an integer past 64 bits raises OverflowError, NaN and negative zero raise CanonError with their codes, and a set or a custom object raises TypeError.

Install

pip install bxm-canon --find-links https://github.com/softdevstudio/bxm/releases/expanded_assets/v0.3.0

pip picks the wheel for your system from the release. The full API is on the Reference page.

What the review caught

Each package gets an independent review before it ships, and this one earned its keep.

The first CI run failed on Windows only. The package was fine. The tests read the spec’s vector files without naming an encoding, and Python on Windows falls back to the local code page, so é arrived as two wrong characters. A one-line fix, and a reminder of why Canon pins every byte instead of trusting defaults.

The review also found that a memoryview over an array of integers would have been encoded as its raw memory, which is little-endian on some machines and big-endian on others. That would have broken the one promise Canon makes, so the package now accepts only views of bytes. And CanonError couldn’t be pickled, so an error raised in a worker process would have crashed the pool instead of reaching the caller. It’s now a plain Python class that pickles with its code and offset.

Tested

On every change, the package is built and tested on Linux, macOS and Windows with Python 3.9 and 3.13. The tests run the spec’s 60 conformance vectors and round-trip Python values through bytes, including the edges: the largest and smallest 64-bit integers, nesting at exactly the 512-level limit, lone surrogates and non-string keys. Every release wheel is installed and run before it’s published.

With this release, Canon is in the three places it was meant to be: the command line and C, JavaScript, and Python. Memo needs nothing new for sharing: CI runners reuse results by keeping the cache folder between runs, and several machines can point at one shared folder.