Trim cached orjson fragments kept by non core containers (#181511)

This commit is contained in:
Erik Montnemery
2026-09-08 15:47:32 +02:00
committed by GitHub
parent 1f889c45a3
commit 5c16d82936
6 changed files with 38 additions and 14 deletions
@@ -15,7 +15,7 @@ from homeassistant.const import CONF_FILENAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import collection, storage
from homeassistant.helpers.json import json_bytes, json_fragment
from homeassistant.helpers.json import cached_json_fragment, json_fragment
from homeassistant.util.yaml import Secrets, load_yaml_dict
from .const import (
@@ -182,7 +182,7 @@ class LovelaceStorage(LovelaceConfig):
"""Build JSON representation of the config."""
if self._data is None or self._data["config"] is None:
raise ConfigNotFound
self._json_config = json_fragment(json_bytes(self._data["config"]))
self._json_config = cached_json_fragment(self._data["config"])
return self._json_config
@@ -260,7 +260,7 @@ class LovelaceYAML(LovelaceConfig):
except FileNotFoundError:
raise ConfigNotFound from None
json = json_fragment(json_bytes(config))
json = cached_json_fragment(config)
self._cache = (config, time.time(), json)
return is_updated, config, json
@@ -62,8 +62,8 @@ from homeassistant.helpers.event import (
from homeassistant.helpers.json import (
JSON_DUMP,
ExtendedJSONEncoder,
cached_json_bytes,
find_paths_unserializable_data,
json_bytes,
json_fragment,
)
from homeassistant.helpers.service import (
@@ -541,7 +541,7 @@ async def _async_get_all_condition_descriptions_json(hass: HomeAssistant) -> byt
# If the descriptions are the same, return the cached JSON payload
if cached_descriptions is descriptions:
return cast(bytes, cached_json_payload)
json_payload = json_bytes(
json_payload = cached_json_bytes(
{
condition: description
for condition, description in descriptions.items()
@@ -588,7 +588,7 @@ async def _async_get_all_service_descriptions_json(hass: HomeAssistant) -> bytes
# If the descriptions are the same, return the cached JSON payload
if cached_descriptions is descriptions:
return cast(bytes, cached_json_payload)
json_payload = json_bytes(descriptions)
json_payload = cached_json_bytes(descriptions)
hass.data[ALL_SERVICE_DESCRIPTIONS_JSON_CACHE] = (descriptions, json_payload)
return json_payload
@@ -613,7 +613,7 @@ async def _async_get_all_trigger_descriptions_json(hass: HomeAssistant) -> bytes
# If the descriptions are the same, return the cached JSON payload
if cached_descriptions is descriptions:
return cast(bytes, cached_json_payload)
json_payload = json_bytes(
json_payload = cached_json_bytes(
{
trigger: description
for trigger, description in descriptions.items()
+7 -3
View File
@@ -66,7 +66,11 @@ from .helpers.event import (
async_call_later,
)
from .helpers.frame import ReportBehavior, report_usage
from .helpers.json import json_bytes, json_bytes_sorted, json_fragment
from .helpers.json import (
cached_json_fragment,
cached_json_fragment_sorted,
json_fragment,
)
from .helpers.typing import (
UNDEFINED,
ConfigType,
@@ -682,7 +686,7 @@ class ConfigEntry[_DataT = Any]:
),
"num_subentries": len(self.subentries),
}
return json_fragment(json_bytes(json_repr))
return cached_json_fragment(json_repr)
def clear_storage_cache(self) -> None:
"""Clear cached properties that are included in as_storage_fragment."""
@@ -691,7 +695,7 @@ class ConfigEntry[_DataT = Any]:
@cached_property
def as_storage_fragment(self) -> json_fragment:
"""Return a storage fragment for this entry."""
return json_fragment(json_bytes_sorted(self.as_dict()))
return cached_json_fragment_sorted(self.as_dict())
async def async_setup(
self,
+10
View File
@@ -140,6 +140,16 @@ def cached_json_fragment(data: Any) -> orjson.Fragment:
return orjson.Fragment(b"".join((json_bytes(data), b"")))
def cached_json_fragment_sorted(data: Any) -> orjson.Fragment:
"""Return a json fragment with sorted keys, right-sized for long-term caching.
The sorted-key variant of cached_json_fragment (see json_bytes_sorted).
"""
# The empty second join item is load-bearing: it forces a copy into a
# right-sized buffer; a single-item join returns the input unchanged.
return orjson.Fragment(b"".join((json_bytes_sorted(data), b"")))
def json_dumps(data: Any) -> str:
r"""Dump json string.
+2 -2
View File
@@ -45,7 +45,7 @@ from .generated.mqtt import MQTT
from .generated.ssdp import SSDP
from .generated.usb import USB
from .generated.zeroconf import HOMEKIT, ZEROCONF
from .helpers.json import json_bytes, json_fragment
from .helpers.json import cached_json_fragment, json_fragment
from .helpers.typing import UNDEFINED, UndefinedType
from .util.async_ import create_eager_task
from .util.hass_dict import HassKey
@@ -798,7 +798,7 @@ class Integration:
@cached_property
def manifest_json_fragment(self) -> json_fragment:
"""Return manifest as a JSON fragment."""
return json_fragment(json_bytes(self.manifest))
return cached_json_fragment(self.manifest)
@cached_property
def name(self) -> str:
+12 -2
View File
@@ -21,6 +21,7 @@ from homeassistant.helpers.json import (
JSONEncoder as DefaultHASSJSONEncoder,
cached_json_bytes,
cached_json_fragment,
cached_json_fragment_sorted,
find_paths_unserializable_data,
json_bytes,
json_bytes_sorted,
@@ -236,10 +237,19 @@ def test_cached_json_bytes() -> None:
)
def test_cached_json_fragment_sorted() -> None:
"""Test cached_json_fragment_sorted serializes with sorted keys."""
data = {"c": 3, "a": 1, "b": 2}
fragment = cached_json_fragment_sorted(data)
assert isinstance(fragment, json_fragment)
assert json_dumps([fragment]) == '[{"a":1,"b":2,"c":3}]'
@pytest.mark.parametrize(
"cached_serializer",
[cached_json_bytes, cached_json_fragment],
ids=["cached_json_bytes", "cached_json_fragment"],
[cached_json_bytes, cached_json_fragment, cached_json_fragment_sorted],
ids=["cached_json_bytes", "cached_json_fragment", "cached_json_fragment_sorted"],
)
def test_cached_json_helpers_trim_buffer(
cached_serializer: Callable[[Any], object],