Skip to content
Merged
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
1 change: 1 addition & 0 deletions docs/changelog/204.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Write interpreter cache entries as compact JSON to keep their size proportional to the interpreter response.
7 changes: 6 additions & 1 deletion src/python_discovery/_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ def _read_entry(self) -> tuple[dict | None, bool]:
):
_LOGGER.warning("ignoring untrusted cache entry at %s", self._file)
return None, True
# A 1 MiB query can expand to 12 MiB when supplementary Unicode is escaped.
if status.st_size > 16 << 20:
_LOGGER.warning("ignoring oversized cache entry at %s", self._file)
return None, True
return json.load(file), False

@staticmethod
Expand All @@ -119,7 +123,8 @@ def write(self, content: dict) -> None:
mode="w", encoding="utf-8", dir=self._folder, prefix=f".{self._key}.", delete=False
) as file:
temporary = Path(file.name)
json.dump(content, file, sort_keys=True, indent=2)
# Compact encoding avoids indentation growth and the streaming encoder's recursion limit.
file.write(json.dumps(content, sort_keys=True))
temporary.replace(self._file)
except OSError:
_LOGGER.debug("failed to write %s", self._file, exc_info=True)
Expand Down
35 changes: 35 additions & 0 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import json
import os
import stat
import sys
Expand Down Expand Up @@ -36,6 +37,40 @@ def test_disk_content_store_read_missing_file(tmp_path: Path) -> None:
assert store.read() is None


def test_disk_content_store_reads_near_limit_escaped_json(tmp_path: Path) -> None:
content = {"padding": "😀" * ((1 << 20) - 4_096)}
store = DiskContentStore(tmp_path, "test")
store.write(content)
assert store.read() == content


def test_disk_content_store_discards_oversized_entry(tmp_path: Path) -> None:
entry = tmp_path / "test.json"
entry.write_text(json.dumps({"padding": "x" * (16 << 20)}), encoding="utf-8")
store = DiskContentStore(tmp_path, "test")
assert store.read() is None
assert not entry.exists()


@pytest.mark.parametrize(
"source",
[
pytest.param(json.dumps("x" * 398), id="flat"),
pytest.param("[" * 200 + "]" * 200, id="nested"),
],
)
def test_disk_content_store_write_size_tracks_input(tmp_path: Path, source: str) -> None:
content = {"padding": json.loads(source)}
DiskContentStore(tmp_path, "test").write(content)
assert (tmp_path / "test.json").stat().st_size < 2 * len(json.dumps(content))


def test_disk_content_store_write_deep_json(tmp_path: Path) -> None:
depth = 2_000 if sys.version_info >= (3, 15) else 900
DiskContentStore(tmp_path, "test").write({"padding": json.loads("[" * depth + "]" * depth)})
assert (tmp_path / "test.json").stat().st_size < 10_000


def test_disk_content_store_remove(tmp_path: Path) -> None:
store = DiskContentStore(tmp_path, "test")
store.write({"key": "value"})
Expand Down