perf(pypangraph): parse graphs into typed pydantic models - #203
Draft
ivan-aksamentov wants to merge 3 commits into
Draft
perf(pypangraph): parse graphs into typed pydantic models#203ivan-aksamentov wants to merge 3 commits into
ivan-aksamentov wants to merge 3 commits into
Conversation
Schema validation dominates the cost of loading a graph: on a mid-sized graph the pure-Python jsonschema pass takes seconds while parsing takes tens of milliseconds, because the validator walks every node, edit and position in interpreted Python. Parse and validate the JSON in one call with PangraphData.model_validate_json, building the typed model in model.py in pydantic's compiled core. A malformed document is reported as a read failure, distinct from a schema violation. The models carry the schema's constraints (non-negative integers, single-character alt, the strand enum), and the path, block and node collections now read the typed models. Pangraph still accepts a plain dict; it is validated with PangraphData.model_validate, so building a graph from an in-memory dict enforces the same schema as loading from a file.
Lock in which malformed graphs the loader rejects (missing required fields, wrong types, out-of-range values, bad strand enum), so the accept/reject contract holds independently of the validation engine.
Document how a graph is read, parsed into typed models and constructed, and why loading uses pydantic. Add a benchmark that times each load phase and every validation engine present, so the numbers can be reproduced on one machine.
This was referenced Aug 18, 2026
ivan-aksamentov
marked this pull request as draft
August 18, 2026 12:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alternative PRs (mutually exclusive, merge one):
Problem
Pangraph.from_jsonvalidates every loaded graph against the JSON schema generated from the Rust types before building the object. On real graphs that validation, not parsing, dominates load time. Issue #200 reports the slowdown.On the benchmark graph below, parsing the JSON takes about 40 ms while the pure-Python
jsonschemapass takes about 2 seconds: the validator walks every node, edit and position in interpreted Python.This change
Parse and validate the JSON in one call with
PangraphData.model_validate_json, building the typed model inpypangraph/model.pyinpydantic's compiled core. A malformed document (invalid JSON) is reported as a read failure, distinct from a schema violation. The models carry the schema's constraints (non-negative integers, single-characteralt, thestrandenum), and the path, block and node collections read the typed models.Pangraphstill accepts a plain dict, which is validated withPangraphData.model_validate, so an in-memory dict enforces the same schema as a file.Why this is the one to merge
pydanticis one of the most widely used Python libraries; the model style, theFieldconstraints and the error format are familiar, so the code is approachable and easy to extend. The load still drops from about 2 seconds to about 92 ms, a 22x speedup.pydanticreports the exact field path, the expected type and the offending value, which makes debugging a bad graph far easier than a schema-walker's message.pydanticanddatamodel-code-generatorare already inrequirements.txt, and the Makefile already generates a Python model from the schema. This PR fits the direction the repository is already pointed.If the goal is typed internals with the most familiar, best-supported validation library, this is the strongest choice.
Benchmark
Fixture:
packages/pypangraph/tests/data/staph.json.gz(664 blocks, 6817 nodes, 15 paths; 1.81 MB compressed, 9.72 MB decoded). Median of 7 runs on one machine in the project Python container, measured bypackages/pypangraph/benchmarks/bench_load. Correctness parity was verified for every engine: each accepts the valid graph and rejects missing-field, wrong-type, negative-value and bad-strand mutations.Full load (parse and validate together):
json+jsonschema)Per-phase breakdown. Decompression is shared; this PR replaces JSON parsing and validation with a single typed parse.
Shared (unchanged by this PR):
Parse and validate (what this PR changes):
Methodology notes:
jsonschemavalidator does not help; the cost is the interpreted traversal, not validator construction.fastjsonschemawas rejected: it errors on the schema'sformat: uintannotation.format: uintis a decorative annotation; the non-negative range is enforced byminimum: 0. No engine asserts on the format string.Conclusion: validation is about 98% of load time, and every candidate removes it.
pydanticfolds parsing and validation into one compiled call that emits typed models, trading raw speed againstmsgspecfor a far more common library and richer error messages.The three alternatives
All three PRs branch from
feat/mergeand rewrite the same loader; they cannot be combined.model_validate_json. Typed internals from the most widely used validation library, with the clearest error messages, about 22x.Pick pydantic for typed internals on the most familiar foundation; pick msgspec for maximum speed, or jsonschema-rs for the smallest diff.
Work items
pydanticmodels mirroring the schema inpypangraph/model.py.pypangraph/class_graph.py; accept a dict viamodel_validate.pydanticinpyproject.tomland the Python container (already inrequirements.txt).benchmarks/bench_loadand a graph-loading doc.Possible improvements
datamodel-codegen(as the Makefile already does for the dataclass example) so they cannot drift from the Rust types.Verify