From ec0e60033b508f520cfc0d07407867bc18c98fbd Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Fri, 31 Jul 2026 23:32:14 +0200 Subject: [PATCH] perf: use the orjson codec for the permission deep copy (#27807) `get_permissions` deep-copies the default permission tree with a `json.loads(json.dumps(...))` round trip before merging group permissions into it. It runs on signin, signup, the permissions endpoint, OAuth, and the chat-completion middleware. It now goes through `JSONCodec`, which selects orjson when `ENABLE_ORJSON` is set. The intermediate string never leaves the expression, so neither the escaping nor the separator differences between the two backends are observable; only the resulting object is used. `default_permissions` always originates from `Config.get('user.permissions')`, a SQLAlchemy `JSON` column, so the tree is JSON-native by construction and the round trip is exact. Note for anyone tempted to simplify this to `copy.deepcopy`: measured on the real `DEFAULT_USER_PERMISSIONS` shape over 200k iterations, `deepcopy` takes 3.51s against 1.45s for the stdlib round trip and 0.40s for orjson. The round trip is the fast option, not a workaround. With `ENABLE_ORJSON` unset, which is the default, `JSONCodec` is stdlib `json` and this call site behaves exactly as before. --- backend/open_webui/utils/access_control/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/open_webui/utils/access_control/__init__.py b/backend/open_webui/utils/access_control/__init__.py index a549dae0a0..2f8c89a1d6 100644 --- a/backend/open_webui/utils/access_control/__init__.py +++ b/backend/open_webui/utils/access_control/__init__.py @@ -1,4 +1,3 @@ -import json from typing import Any from open_webui.config import DEFAULT_USER_PERMISSIONS @@ -12,6 +11,7 @@ from open_webui.models.access_grants import ( ) from open_webui.models.groups import Groups from open_webui.models.users import UserModel +from open_webui.utils.json_codec import JSONCodec from sqlalchemy.ext.asyncio import AsyncSession @@ -57,7 +57,7 @@ async def get_permissions( user_groups = await Groups.get_groups_by_member_id(user_id, db=db) # Deep copy default permissions to avoid modifying the original dict - permissions = json.loads(json.dumps(default_permissions)) + permissions = JSONCodec.loads(JSONCodec.dumps(default_permissions)) # Combine permissions from all user groups for group in user_groups: