Skip to main content

Rust/PyO3 accelerator for cerberus: a native, iso-functional Validator with transparent fallback to cerberus.

Project description

pulse-cerberus

A Rust/PyO3 accelerator for cerberus — an iso-functional, drop-in Validator whose validate() hot path runs in native code, with transparent fallback to cerberus for anything outside its fast path. It's a separate package that depends on cerberus and delegates back to it, so it never diverges on input cerberus accepts — by construction.

pip install pulse-cerberus
# was:  from cerberus import Validator
from pulse_cerberus import Validator

schema = {
    "id":   {"type": "integer", "required": True, "min": 1},
    "name": {"type": "string", "required": True, "minlength": 1, "maxlength": 64},
    "role": {"type": "string", "allowed": ["admin", "user", "guest"]},
    "tags": {"type": "list", "schema": {"type": "string"}},
    "addr": {"type": "dict", "schema": {"city": {"type": "string"}}},
}

v = Validator(schema)          # compiled once
v.validate({"id": 1, "name": "alice", "role": "admin"})   # → True, ~200× faster
v.errors                        # same .errors tree as cerberus, byte-for-byte

The only change is the import. Validator, .validate(), .validated(), .errors, .document, SchemaError, DocumentError, registries — the cerberus API works unchanged.

Performance

cerberus's Validator.validate is interpreted Python: per call it re-dispatches every rule, spawns child-validators for nested schemas, and re-expands the schema. pulse-cerberus compiles the schema once into a Rust rule-AST and then validates flatly.

Drift-immune A/B (median per call), pre-built validator, realistic schema (8 fields incl. a nested dict and a list), CPython 3.11, Apple Silicon:

median / validate() speedup
cerberus.Validator.validate ~245 µs
pulse_cerberus.Validator.validate ~1.2 µs ~×200

The striking part: cerberus pays a large fixed per-call cost (rule dispatch + child-validator spawning

  • schema re-expansion) on every validate(), even for a small document. That interpreted machinery is exactly what a native validator removes. Reproduce it:
import statistics, time, cerberus, pulse_cerberus

schema = {"id": {"type": "integer", "required": True, "min": 1},
          "name": {"type": "string", "required": True, "minlength": 1, "maxlength": 64},
          "role": {"type": "string", "allowed": ["admin", "user", "guest"]},
          "tags": {"type": "list", "schema": {"type": "string"}},
          "addr": {"type": "dict", "schema": {"city": {"type": "string"}}}}
doc = {"id": 1, "name": "alice", "role": "admin", "tags": ["x"], "addr": {"city": "Paris"}}

ref  = cerberus.Validator(schema)        # build once (validators are meant to be reused)
cand = pulse_cerberus.Validator(schema)

def bench(v, reps, rounds=15):
    out = []
    for _ in range(rounds):
        t = time.perf_counter()
        for _ in range(reps): v.validate(doc)
        out.append((time.perf_counter() - t) / reps)
    return statistics.median(out)

r = bench(ref, 2000); c = bench(cand, 20000)
print(f"cerberus {r*1e6:.1f} us  ->  pulse {c*1e6:.2f} us   (x{r/c:.0f})")

Why Rust wins here (and why this is not a false friend)

Profiling validate(): the hot path is interpreted Python (__validate_definitions / __get_rule_handler per rule×field, child-validator spawning, schema re-expansion), 0 % in the C re engine. The ~38 % that shows up as "C" is isinstance / abc.__instancecheck__ / dict.get — dispatch glue that simply evaporates in typed Rust. Because the bottleneck is interpreted Python (not a C-bound kernel), a native rewrite wins by orders of magnitude.

What's covered natively (and what falls back)

pulse-cerberus validates natively when the schema uses only:

  • types integer / float / number / boolean / string / dict / list (with cerberus's exact bool semantics: integer/float accept True, number excludes it);
  • rules required, allowed, min, max, minlength, maxlength, empty, nullable, and nested schema (dict + list-of), with allow_unknown and require_all.

Everything else is transparently delegated to cerberus: normalization (coerce/default/rename/ purge_unknown/readonly), logic (*of, dependencies, excludes), check_with, keysrules/ valuesrules, items, contains, regex, allow_unknown as a rules-set, registries, custom Validator subclasses, non-dict documents, and exotic value types. A SchemaError is raised at construction exactly as cerberus would. When in doubt, it falls back — it never guesses.

The error messages are rendered through Python's str() (cerberus formats its own messages the same way), so the .errors tree — structure, messages, and per-field alphabetical-by-rule order — is identical to cerberus's.

Iso-functionality

Proven by a typed differential oracle comparing (validate(), .errors, .document) against stock cerberus, on a curated corpus of the iso-critical cases plus adversarial fuzzing of random (schema, doc) pairs — including the bool subtleties, error ordering, nested/list-of error trees, the empty/min interaction, and exception parity (SchemaError/DocumentError). The pure-Python fallback path is verified to be iso too (PULSE_FORCE_FALLBACK=1).

Wheels

abi3 wheels (Python ≥ 3.11) for Linux (x86_64/aarch64, manylinux + musllinux), macOS (Apple Silicon), and Windows; sdist elsewhere (builds the Rust core via maturin).

License

ISC (same as cerberus).

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

pulse_cerberus-0.1.0.tar.gz (17.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pulse_cerberus-0.1.0-cp311-abi3-win_amd64.whl (141.0 kB view details)

Uploaded CPython 3.11+Windows x86-64

pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (257.8 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ x86-64

pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (255.9 kB view details)

Uploaded CPython 3.11+manylinux: glibc 2.17+ ARM64

pulse_cerberus-0.1.0-cp311-abi3-macosx_11_0_arm64.whl (234.8 kB view details)

Uploaded CPython 3.11+macOS 11.0+ ARM64

File details

Details for the file pulse_cerberus-0.1.0.tar.gz.

File metadata

  • Download URL: pulse_cerberus-0.1.0.tar.gz
  • Upload date:
  • Size: 17.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pulse_cerberus-0.1.0.tar.gz
Algorithm Hash digest
SHA256 a8e6be9150ed956c2dbcfd37793864d55b7f08573e271c410b94d246bd9ef866
MD5 2d9ac160da6203b70b79f56b171c4057
BLAKE2b-256 d1cdbe187f3a7a813090f67c504c85ba593c4f1f877cdb030e2e578b26881d54

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulse_cerberus-0.1.0.tar.gz:

Publisher: release.yml on AstekGroup/pulse-cerberus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulse_cerberus-0.1.0-cp311-abi3-win_amd64.whl.

File metadata

File hashes

Hashes for pulse_cerberus-0.1.0-cp311-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 a7fc44c0e2e009f4433bc500623b3fd3e27b116ae667b05b78aee035eb331f71
MD5 fc71fef4776b03661c65a816eeb8906a
BLAKE2b-256 a44cc9cbb87e665bcaccfb275612278fcf9398ccf81ada60f4d9a547838ec8a4

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulse_cerberus-0.1.0-cp311-abi3-win_amd64.whl:

Publisher: release.yml on AstekGroup/pulse-cerberus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 3d3e0c10a94096dad1ef79147dffd428c88e8624fae767b79f58821665774f42
MD5 d56cf942107261505c6e182ad23a2f63
BLAKE2b-256 4e47e814bb4c79f86492f8c925283b244516d54ff4e3a979a43d9aab783013a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on AstekGroup/pulse-cerberus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b6c85c8431ed5109533e24aa8a3478ac2601c1cf6a4b13ee570c527ec0efe8a1
MD5 0551142c326f70ee4d8ecbcd960c8f99
BLAKE2b-256 64f25818c22697343d1af79fa00355dbe1a2b9a121c8fe6e2a5907b89c495a30

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulse_cerberus-0.1.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on AstekGroup/pulse-cerberus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pulse_cerberus-0.1.0-cp311-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for pulse_cerberus-0.1.0-cp311-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 9235f3cb133fc2d2819e113d6beefc854724c35b718bf8d5d4d1d9bb49773ccb
MD5 c6d43ab3792fc9e666b2a6895be9c31f
BLAKE2b-256 b77cd18d93c8b7761985cf62f3c3df963f272dcb689be8d95b4fdb86ed5ad78b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pulse_cerberus-0.1.0-cp311-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on AstekGroup/pulse-cerberus

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page