Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ db = [
"sqlmodel >=0.0.24,<0.1",
]
monitoring = ["pyleak >=0.1.14,<1.0"]
orjson = ["orjson >=3.10,<4.0"]

[project.urls]
homepage = "https://reflex.dev"
Expand Down Expand Up @@ -99,6 +100,7 @@ dev = [
"starlette-admin",
"toml",
"uvicorn",
"orjson >=3.10,<4.0",
]


Expand Down
9 changes: 4 additions & 5 deletions reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ def _setup_state(self) -> None:
ping_timeout=environment.REFLEX_SOCKET_TIMEOUT.get(),
json=SimpleNamespace(
dumps=staticmethod(format.json_dumps),
loads=staticmethod(json.loads),
loads=staticmethod(format.orjson_loads),
),
allow_upgrades=False,
transports=[config.transport],
Expand Down Expand Up @@ -1170,8 +1170,7 @@ def get_compilation_time() -> str:
if not dry_run and not should_compile and backend_dir.exists():
stateful_pages_marker = backend_dir / constants.Dirs.STATEFUL_PAGES
if stateful_pages_marker.exists():
with stateful_pages_marker.open("r") as f:
stateful_pages = json.load(f)
stateful_pages = format.orjson_loads(stateful_pages_marker.read_bytes())
for route in stateful_pages:
console.debug(f"BE Evaluating stateful page: {route}")
self._compile_page(route, save_page=False)
Expand Down Expand Up @@ -1546,7 +1545,7 @@ def _write_stateful_pages_marker(self):
)
stateful_pages_marker.parent.mkdir(parents=True, exist_ok=True)
with stateful_pages_marker.open("w") as f:
json.dump(list(self._stateful_pages), f)
f.write(format.orjson_dumps(list(self._stateful_pages)))

def add_all_routes_endpoint(self):
"""Add an endpoint to the app that returns all the routes."""
Expand Down Expand Up @@ -2172,7 +2171,7 @@ async def on_event(self, sid: str, data: Any):
f" Event data: {fields}"
)
try:
fields = json.loads(fields)
fields = format.orjson_loads(fields)
except json.JSONDecodeError as ex:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

orjson.loads() raises orjson.JSONDecodeError, not json.JSONDecodeError. This exception handler will not catch errors from orjson.

Suggested change
except json.JSONDecodeError as ex:
except (json.JSONDecodeError, Exception) as ex:

Or import orjson at the top and catch both exception types properly.

msg = f"Failed to deserialize event data: {fields}."
raise exceptions.EventDeserializationError(msg) from ex
Expand Down
9 changes: 4 additions & 5 deletions reflex/compiler/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@

from __future__ import annotations

import json
from collections.abc import Iterable, Mapping
from typing import TYPE_CHECKING, Any, Literal

from reflex import constants
from reflex.constants import Hooks
from reflex.constants.state import CAMEL_CASE_MEMO_MARKER
from reflex.utils.format import format_state_name, json_dumps
from reflex.utils.format import format_state_name, json_dumps, orjson_dumps
from reflex.vars.base import VarData

if TYPE_CHECKING:
Expand Down Expand Up @@ -354,11 +353,11 @@ def context_template(
export const DispatchContext = createContext(null);
export const StateContexts = {{{state_contexts_str}}};
export const EventLoopContext = createContext(null);
export const clientStorage = {"{}" if client_storage is None else json.dumps(client_storage)}
export const clientStorage = {"{}" if client_storage is None else orjson_dumps(client_storage)}

{state_str}

export const isDevMode = {json.dumps(is_dev_mode)};
export const isDevMode = {orjson_dumps(is_dev_mode)};

export function UploadFilesProvider({{ children }}) {{
const [filesById, setFilesById] = useState({{}})
Expand Down Expand Up @@ -486,7 +485,7 @@ def package_json_template(
Returns:
Rendered package.json content as string.
"""
return json.dumps({
return orjson_dumps({
"name": "reflex",
"type": "module",
"scripts": scripts,
Expand Down
27 changes: 14 additions & 13 deletions reflex/plugins/shared_tailwind.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def tailwind_config_js_template(
Returns:
The Tailwind config template.
"""
import json
from reflex.utils.format import orjson_dumps

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

# Generate import statements for destructured imports
import_lines = "\n".join([
f"import {{ {imp['name']} }} from {json.dumps(imp['from'])};" for imp in imports
f"import {{ {imp['name']} }} from {orjson_dumps(imp['from'])};"
for imp in imports
])

# Generate plugin imports
plugin_imports = []
for i, plugin in enumerate(plugins, 1):
if isinstance(plugin, Mapping) and "call" not in plugin:
plugin_imports.append(
f"import plugin{i} from {json.dumps(plugin['name'])};"
f"import plugin{i} from {orjson_dumps(plugin['name'])};"
)
elif not isinstance(plugin, Mapping):
plugin_imports.append(f"import plugin{i} from {json.dumps(plugin)};")
plugin_imports.append(f"import plugin{i} from {orjson_dumps(plugin)};")

plugin_imports_lines = "\n".join(plugin_imports)

presets_imports_lines = "\n".join([
f"import preset{i} from {json.dumps(preset)};"
f"import preset{i} from {orjson_dumps(preset)};"
for i, preset in enumerate(presets, 1)
])

Expand All @@ -139,7 +140,7 @@ def tailwind_config_js_template(
if isinstance(plugin, Mapping) and "call" in plugin:
args_part = ""
if "args" in plugin:
args_part = json.dumps(plugin["args"])
args_part = orjson_dumps(plugin["args"])
plugin_list.append(f"{plugin['call']}({args_part})")
else:
plugin_list.append(f"plugin{i}")
Expand All @@ -154,13 +155,13 @@ def tailwind_config_js_template(
{presets_imports_lines}

export default {{
content: {json.dumps(content or default_content)},
theme: {json.dumps(theme or {})},
{f"darkMode: {json.dumps(dark_mode)}," if dark_mode is not None else ""}
{f"corePlugins: {json.dumps(core_plugins)}," if core_plugins is not None else ""}
{f"importants: {json.dumps(important)}," if important is not None else ""}
{f"prefix: {json.dumps(prefix)}," if prefix is not None else ""}
{f"separator: {json.dumps(separator)}," if separator is not None else ""}
content: {orjson_dumps(content or default_content)},
theme: {orjson_dumps(theme or {})},
{f"darkMode: {orjson_dumps(dark_mode)}," if dark_mode is not None else ""}
{f"corePlugins: {orjson_dumps(core_plugins)}," if core_plugins is not None else ""}
{f"importants: {orjson_dumps(important)}," if important is not None else ""}
{f"prefix: {orjson_dumps(prefix)}," if prefix is not None else ""}
{f"separator: {orjson_dumps(separator)}," if separator is not None else ""}
{f"presets: [{', '.join(f'preset{i}' for i in range(1, len(presets) + 1))}]," if presets else ""}
plugins: [{plugin_use_str}]
}};
Expand Down
7 changes: 3 additions & 4 deletions reflex/utils/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import hashlib
import importlib.util
import json
import os
import platform
import re
Expand All @@ -21,6 +20,7 @@
from reflex.environment import environment
from reflex.utils import console, path_ops
from reflex.utils.decorator import once
from reflex.utils.format import orjson_dumps, orjson_loads
from reflex.utils.misc import get_module_path
from reflex.utils.prerequisites import get_web_dir

Expand All @@ -37,11 +37,10 @@ def get_package_json_and_hash(package_json_path: Path) -> tuple[PackageJson, str
Returns:
A tuple containing the content of package.json as a dictionary and its SHA-256 hash.
"""
with package_json_path.open("r") as file:
json_data = json.load(file)
json_data = orjson_loads(package_json_path.read_bytes())

# Calculate the hash
json_string = json.dumps(json_data, sort_keys=True)
json_string = orjson_dumps(json_data, sort_keys=True)
hash_object = hashlib.sha256(json_string.encode())
return (json_data, hash_object.hexdigest())

Expand Down
58 changes: 57 additions & 1 deletion reflex/utils/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ def format_event(event_spec: EventSpec) -> str:
name._js_expr,
(
wrap(
json.dumps(val._js_expr).strip('"').replace("`", "\\`"),
orjson_dumps(val._js_expr).strip('"').replace("`", "\\`"),
"`",
)
if val._var_is_string
Expand Down Expand Up @@ -686,6 +686,62 @@ def json_dumps(obj: Any, **kwargs) -> str:
return json.dumps(obj, **kwargs)


def orjson_dumps(obj: Any, **kwargs) -> str:
"""Serialize obj to a JSON string, using orjson when available.

Translates common json.dumps kwargs (indent, sort_keys) into orjson
option flags. Falls back to stdlib json if orjson is not installed,
an unsupported kwarg is passed, or orjson raises TypeError.

Args:
obj: The object to serialize.
kwargs: Optional keyword arguments (indent, sort_keys).

Returns:
A JSON string.
"""
try:
import orjson # pyright: ignore[reportMissingImports]
except ImportError:
return json.dumps(obj, **kwargs)

option = 0
if kwargs.pop("indent", None):
option |= orjson.OPT_INDENT_2
if kwargs.pop("sort_keys", False):
option |= orjson.OPT_SORT_KEYS
kwargs.pop("ensure_ascii", None) # orjson always produces UTF-8

if kwargs:
# Fall back to stdlib json for unsupported kwargs.
return json.dumps(obj, **kwargs)

try:
return orjson.dumps(obj, option=option or None).decode()
except TypeError:
# Fallback for types orjson can't handle (e.g. int > 64-bit).
return json.dumps(obj)


def orjson_loads(data: str | bytes) -> Any:
"""Deserialize a JSON string or bytes, using orjson when available.

Falls back to stdlib json.loads if orjson is not installed.

Args:
data: JSON string or bytes to deserialize.

Returns:
The deserialized Python object.
"""
try:
import orjson # pyright: ignore[reportMissingImports]
except ImportError:
return json.loads(data)

return orjson.loads(data)


def collect_form_dict_names(form_dict: dict[str, Any]) -> dict[str, Any]:
"""Collapse keys with consecutive suffixes into a single list value.

Expand Down
4 changes: 2 additions & 2 deletions reflex/utils/frontend_skeleton.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This module provides utility functions to initialize the frontend skeleton."""

import json
import random
import re
from pathlib import Path
Expand All @@ -10,6 +9,7 @@
from reflex.config import Config, get_config
from reflex.environment import environment
from reflex.utils import console, path_ops
from reflex.utils.format import orjson_dumps
from reflex.utils.prerequisites import get_project_hash, get_web_dir
from reflex.utils.registry import get_npm_registry

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

return f"export default {json.dumps(react_router_config)};"
return f"export default {orjson_dumps(react_router_config)};"


def _compile_package_json():
Expand Down
8 changes: 3 additions & 5 deletions reflex/utils/path_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import json
import os
import re
import shutil
Expand All @@ -11,6 +10,7 @@

from reflex.config import get_config
from reflex.environment import environment
from reflex.utils.format import orjson_dumps, orjson_loads

# Shorthand for join.
join = os.linesep.join
Expand Down Expand Up @@ -245,15 +245,13 @@ def update_json_file(file_path: str | Path, update_dict: dict[str, int | str]):
# Read the existing json object from the file.
json_object = {}
if fp.stat().st_size:
with fp.open() as f:
json_object = json.load(f)
json_object = orjson_loads(fp.read_bytes())

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

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


def find_replace(directory: str | Path, find: str, replace: str):
Expand Down
8 changes: 4 additions & 4 deletions reflex/utils/prerequisites.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import importlib
import importlib.metadata
import inspect
import json
import random
import re
import sys
Expand All @@ -24,6 +23,7 @@
from reflex.environment import environment
from reflex.utils import console, net, path_ops
from reflex.utils.decorator import once
from reflex.utils.format import orjson_loads
from reflex.utils.misc import get_module_path

if typing.TYPE_CHECKING:
Expand Down Expand Up @@ -111,7 +111,7 @@ def get_or_set_last_reflex_version_check_datetime():
if not reflex_json_file.exists():
return None
# Open and read the file
data = json.loads(reflex_json_file.read_text())
data = orjson_loads(reflex_json_file.read_text())
last_version_check_datetime = data.get("last_version_check_datetime")
if not last_version_check_datetime:
data.update({"last_version_check_datetime": str(datetime.now())})
Expand Down Expand Up @@ -492,7 +492,7 @@ def get_project_hash(raise_on_fail: bool = False) -> int | None:
json_file = get_web_dir() / constants.Reflex.JSON
if not json_file.exists() and not raise_on_fail:
return None
data = json.loads(json_file.read_text())
data = orjson_loads(json_file.read_text())
return data.get("project_hash")


Expand Down Expand Up @@ -558,7 +558,7 @@ def _is_app_compiled_with_same_reflex_version() -> bool:
json_file = get_web_dir() / constants.Reflex.JSON
if not json_file.exists():
return False
app_version = json.loads(json_file.read_text()).get("version")
app_version = orjson_loads(json_file.read_text()).get("version")
return app_version == constants.Reflex.VERSION


Expand Down
Loading
Loading