From 089b1add1d132a73c9bfd505f5e10df1a7f6aeb8 Mon Sep 17 00:00:00 2001 From: Roman Kozlov Date: Wed, 31 Jan 2024 19:39:37 +0300 Subject: [PATCH] HH-205308 fix recursion in json encoder --- frontik/json_builder.py | 44 ++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/frontik/json_builder.py b/frontik/json_builder.py index b536cad69..bdf940cea 100644 --- a/frontik/json_builder.py +++ b/frontik/json_builder.py @@ -1,31 +1,53 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Callable, Optional, Union +from typing import Any, Callable, Optional, Union import orjson from pydantic import BaseModel from tornado.concurrent import Future -if TYPE_CHECKING: - from typing import Any - handler_logger = logging.getLogger('handler') - FrontikJsonDecodeError = orjson.JSONDecodeError -def _encode_value(value: Any) -> Any: +def _deep_encode_value(value: Any) -> Any: + """ + This method partially duplicates ``_encode_value()``. + + It is only used by ``JsonBuilder.to_dict()`` + which is only used by ``JsonProducer.get_jinja_context()``. + """ + if isinstance(value, dict): - return {k: _encode_value(v) for k, v in value.items()} + return {k: _deep_encode_value(v) for k, v in value.items()} elif isinstance(value, (set, frozenset, list, tuple)): - return [_encode_value(v1) for v1 in value] + return [_deep_encode_value(v1) for v1 in value] + + elif isinstance(value, Future): + if value.done() and value.exception() is None: + return _deep_encode_value(value.result()) + + return None + + elif isinstance(value, BaseModel): + return _deep_encode_value(value.model_dump()) + + elif hasattr(value, 'to_dict'): + return _deep_encode_value(value.to_dict()) + + return value + + +def _encode_value(value: Any) -> Any: + if isinstance(value, (set, frozenset)): + return list(value) elif isinstance(value, Future): if value.done() and value.exception() is None: - return _encode_value(value.result()) + return value.result() return None @@ -35,7 +57,7 @@ def _encode_value(value: Any) -> Any: elif hasattr(value, 'to_dict'): return value.to_dict() - return value + raise TypeError def json_encode(obj: Any, default: Callable = _encode_value) -> str: @@ -76,7 +98,7 @@ def replace(self, *args: Any, **kwargs: Any) -> None: def to_dict(self) -> dict: """Return plain dict from all data appended to JsonBuilder""" - return _encode_value(self._concat_chunks()) + return _deep_encode_value(self._concat_chunks()) def _concat_chunks(self) -> dict: result = {}