Skip to content

Commit 091d8d9

Browse files
try orjson
1 parent 5d1d6db commit 091d8d9

17 files changed

Lines changed: 210 additions & 65 deletions

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ db = [
5757
"sqlmodel >=0.0.24,<0.1",
5858
]
5959
monitoring = ["pyleak >=0.1.14,<1.0"]
60+
orjson = ["orjson >=3.10,<4.0"]
6061

6162
[project.urls]
6263
homepage = "https://reflex.dev"

reflex/app.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ def _setup_state(self) -> None:
546546
ping_timeout=environment.REFLEX_SOCKET_TIMEOUT.get(),
547547
json=SimpleNamespace(
548548
dumps=staticmethod(format.json_dumps),
549-
loads=staticmethod(json.loads),
549+
loads=staticmethod(format.orjson_loads),
550550
),
551551
allow_upgrades=False,
552552
transports=[config.transport],
@@ -1170,8 +1170,8 @@ def get_compilation_time() -> str:
11701170
if not dry_run and not should_compile and backend_dir.exists():
11711171
stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
11721172
if stateful_pages_marker.exists():
1173-
with stateful_pages_marker.open("r") as f:
1174-
stateful_pages = json.load(f)
1173+
with stateful_pages_marker.open("rb") as f:
1174+
stateful_pages = format.orjson_loads(f.read())
11751175
for route in stateful_pages:
11761176
console.debug(f"BE Evaluating stateful page: {route}")
11771177
self._compile_page(route, save_page=False)
@@ -1546,7 +1546,7 @@ def _write_stateful_pages_marker(self):
15461546
)
15471547
stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
15481548
with stateful_pages_marker.open("w") as f:
1549-
json.dump(list(self._stateful_pages), f)
1549+
f.write(format.orjson_dumps(list(self._stateful_pages)))
15501550

15511551
def add_all_routes_endpoint(self):
15521552
"""Add an endpoint to the app that returns all the routes."""
@@ -2172,7 +2172,7 @@ async def on_event(self, sid: str, data: Any):
21722172
f" Event data: {fields}"
21732173
)
21742174
try:
2175-
fields = json.loads(fields)
2175+
fields = format.orjson_loads(fields)
21762176
except json.JSONDecodeError as ex:
21772177
msg = f"Failed to deserialize event data: {fields}."
21782178
raise exceptions.EventDeserializationError(msg) from ex

reflex/compiler/templates.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@
22

33
from __future__ import annotations
44

5-
import json
65
from collections.abc import Iterable, Mapping
76
from typing import TYPE_CHECKING, Any, Literal
87

98
from reflex import constants
109
from reflex.constants import Hooks
1110
from reflex.constants.state import CAMEL_CASE_MEMO_MARKER
12-
from reflex.utils.format import format_state_name, json_dumps
11+
from reflex.utils.format import format_state_name, json_dumps, orjson_dumps
1312
from reflex.vars.base import VarData
1413

1514
if TYPE_CHECKING:
@@ -354,11 +353,11 @@ def context_template(
354353
export const DispatchContext = createContext(null);
355354
export const StateContexts = {{{state_contexts_str}}};
356355
export const EventLoopContext = createContext(null);
357-
export const clientStorage = {"{}" if client_storage is None else json.dumps(client_storage)}
356+
export const clientStorage = {"{}" if client_storage is None else orjson_dumps(client_storage)}
358357
359358
{state_str}
360359
361-
export const isDevMode = {json.dumps(is_dev_mode)};
360+
export const isDevMode = {orjson_dumps(is_dev_mode)};
362361
363362
export function UploadFilesProvider({{ children }}) {{
364363
const [filesById, setFilesById] = useState({{}})
@@ -486,7 +485,7 @@ def package_json_template(
486485
Returns:
487486
Rendered package.json content as string.
488487
"""
489-
return json.dumps({
488+
return orjson_dumps({
490489
"name": "reflex",
491490
"type": "module",
492491
"scripts": scripts,

reflex/plugins/shared_tailwind.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def tailwind_config_js_template(
9191
Returns:
9292
The Tailwind config template.
9393
"""
94-
import json
94+
from reflex.utils.format import orjson_dumps
9595

9696
# Extract parameters
9797
plugins = kwargs.get("plugins", [])
@@ -113,23 +113,24 @@ def tailwind_config_js_template(
113113

114114
# Generate import statements for destructured imports
115115
import_lines = "\n".join([
116-
f"import {{ {imp['name']} }} from {json.dumps(imp['from'])};" for imp in imports
116+
f"import {{ {imp['name']} }} from {orjson_dumps(imp['from'])};"
117+
for imp in imports
117118
])
118119

119120
# Generate plugin imports
120121
plugin_imports = []
121122
for i, plugin in enumerate(plugins, 1):
122123
if isinstance(plugin, Mapping) and "call" not in plugin:
123124
plugin_imports.append(
124-
f"import plugin{i} from {json.dumps(plugin['name'])};"
125+
f"import plugin{i} from {orjson_dumps(plugin['name'])};"
125126
)
126127
elif not isinstance(plugin, Mapping):
127-
plugin_imports.append(f"import plugin{i} from {json.dumps(plugin)};")
128+
plugin_imports.append(f"import plugin{i} from {orjson_dumps(plugin)};")
128129

129130
plugin_imports_lines = "\n".join(plugin_imports)
130131

131132
presets_imports_lines = "\n".join([
132-
f"import preset{i} from {json.dumps(preset)};"
133+
f"import preset{i} from {orjson_dumps(preset)};"
133134
for i, preset in enumerate(presets, 1)
134135
])
135136

@@ -139,7 +140,7 @@ def tailwind_config_js_template(
139140
if isinstance(plugin, Mapping) and "call" in plugin:
140141
args_part = ""
141142
if "args" in plugin:
142-
args_part = json.dumps(plugin["args"])
143+
args_part = orjson_dumps(plugin["args"])
143144
plugin_list.append(f"{plugin['call']}({args_part})")
144145
else:
145146
plugin_list.append(f"plugin{i}")
@@ -154,13 +155,13 @@ def tailwind_config_js_template(
154155
{presets_imports_lines}
155156
156157
export default {{
157-
content: {json.dumps(content or default_content)},
158-
theme: {json.dumps(theme or {})},
159-
{f"darkMode: {json.dumps(dark_mode)}," if dark_mode is not None else ""}
160-
{f"corePlugins: {json.dumps(core_plugins)}," if core_plugins is not None else ""}
161-
{f"importants: {json.dumps(important)}," if important is not None else ""}
162-
{f"prefix: {json.dumps(prefix)}," if prefix is not None else ""}
163-
{f"separator: {json.dumps(separator)}," if separator is not None else ""}
158+
content: {orjson_dumps(content or default_content)},
159+
theme: {orjson_dumps(theme or {})},
160+
{f"darkMode: {orjson_dumps(dark_mode)}," if dark_mode is not None else ""}
161+
{f"corePlugins: {orjson_dumps(core_plugins)}," if core_plugins is not None else ""}
162+
{f"importants: {orjson_dumps(important)}," if important is not None else ""}
163+
{f"prefix: {orjson_dumps(prefix)}," if prefix is not None else ""}
164+
{f"separator: {orjson_dumps(separator)}," if separator is not None else ""}
164165
{f"presets: [{', '.join(f'preset{i}' for i in range(1, len(presets) + 1))}]," if presets else ""}
165166
plugins: [{plugin_use_str}]
166167
}};

reflex/utils/exec.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import hashlib
66
import importlib.util
7-
import json
87
import os
98
import platform
109
import re
@@ -21,6 +20,7 @@
2120
from reflex.environment import environment
2221
from reflex.utils import console, path_ops
2322
from reflex.utils.decorator import once
23+
from reflex.utils.format import orjson_dumps, orjson_loads
2424
from reflex.utils.misc import get_module_path
2525
from reflex.utils.prerequisites import get_web_dir
2626

@@ -37,11 +37,11 @@ def get_package_json_and_hash(package_json_path: Path) -> tuple[PackageJson, str
3737
Returns:
3838
A tuple containing the content of package.json as a dictionary and its SHA-256 hash.
3939
"""
40-
with package_json_path.open("r") as file:
41-
json_data = json.load(file)
40+
with package_json_path.open("rb") as file:
41+
json_data = orjson_loads(file.read())
4242

4343
# Calculate the hash
44-
json_string = json.dumps(json_data, sort_keys=True)
44+
json_string = orjson_dumps(json_data, sort_keys=True)
4545
hash_object = hashlib.sha256(json_string.encode())
4646
return (json_data, hash_object.hexdigest())
4747

reflex/utils/format.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def format_event(event_spec: EventSpec) -> str:
498498
name._js_expr,
499499
(
500500
wrap(
501-
json.dumps(val._js_expr).strip('"').replace("`", "\\`"),
501+
orjson_dumps(val._js_expr).strip('"').replace("`", "\\`"),
502502
"`",
503503
)
504504
if val._var_is_string
@@ -686,6 +686,62 @@ def json_dumps(obj: Any, **kwargs) -> str:
686686
return json.dumps(obj, **kwargs)
687687

688688

689+
def orjson_dumps(obj: Any, **kwargs) -> str:
690+
"""Serialize obj to a JSON string, using orjson when available.
691+
692+
Translates common json.dumps kwargs (indent, sort_keys) into orjson
693+
option flags. Falls back to stdlib json if orjson is not installed,
694+
an unsupported kwarg is passed, or orjson raises TypeError.
695+
696+
Args:
697+
obj: The object to serialize.
698+
kwargs: Optional keyword arguments (indent, sort_keys).
699+
700+
Returns:
701+
A JSON string.
702+
"""
703+
try:
704+
import orjson
705+
except ImportError:
706+
return json.dumps(obj, **kwargs)
707+
708+
option = 0
709+
if kwargs.pop("indent", None):
710+
option |= orjson.OPT_INDENT_2
711+
if kwargs.pop("sort_keys", False):
712+
option |= orjson.OPT_SORT_KEYS
713+
kwargs.pop("ensure_ascii", None) # orjson always produces UTF-8
714+
715+
if kwargs:
716+
# Fall back to stdlib json for unsupported kwargs.
717+
return json.dumps(obj, **kwargs)
718+
719+
try:
720+
return orjson.dumps(obj, option=option or None).decode()
721+
except TypeError:
722+
# Fallback for types orjson can't handle (e.g. int > 64-bit).
723+
return json.dumps(obj)
724+
725+
726+
def orjson_loads(data: str | bytes) -> Any:
727+
"""Deserialize a JSON string or bytes, using orjson when available.
728+
729+
Falls back to stdlib json.loads if orjson is not installed.
730+
731+
Args:
732+
data: JSON string or bytes to deserialize.
733+
734+
Returns:
735+
The deserialized Python object.
736+
"""
737+
try:
738+
import orjson
739+
except ImportError:
740+
return json.loads(data)
741+
742+
return orjson.loads(data)
743+
744+
689745
def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]:
690746
"""Collapse keys with consecutive suffixes into a single list value.
691747

reflex/utils/frontend_skeleton.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""This module provides utility functions to initialize the frontend skeleton."""
22

3-
import json
43
import random
54
import re
65
from pathlib import Path
@@ -10,6 +9,7 @@
109
from reflex.config import Config, get_config
1110
from reflex.environment import environment
1211
from reflex.utils import console, path_ops
12+
from reflex.utils.format import orjson_dumps
1313
from reflex.utils.prerequisites import get_project_hash, get_web_dir
1414
from reflex.utils.registry import get_npm_registry
1515

@@ -164,7 +164,7 @@ def _update_react_router_config(config: Config, prerender_routes: bool = False):
164164
react_router_config["prerender"] = True
165165
react_router_config["build"] = constants.Dirs.BUILD_DIR
166166

167-
return f"export default {json.dumps(react_router_config)};"
167+
return f"export default {orjson_dumps(react_router_config)};"
168168

169169

170170
def _compile_package_json():

reflex/utils/path_ops.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import annotations
44

5-
import json
65
import os
76
import re
87
import shutil
@@ -11,6 +10,7 @@
1110

1211
from reflex.config import get_config
1312
from reflex.environment import environment
13+
from reflex.utils.format import orjson_dumps, orjson_loads
1414

1515
# Shorthand for join.
1616
join = os.linesep.join
@@ -245,15 +245,14 @@ def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
245245
# Read the existing json object from the file.
246246
json_object = {}
247247
if fp.stat().st_size:
248-
with fp.open() as f:
249-
json_object = json.load(f)
248+
with fp.open("rb") as f:
249+
json_object = orjson_loads(f.read())
250250

251251
# Update the json object with the new data.
252252
json_object.update(update_dict)
253253

254254
# Write the updated json object to the file
255-
with fp.open("w") as f:
256-
json.dump(json_object, f, ensure_ascii=False)
255+
fp.write_text(orjson_dumps(json_object))
257256

258257

259258
def find_replace(directory: str | Path, find: str, replace: str):

reflex/utils/prerequisites.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import importlib
77
import importlib.metadata
88
import inspect
9-
import json
109
import random
1110
import re
1211
import sys
@@ -24,6 +23,7 @@
2423
from reflex.environment import environment
2524
from reflex.utils import console, net, path_ops
2625
from reflex.utils.decorator import once
26+
from reflex.utils.format import orjson_loads
2727
from reflex.utils.misc import get_module_path
2828

2929
if typing.TYPE_CHECKING:
@@ -111,7 +111,7 @@ def get_or_set_last_reflex_version_check_datetime():
111111
if not reflex_json_file.exists():
112112
return None
113113
# Open and read the file
114-
data = json.loads(reflex_json_file.read_text())
114+
data = orjson_loads(reflex_json_file.read_text())
115115
last_version_check_datetime = data.get("last_version_check_datetime")
116116
if not last_version_check_datetime:
117117
data.update({"last_version_check_datetime": str(datetime.now())})
@@ -492,7 +492,7 @@ def get_project_hash(raise_on_fail: bool = False) -> int | None:
492492
json_file = get_web_dir() / constants.Reflex.JSON
493493
if not json_file.exists() and not raise_on_fail:
494494
return None
495-
data = json.loads(json_file.read_text())
495+
data = orjson_loads(json_file.read_text())
496496
return data.get("project_hash")
497497

498498

@@ -558,7 +558,7 @@ def _is_app_compiled_with_same_reflex_version() -> bool:
558558
json_file = get_web_dir() / constants.Reflex.JSON
559559
if not json_file.exists():
560560
return False
561-
app_version = json.loads(json_file.read_text()).get("version")
561+
app_version = orjson_loads(json_file.read_text()).get("version")
562562
return app_version == constants.Reflex.VERSION
563563

564564

0 commit comments

Comments
 (0)