Versioned persistence for Python dataclasses with hash validation, declarative migrations, and pluggable backends
Project description
versionable
Save and load Python dataclasses to files — with schema versioning, type converters, and pluggable storage backends.
Why versionable ?
Your data lives in files. Your code keeps changing. Without protection, old files silently load with missing fields, wrong types, or stale values — your data schemes against you.
versionable stops the scheming. Define your data as a Python dataclass, and get save() and load() functions
that produce human-readable files (YAML, JSON, TOML) or binary-efficient ones (HDF5). Every file is stamped with a
schema fingerprint and version number, so a file written by v1 of your code loads cleanly into v5 — automatically
migrated, never silently broken.
What to use? .pickle is unsafe. Pure .json
and .yaml carry no schema and manual wrappers break the moment your schema changes. .csv and .parquet are great
for tables but poor at handling structured metadata. .npz files aren't even guaranteed to be compatible across numpy
versions. .proto (Protocol Buffers) are schema-aware but require a build step and offer no migrations. And if you've
resorted to folders with sidecar files (data.npy + params.json + metadata.txt), you already know how easily those
drift out of sync.
What you get with versionable:
- Zero boilerplate — no schema files, no code generation, no build step. Just inherit from
Versionable - Simple versioning with declarative migrations — rename, add, remove, or transform fields across versions
- Rich type support — datetime, Path, UUID, Enum, numpy arrays, and more — easy to extend with your own
- Nested objects with independent versioning — compose complex dataclasses from smaller
Versionablepieces - Native numpy array support — with lazy HDF5 loading for large datasets
- JSON, YAML, TOML, HDF5 — or bring your own backend
- Import-time safety — schema hash mismatches are caught when your module loads, not in production
- Modern, type-safe Python — fully typed and compatible with mypy, pyright, and other static analyzers
How does it compare?
| Versionable Features | pickle | dc libs¹ | protobuf | raw JSON | sidecars |
|---|---|---|---|---|---|
| ✅ Zero boilerplate | ✅ | ✅ | ❌ | ❌ | ❌ |
| ✅ Versioning with declarative migrations | ❌ | ❌ | ❌ | ❌ | ❌ |
| ✅ Rich type support | ✅ | ✅ | 🔧 | ❌ | ❌ |
| ✅ Nested objects, versioned independently | 🟠 | 🟠 | 🟠 | 🟠 | ❌ |
| ✅ Native numpy / lazy HDF5 | 🟠 | ❌ | ❌ | ❌ | 🟠 |
| ✅ Custom Backends | ❌ | 🟠 | 🟠 | 🟠 | ❌ |
| ✅ Import-time validation | ❌ | ❌ | 🔧 | ❌ | ❌ |
| ✅ Modern, type-safe Python | ❌ | 🟠 | ✅ | ❌ | ❌ |
¹ pydantic, dataclasses-json, etc.
- 🔧 = requires manual effort / build step
- 🟠 = partial
Installation
pip install versionable
# With HDF5 backend support (h5py + hdf5plugin)
pip install "versionable[hdf5]"
Or install from source:
pip install git+https://github.com/hendrickmelo/versionable.git
Quick Start
You save a config file today:
from dataclasses import dataclass
import versionable
from versionable import Versionable
@dataclass
class SensorConfig(Versionable, version=1, hash="82bc30"):
name: str
value: float
config = SensorConfig(name="experiment-A", value=9.81)
versionable.save(config, "config.yaml")
A few weeks later you rename value to magnitude. Without versionable, old files silently load with missing data.
With it, you bump the version and declare a migration — old files upgrade automatically:
from versionable import Versionable, Migration
@dataclass
class SensorConfig(Versionable, version=2, hash="064498"):
name: str
magnitude: float # renamed from "value"
class Migrate:
v1 = Migration().rename("value", "magnitude")
# Old file loads cleanly into the new schema
loaded = versionable.load(SensorConfig, "config.yaml")
assert loaded.magnitude == 9.81
The schema hash is validated at import time — if your fields change but the hash doesn't, you get an error immediately, not a silent bug in production.
Learn More
Want to see how old files get upgraded automatically when your schema changes?
- See migrations in action
- Explore the available backends
For AI Agents
If you're an AI agent working with versionable, see AGENT.md for a condensed API reference.
Complete Documentation
For custom type converters, HDF5 support, and more, see the full documentation.
Acknowledgements
The idea behind versionable started over 15 years ago in C++, where I first learned the approach from
Steve Araiza. Over the years the idea of a Serializable / Versionable class evolved from
using CArchive to make use of C++11, variadic macros, and other fun modern C++ features. Some version of this pattern
has been a part of every project I've worked on since those days.
This is the Python version of the idea. It is built using modern, type-safe Python with great fresh ideas from Emma Powers.
A big thank you to both of them! 🥓🥞🍳
License
MIT - Copyright ©️ 2026 Hendrick Melo
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file versionable-0.0.1rc1.tar.gz.
File metadata
- Download URL: versionable-0.0.1rc1.tar.gz
- Upload date:
- Size: 281.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
712792ec10edd64d2463ac6dfcc42d3ba16813d797d8774dbaff093c88290be5
|
|
| MD5 |
672601df01b53624a932470ae3db5879
|
|
| BLAKE2b-256 |
5cb5f374e9f4c6237821adc3cced7d99fac78c694c86bf277a64475d2de8e46c
|
Provenance
The following attestation bundles were made for versionable-0.0.1rc1.tar.gz:
Publisher:
publish.yml on hendrickmelo/versionable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
versionable-0.0.1rc1.tar.gz -
Subject digest:
712792ec10edd64d2463ac6dfcc42d3ba16813d797d8774dbaff093c88290be5 - Sigstore transparency entry: 1244274261
- Sigstore integration time:
-
Permalink:
hendrickmelo/versionable@13aafb16fcad574b1f73abe60d8a14ce12926dfb -
Branch / Tag:
refs/tags/v0.0.1rc1 - Owner: https://github.com/hendrickmelo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@13aafb16fcad574b1f73abe60d8a14ce12926dfb -
Trigger Event:
release
-
Statement type:
File details
Details for the file versionable-0.0.1rc1-py3-none-any.whl.
File metadata
- Download URL: versionable-0.0.1rc1-py3-none-any.whl
- Upload date:
- Size: 34.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94647045e8682d56c6a492d4ed41858c885a93848e0ecfa9711dc8e7f849707d
|
|
| MD5 |
2e5f079cb4d66a9dd79bafbeef2daf23
|
|
| BLAKE2b-256 |
0072f719056b9b606073905000f80e05afa6e1aa9bd9429de65cc03c7206841a
|
Provenance
The following attestation bundles were made for versionable-0.0.1rc1-py3-none-any.whl:
Publisher:
publish.yml on hendrickmelo/versionable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
versionable-0.0.1rc1-py3-none-any.whl -
Subject digest:
94647045e8682d56c6a492d4ed41858c885a93848e0ecfa9711dc8e7f849707d - Sigstore transparency entry: 1244274276
- Sigstore integration time:
-
Permalink:
hendrickmelo/versionable@13aafb16fcad574b1f73abe60d8a14ce12926dfb -
Branch / Tag:
refs/tags/v0.0.1rc1 - Owner: https://github.com/hendrickmelo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@13aafb16fcad574b1f73abe60d8a14ce12926dfb -
Trigger Event:
release
-
Statement type: