Skip to main content

treekle🌳

PyPI version CI Python 3.12+ License: Apache-2.0 Discord

tree + pickle: pickle's semantics, written as a plain tree of dicts, lists, and scalars instead of opaque bytes.

Quick Start

# Mac:
#   # Required for quick install.
#   brew install uv

# Ubuntu/Debian:
#   # Required for quick install.
#   sudo apt-get install -y curl
#   curl -LsSf https://astral.sh/uv/install.sh | sh

uv add treekle

# Alternatively: python -m pip install treekle

to_plain turns a value into plain data any JSON, TOML, or YAML writer can store. from_plain turns it back into the type you ask for. Neither touches text, so the wire format is yours.

Example

from dataclasses import dataclass, field

import json
import math

from treekle import ReadError, from_plain, to_plain


def mse(a: float, b: float) -> float:
    return (a - b) ** 2


@dataclass
class Layer:
    width: int


@dataclass
class Model:
    encoder: Layer
    decoder: Layer
    shape: tuple[int, int] = (2, 3)
    clip: float = math.inf
    loss: object = mse
    tags: set[str] = field(default_factory=lambda: {"base"})


layer = Layer(width=64)
text = json.dumps(to_plain(Model(encoder=layer, decoder=layer)))
# {"py/object": "__main__.Model",
#  "clip": {"py/float": "inf"},
#  "decoder": {"py/object": "__main__.Layer", "width": 64},
#  "encoder": {"py/ref": ["decoder"]},
#  "loss": {"py/function": "__main__.mse"},
#  "shape": {"py/tuple": [2, 3]},
#  "tags": {"py/set": ["base"]}}

model = from_plain(json.loads(text), Model, allow_imports=True)
assert model.encoder is model.decoder  # one object, not two copies
assert model.shape == (2, 3)  # a tuple, not a list
assert model.clip == math.inf  # JSON has no inf
assert model.loss is mse  # the function itself

A lambda has no import path, so it is refused when written, not when read:

to_plain(lambda x: x)
# TypeError: Cannot serialize <function <lambda> ...>: import path
# '__main__.<lambda>' does not resolve to the same object.

Bad input reads as far as it can:

try:
    from_plain({"width": "wide"}, Layer)
except ReadError as error:
    error.partial  # Layer(width=Invalid(raw='wide', reason="cannot read 'wide' as int"))
    error.bad  # {('width',): Invalid(raw='wide', reason="cannot read 'wide' as int")}

Features

Tags for what JSON lacks

Anything a wire format cannot carry natively becomes a py/* tag, spelled as jsonpickle spells it where one exists:

Value Tag
class instance, class, function py/object, py/type, py/function
tuple, set, frozenset, mappingproxy py/tuple, py/set, py/frozenset, py/mappingproxy
non-finite float, complex, bytes py/float, py/complex, py/b64
Path, UUID, datetime py/path, py/uuid, py/datetime
anything else picklable py/reduce
a type you register with hooks= py/hook
a repeat of an earlier object py/ref
a non-string or tag-like dict key json://...

Shared objects and cycles

A repeat is written as py/ref, the path to its first occurrence, so model.encoder is model.decoder survives the round trip. Cycles terminate the same way. Older py/id back-references still read.

The type decides, unless a tag does

from_plain(data, T) reads data as T: a JSON array as a tuple[int, int], "red" as Color.RED, a dict as a dataclass. Where the data carries a tag, the tag decides, so a field typed object or a Protocol reads back as whatever class the payload names. Importing a class by name is gated behind allow_imports=True.

Best-effort decoding

A bad part does not stop the read. ReadError.partial is the value with an Invalid in place of each bad part; ReadError.bad maps each bad part's path to its Invalid.

Wire-invariant

The core never parses or prints text. treekle.wire.json_loads is the one reader, typed so from_plain(json_loads(text), T) needs no cast.

Why not another serializer

tl;dr: No library is perfect, including this one. Fast libraries decode only classes known ahead of time; treekle trades speed for pickle semantics in plain text.

C/Rust text class from data callables shared refs best-effort
msgspec ✅ ✅ ❌ ❌ ❌ ❌
pydantic ✅ ✅ ❌ ❌ ❌ ❌
cattrs ❌ ✅ ❌ ❌ ❌ ❌
mashumaro ❌ ✅ ❌ ❌ ❌ ❌
pickle ✅ ❌ ✅ ✅ ✅ ❌
jsonpickle ❌ ✅ ✅ ✅ ✅ ❌
treekle ❌ ✅ ✅ ✅ ✅ ✅
  • C/Rust: the core is a compiled extension, not pure Python.
  • text: encodes to human-readable text (JSON), not opaque bytes.
  • class from data: decode builds a class the payload names by import path, beyond the declared type and its subclasses.
  • callables: a function round-trips by import path, with no special field type.
  • shared refs: an object referenced twice decodes as one object; cycles too.
  • best-effort: bad input yields the partial value and every bad part's location.

Details: each mark is what a probe of msgspec 0.22, pydantic 2.13, cattrs 26.2, mashumaro 3.23, and jsonpickle 4.1 returned:

text class from data callables shared refs best-effort
msgspec JSON dict raises split first error
pydantic JSON dict raises split all, no value
cattrs JSON raises raises split all, no value
mashumaro JSON raises raises split first error
pickle binary the class same function kept first error
jsonpickle JSON the class same function kept no error, dict
treekle JSON the class same function kept partial value, all
  • text: what one dataclass encodes to.
  • class from data: an object field given a payload naming another class.
  • callables: a field holding a module-level function.
  • shared refs: one object in two fields; "split" decodes two separate copies.
  • best-effort: two bad fields.

One cycle shape fails to read: one through a dataclass decoded as its declared type.

Metadata

Release files for treekle 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 treekle 0.1.0
File Size Uploaded
treekle-0.1.0.tar.gz 141.6 kB Details

Built distribution (wheel)

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

Total release size: 179.8 kB

Release files / treekle-0.1.0.tar.gz

Download URL treekle-0.1.0.tar.gz
Size 141.6 kB
Tags Source
SHA-256 checksum
How to use checksums
696deffc81973d577731160161bc853fa884a3821d26b94a06ef40cd0f0ea384
BLAKE2b-256 checksum
How to use checksums
6380e0c724e2197556f882f28c1eb3323091f7cb23e69887e2228261a2e85256
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 11, 2026.

Transparency log

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

Download URL treekle-0.1.0-py3-none-any.whl
Size 38.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a025df09ca7c3f0ed05517e558c1dc23acd051ca5c7f8ecbc831c0eb9bc95e75
BLAKE2b-256 checksum
How to use checksums
3b0fb193baf7470e5e9d1b4574f77a05b8d0a88547141a3fb37287b55bc80543
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 11, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.1

2 release files

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