Rust acceleration core for marshmallow serialization, installed as a separate, opt-in package.
Project description
marshmallow_core
A Rust acceleration core for marshmallow,
shipped as a separate, opt-in package. Install it next to stock marshmallow
and activate it explicitly — it replaces marshmallow's per-object
_serialize / _deserialize loops with a PyO3 extension while producing
identical results.
pip install marshmallow marshmallow_core
import marshmallow as ma
import marshmallow_core
marshmallow_core.install() # patch marshmallow.Schema in this process
class Person(ma.Schema):
name = ma.fields.String()
age = ma.fields.Integer()
Person().load({"name": "ann", "age": "30"}) # accelerated
Person().dump({"name": "ann", "age": 30}) # accelerated
marshmallow_core.uninstall() # restore the stock pure-Python methods
How it works
install()monkey-patchesSchema._serializeandSchema._do_load. Each bound schema is compiled once (cached on the instance) into a recursive payload describing every field as either native (run entirely in Rust) or a callback (defers to the PythonFieldmethod). Anything not modelled natively stays a callback, so output is behaviour-identical. The compiled plan is cached per instance, so reuse schema instances rather than constructing a newSchema()per call — otherwise every call recompiles (stock marshmallow rewards instance reuse too).- Both cores handle the happy path and raise an internal
AccelFallbackon any error/edge case, so marshmallow re-runs the unchanged pure-Python path and every error message and value matches exactly. (Dump has no side effects — it builds a fresh output — so it can discard a partial result and re-run safely, just like load.) dumpsis fused: it writes JSON bytes directly in Rust, skipping the intermediate Python dict and thejson.dumpspass, byte-for-byte identical tojson.dumps(schema.dump(obj)). It activates for hook-free schemas using the stdlibjsonrender module with no extrajsonkwargs, and falls back todump+json.dumpsfor anything it can't reproduce exactly. (loadsis already accelerated through the patched load path; a Rust JSON parser was prototyped but did not beat CPython's Cjson.loads, so it was not shipped.)- Acceleration is strictly a speedup. Set
MARSHMALLOW_NO_ACCEL=1(or hit a protocol-version mismatch between the Python and Rust halves) and the core becomes a no-op even afterinstall().
Scope / limitations
install() accelerates dump for all compilable schemas, and load for most
schemas — including those with pre_load / post_load / validates /
validates_schema hooks: the core runs the per-field deserialize step while
those hooks run in Python around it (mirroring marshmallow's own _do_load
split). Recognized field validators (Range / Length / OneOf / Equal /
NoneOf / ContainsOnly) run natively; any other validator, or field-level
pre_load / post_load, keeps that field on the callback path. unknown=INCLUDE, collection/dotted partial, and dotted
attribute writes are all accelerated. Natively modelled fields include the
scalars plus Decimal, Dict (incl. typed keys/values), Tuple, Pluck,
Constant, TimeDelta, Boolean, Integer(strict=True), and
NaiveDateTime / AwareDateTime. The dump core has an AccelFallback (it
discards a partial result and re-runs pure Python on any shape it can't
reproduce), so it accelerates the composite fields too. Custom dict_class /
get_attribute, self-referential schemas, custom strptime temporal formats, and
callable defaults always fall back to pure Python.
install() patches Schema process-wide and is intended for the standard
GIL-enabled CPython. The free-threaded build (3.13t) is not yet supported:
the install swap and the per-class hook caches aren't audited for concurrent
access. Call install() once at startup before spawning workers.
Where the speedup is limited
Some shapes are inherently bounded — the work the core can't move into Rust dominates the call. These are correct, just not where the gains are:
- Hook-bearing loads are the weakest case (~2x, vs ~7x without hooks). When a
schema has
pre_load/post_load/validates/validates_schema, the core runs the per-field deserialize but marshmallow's Python hook-dispatch (_invoke_load_processors,_invoke_field_validators) runs around it. On a small schema the core step is ~8% of the load; the remaining ~90% is that Python machinery, which wraps user callbacks and cannot be moved into Rust. - Small / flat schemas are capped by fixed per-call overhead (~20–30%). The
dump/loadentry prologue (argument normalization, the per-instance serializer-cache lookup, the partial/unknown checks) is constant per call, so it dominates exactly when the payload is tiny. It amortizes to near-zero as the payload grows — speedup on a list of records is flat regardless of length. loadsgains less thanload. JSON parsing still goes through CPython's Cjson.loads; a fused Rust parser was prototyped but did not beat it, so only the subsequent per-field step is accelerated.
For collections of records (the common hot path) the fixed overhead vanishes and
the speedup is steady (~7–8x). Run performance/analyze_paths.py to see whether a
given schema even reaches the core, and performance/benchmark.py to measure it.
Development
Requires cargo (rustup) and maturin.
# build + install the extension into the current venv
uvx maturin develop --release
# run the tests (needs marshmallow + pytest installed)
pytest
# force the pure-Python path
MARSHMALLOW_NO_ACCEL=1 pytest
tests/test_equivalence.py asserts that dump/load produce identical output
and errors with the core active vs. forced onto pure Python, across scalars,
nested/list/enum/temporal/UUID fields, partial=True, and error inputs.
Benchmarking
The performance/ directory (not shipped in wheels) measures the core against
stock marshmallow through the public install() / uninstall() API. Run it from
the repo root with the compiled extension importable (uvx maturin develop --release first, or point PYTHONPATH at the repo while the wheel is installed):
# stock-vs-core table for dump / load / dumps / loads on four schema shapes
python -m performance.benchmark # all cases
python -m performance.benchmark --number 20000 --only flat,list
# coverage probe: per-field native vs callback for each schema shape
python -m performance.analyze_paths
benchmark.py reports per-call microseconds for stock and core plus the speedup
ratio, across flat-scalar, nested, list-heavy, and validator-heavy schemas.
analyze_paths.py inspects the compiled payload and shows which fields run
native in Rust vs. fall back to a Python callback — it tells you exactly where a
real schema still defers to pure Python.
Releasing
CI (.github/workflows/ci.yml) builds the wheel and runs the suite against
stock marshmallow on Python 3.10–3.13, both with the core active and with
MARSHMALLOW_NO_ACCEL=1. Publishing (.github/workflows/release.yml) builds
abi3 wheels + sdist for Linux/macOS/Windows on a v* tag and uploads them to
PyPI via trusted publishing. Before
the first release, configure the PyPI trusted publisher for this repo and create
a pypi GitHub Environment, then push a tag (e.g. git tag v0.1.0 && git push --tags).
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
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 marshmallow_core-0.1.10.tar.gz.
File metadata
- Download URL: marshmallow_core-0.1.10.tar.gz
- Upload date:
- Size: 108.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7444f47bc74e4633fc5f1b7d31e557c4283103efb668c408c7cca391c7324554
|
|
| MD5 |
e85f8cc75e48d240f844a8253e4d75cd
|
|
| BLAKE2b-256 |
fc6cca6bdd42c6601aaaa0a64a82ba6129bd47667b367eac23d5218cf20d050e
|
Provenance
The following attestation bundles were made for marshmallow_core-0.1.10.tar.gz:
Publisher:
release.yml on gunlinux/marshmallow_core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_core-0.1.10.tar.gz -
Subject digest:
7444f47bc74e4633fc5f1b7d31e557c4283103efb668c408c7cca391c7324554 - Sigstore transparency entry: 1783970669
- Sigstore integration time:
-
Permalink:
gunlinux/marshmallow_core@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Branch / Tag:
refs/tags/v0.1.10 - Owner: https://github.com/gunlinux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file marshmallow_core-0.1.10-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: marshmallow_core-0.1.10-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 254.3 kB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
444ede46f74a561d8f1345609b65995b707712f04a3e17ae3dd1db76d8d32c5c
|
|
| MD5 |
741bb52cb99ae69c303063fba263428e
|
|
| BLAKE2b-256 |
18bd80a3d7d392a97ccc0f513aa32dde8a3ad5e6aae12ef201b4fda3cd6a753c
|
Provenance
The following attestation bundles were made for marshmallow_core-0.1.10-cp310-abi3-win_amd64.whl:
Publisher:
release.yml on gunlinux/marshmallow_core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_core-0.1.10-cp310-abi3-win_amd64.whl -
Subject digest:
444ede46f74a561d8f1345609b65995b707712f04a3e17ae3dd1db76d8d32c5c - Sigstore transparency entry: 1783973069
- Sigstore integration time:
-
Permalink:
gunlinux/marshmallow_core@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Branch / Tag:
refs/tags/v0.1.10 - Owner: https://github.com/gunlinux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 386.1 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b77c313ea734426eafd25a348c359a236e3bb5386dfda69d55280b8b408e4350
|
|
| MD5 |
8f79365a8dc5fa1d274fa55a9f855ed7
|
|
| BLAKE2b-256 |
544aee417ba71234e79297212040c67a4d06ee93fa297f883ad37cc21c8708a0
|
Provenance
The following attestation bundles were made for marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on gunlinux/marshmallow_core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
b77c313ea734426eafd25a348c359a236e3bb5386dfda69d55280b8b408e4350 - Sigstore transparency entry: 1783970711
- Sigstore integration time:
-
Permalink:
gunlinux/marshmallow_core@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Branch / Tag:
refs/tags/v0.1.10 - Owner: https://github.com/gunlinux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 387.6 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38276319f57467a79748ac82d5c677f80b4991b25887944f22784e2aa7406396
|
|
| MD5 |
e4b53eac4d0b05b1001996dad580609e
|
|
| BLAKE2b-256 |
1eb2f2e6f0a1585f5e26122f565a6c530960e6089318f76893c991aa6adad653
|
Provenance
The following attestation bundles were made for marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on gunlinux/marshmallow_core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_core-0.1.10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
38276319f57467a79748ac82d5c677f80b4991b25887944f22784e2aa7406396 - Sigstore transparency entry: 1783971535
- Sigstore integration time:
-
Permalink:
gunlinux/marshmallow_core@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Branch / Tag:
refs/tags/v0.1.10 - Owner: https://github.com/gunlinux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Trigger Event:
push
-
Statement type:
File details
Details for the file marshmallow_core-0.1.10-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: marshmallow_core-0.1.10-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 354.0 kB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc6766cdb3db4033577adee26c1c3b5a5898b0b6775fa9aaee045e55d814d48a
|
|
| MD5 |
eb6d43390c65ecdbd9578105cad84855
|
|
| BLAKE2b-256 |
5d8efc7a8e44a1d0bb1ec40d2f9e861005c8f0452e10950a4d300f82542001a3
|
Provenance
The following attestation bundles were made for marshmallow_core-0.1.10-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on gunlinux/marshmallow_core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
marshmallow_core-0.1.10-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
fc6766cdb3db4033577adee26c1c3b5a5898b0b6775fa9aaee045e55d814d48a - Sigstore transparency entry: 1783971461
- Sigstore integration time:
-
Permalink:
gunlinux/marshmallow_core@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Branch / Tag:
refs/tags/v0.1.10 - Owner: https://github.com/gunlinux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@b2269bfab8d102dd2f3ded42ea9721eb5a318b78 -
Trigger Event:
push
-
Statement type: