Installation & Quickstart
Installation
Install pysafe-pickle from PyPI using your favorite package manager:
pip install pysafe-pickleuv add pysafe-picklepoetry add pysafe-picklepysafe-pickle has no runtime dependencies and requires Python 3.10 or newer (tested on Python 3.10 through 3.15). A single abi3 wheel covers all of these versions; free-threaded builds are not supported.
NumPy / PyTorch not supported
There are no numpy, torch, or all extras, and NumPy arrays or PyTorch tensors cannot be serialized (they raise UnsafeTypeError: Unsupported type). Convert them to lists or bytes first.
Quickstart
The core API mirrors the shape of Python's standard pickle module (dumps, loads, dump, load, Pickler, Unpickler). Only pickle protocol 5 is supported, and only the types listed in What is pysafe-pickle? can be serialized.
1. Basic Serialization (dumps and loads)
import datetime as dt
import uuid
from decimal import Decimal
import pysafe_pickle as psp
data = {
"id": uuid.UUID("12345678-1234-5678-1234-567812345678"),
"created": dt.datetime(2026, 10, 28, 9, 30),
"total": Decimal("19.99"),
"values": [1, 2, 3, 4.5],
"metadata": (None, "v1.4.0", b"\x00\x01\x02"),
}
binary_blob = psp.dumps(data)
restored = psp.loads(binary_blob)
print(restored == data) # True
print(restored["created"]) # 2026-10-28 09:30:00Besides bytes, loads also accepts bytearray and memoryview inputs.
2. File I/O (dump and load)
import pysafe_pickle as psp
with open("checkpoint.psp", "wb") as f:
psp.dump({"epoch": 41, "loss": 0.0131}, f)
psp.dump({"epoch": 42, "loss": 0.0125}, f)
with open("checkpoint.psp", "rb") as f:
print(psp.load(f)) # {'epoch': 41, 'loss': 0.0131}
print(psp.load(f)) # {'epoch': 42, 'loss': 0.0125}Each load(f) reads one record and leaves the file positioned after it. For long streams of records, see Streaming.
3. Your Own Classes
Pass your dataclasses (and enums and namedtuples) with classes= to get real instances back:
import enum
from dataclasses import dataclass
import pysafe_pickle as psp
class Role(enum.Enum):
ADMIN = "admin"
USER = "user"
@dataclass
class User:
name: str
role: Role = Role.USER
blob = psp.dumps([User("alice", Role.ADMIN), User("bob")])
users = psp.loads(blob, classes=[User, Role])
print(users) # [User(name='alice', role=<Role.ADMIN: 'admin'>), User(name='bob', role=<Role.USER: 'user'>)]
print(isinstance(users[0], User)) # TrueWithout classes=, dataclasses come back as instances of a generated class with the same name and fields (isinstance is False), and enum members cannot be loaded at all (UnsafeTypeError). __init__ and __post_init__ never run on load.
4. Handling Cyclic Graphs
pysafe-pickle tracks object identities, so cycles and shared references survive a round trip:
import pysafe_pickle as psp
node_a = {"name": "Node A"}
node_b = {"name": "Node B"}
node_a["neighbor"] = node_b
node_b["neighbor"] = node_a
decoded = psp.loads(psp.dumps(node_a))
print(decoded["neighbor"]["neighbor"] is decoded) # True5. Signing Payloads
import pysafe_pickle as psp
blob = psp.dumps({"user": "admin"}, hmac_key=b"secret")
print(psp.loads(blob, hmac_key=b"secret")) # {'user': 'admin'}A tampered payload or a wrong key raises HMACError. See Security & Allowlist.
What's New in 1.4
Highlights from CHANGELOG.md:
- HMAC-SHA256 signing with
hmac_key=on every dump and load function. classes=: real dataclass, enum and namedtuple instances on load, with defaults for missing fields.- Automatic migrations when you pass
classes=and the stored schema version differs. - New types:
datetime,date,time,timedelta,timezone,ZoneInfo,Decimal,UUID,Fraction,range,slice,Ellipsis,NotImplemented,pathlibpaths,OrderedDict,defaultdict(builtin factories),Counter,deque,Enummembers and namedtuples. - Multi-record streams with
Pickler/Unpicklerand repeatedload(f). - Safe reader for existing pickle files:
load_pickle,loads_pickle,convert_pickle. - Real exceptions:
UnsafeTypeError(aTypeError),DecodeError,HMACError,SchemaVersionError(allValueErrors).
Behavior changes from 1.3
Exceptions are now the package's own classes, but they subclass the built-in ones raised before, so except TypeError / except ValueError still work. IntEnum members, OrderedDict, defaultdict(list) and namedtuples used to raise TypeError in dumps and now serialize.
Next Steps
- Evolve data models over time with Schema Evolution.
- Write and read many records with Streaming & PickleBuffer.
- Lock down untrusted input with Security & Allowlist.
- Read old
.pklfiles safely with Reading Existing Pickle Files.