Repository navigation
add orjson - #6116
add orjson#6116benedikt-bartscher wants to merge 70 commits into
Conversation
Merging this PR will not alter performance
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| 🆕 | test_process_event_wire[large_delta] |
N/A | 38.8 ms | N/A |
| 🆕 | test_process_event_wire[small_delta] |
N/A | 3.1 ms | N/A |
Comparing benedikt-bartscher:try-orjson (a0154b5) with main (4be8b4d)
[Medium impact] The PR changes JSON handling across generated app files and live Reflex updates. Fix the shared-base serializer bypass before merging; installing the extra can change outgoing values. FindingsSummaryAdds optional orjson encoding for state updates and generated frontend files, with stdlib fallbacks.
Previous threads were unnumbered, so no numbered classifications are recorded. benedikt-bartscher disputed the Plotly auto-engine findings because its dump and parse engines agree; those findings are set aside. The exception-inheritance objection was also correctly disputed by benedikt-bartscher. Reviews (44) · Last reviewed commit: "Merge remote-tracking branch 'upstream/m..." · Reviewed by Greptile |
|
the issue with orjson is that we lose NaN handling which is relevant for numpy, which is why we were using JSON5 as well |
|
Yeah, that's why i did not update it in all places |
…llback Happy path uses native JSON.parse; the catch rewrites Python"s bare Infinity/-Infinity/NaN tokens outside string literals before retrying, so only payloads that actually contain specials pay the extra cost. NaN has no JSON literal and becomes null (matches JSON.stringify).
…o null Swap bare NaN for a sentinel string before JSON.parse and revive it back to a real NaN so Python-side float(nan) round-trips to the frontend.
|
related #6339 |
Apply orjson_dumps/orjson_loads helpers to the new reflex_base package locations after upstream's restructuring.
bed6d7b to
7555e7b
Compare
orjson.loads rounds integers outside the 64-bit range to floats, so a customdata id could be silently corrupted when plotly serialized with its stdlib engine.
There was a problem hiding this comment.
All reported issues were addressed across 39 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
|
blocked by #6933 edit: merged/unblocked now |
Route main's new stateful-pages marker reader/writer through the orjson helpers and keep both new event-processing benchmarks.
# Conflicts: # reflex/app.py # reflex/utils/frontend_skeleton.py # tests/benchmarks/test_event_processing.py # tests/units/test_prerequisites.py # tests/units/utils/test_exec.py
Upstream's new JSON emitters in the compile path now go through orjson_dumps like the PR's other call sites: the stateSchema export in app_root_template (templates.py no longer imports json) and the useStateContext hook in VarData.from_state. The stateful pages marker read keeps orjson_loads with upstream's ValueError comment. The orjson test's datetime.timezone.utc becomes datetime.UTC, which ruff now requires after upstream raised the minimum Python to 3.11.
| if not _orjson_native_equivalent and any( | ||
| isinstance(candidate, type) and issubclass(candidate, _ORJSON_NATIVE_TYPES) | ||
| for candidate in (type_, *_union_members(type_)) |
There was a problem hiding this comment.
Shared serializers are skipped
A serializer registered for a shared base class can still be bypassed when orjson is installed. For example, register a serializer for WireValue, then define Status(WireValue, Enum). get_serializer(Status) finds that serializer, but this check only tests whether WireValue itself subclasses Enum or UUID.
The flag stays false, so outgoing updates use Status.value instead of the registered result. Account for serializers inherited through other base classes so installing the extra does not change user data.
| await p.enqueue( | ||
| TOKEN, | ||
| Event( | ||
| name=(fields := orjson_loads(raw))["name"], |
There was a problem hiding this comment.
Benchmark measures the wrong decoder
The new wire benchmark decodes incoming events with orjson_loads, but the real socket path deliberately uses stdlib json.loads. With the extra installed, this measures a faster decoder that users do not get, so the benchmark can overstate the event-path improvement. Use _sio_loads and _sio_dumps here to measure the actual socket path.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
#7448 seems like a better approach |
State updates over the websocket and generated frontend files are serialized with orjson when the new
reflex[orjson]extra is installed; without it Reflex falls back to the stdlibjsonmodule. Output is byte-compatible between the two backends, and inbound socket integers beyond 64 bits still reach event handlers exactly.Benchmarks
Event wire path — decode 3 incoming events, process them, serialize 3 deltas — measured with each revision's own socket codec (
_sio_dumps/_sio_loads). Python 3.14.7, 500 rounds, 3 interleaved repetitions; medians, ±1–2% run to run.mainOutgoing encode in isolation (95.7 KB payload):
mainWithout the extra, the fallback adds two scans of the serialized string on top of the stdlib dump — the sentinel-collision check and the
isascii()UTF-8 guard — about +29 µs on 95.7 KB.