storage/sqlite: fix a bug when storing UTF-8 values
- ID
33d832a- date
2026-11-06 21:57:32+00:00- author
Alex Chan <alexc@tailscale.com>- parent
0246dc9- message
storage/sqlite: fix a bug when storing UTF-8 values Encoding the JSON as ASCII means only ASCII values can be stored, which isn't the intention.- changed files
2 files, 49 additions, 30 deletions
Changed files
src/funkycache/storage.py (6794 → 6793)
diff --git a/src/funkycache/storage.py b/src/funkycache/storage.py
index a9f009c..abfad40 100644
--- a/src/funkycache/storage.py
+++ b/src/funkycache/storage.py
@@ -112,7 +112,7 @@ class TypedCacheStorage[V](CacheStorage[V]):
Throws an exception if you overwrite an existing key with
a different value.
"""
- json_str = self.adapter.dump_json(value).decode("ascii")
+ json_str = self.adapter.dump_json(value).decode("utf8")
self._set_str(key, json_str)
@abstractmethod
tests/test_storage.py (4073 → 4736)
diff --git a/tests/test_storage.py b/tests/test_storage.py
index c448a19..0789c9e 100644
--- a/tests/test_storage.py
+++ b/tests/test_storage.py
@@ -3,7 +3,8 @@ Tests for backend storage implementations.
"""
from abc import ABC, abstractmethod
-from collections.abc import Iterator, Mapping
+from collections.abc import Generator, Mapping
+from contextlib import contextmanager
from pathlib import Path
import sqlite3
from typing import Any
@@ -28,10 +29,12 @@ class CacheStorageTestSuite(ABC):
"""
@abstractmethod
- @pytest.fixture
- def storage(self) -> Iterator[CacheStorage[int]]:
+ @contextmanager
+ def create_storage[V](
+ self, tmp_path: Path, data_type: Any
+ ) -> Generator[CacheStorage[V]]:
"""
- Return an empty cache storage backend that holds integer values.
+ Return an empty cache storage backend.
"""
@pytest.mark.parametrize(
@@ -45,31 +48,41 @@ class CacheStorageTestSuite(ABC):
pytest.param({"n": Path("10.txt")}, id="path"),
],
)
- def test_cache(self, storage: CacheStorage[int], key: Mapping[str, Any]) -> None:
+ def test_cache(self, key: Mapping[str, Any], tmp_path: Path) -> None:
"""
Test the basic properties of a cache.
"""
- fib10 = 55
- fib20 = 6765
+ with self.create_storage(tmp_path, data_type=int) as storage:
+ fib10 = 55
+ fib20 = 6765
- # Test empty storage
- with pytest.raises(KeyError):
- storage.get(key)
+ # Test empty storage
+ with pytest.raises(KeyError):
+ storage.get(key)
- # Store and retrieve a value
- storage.set(key, value=fib10)
- assert storage.get(key) == fib10
+ # Store and retrieve a value
+ storage.set(key, value=fib10)
+ assert storage.get(key) == fib10
- # Overwriting an existing value is an error.
- with pytest.raises(CannotOverwriteValueException) as exc:
- storage.set(key, value=fib20)
- assert repr(key) in exc.value.args[0]
+ # Overwriting an existing value is an error.
+ with pytest.raises(CannotOverwriteValueException) as exc:
+ storage.set(key, value=fib20)
+ assert repr(key) in exc.value.args[0]
- assert storage.get(key) == fib10
+ assert storage.get(key) == fib10
- # Writing a key with the same value as the already-stored
- # value is allowed.
- storage.set(key, value=fib10)
+ # Writing a key with the same value as the already-stored
+ # value is allowed.
+ storage.set(key, value=fib10)
+
+ def test_can_store_non_ascii_values(self, tmp_path: Path) -> None:
+ """
+ The storage can hold values which don't encode as ASCII.
+ """
+ with self.create_storage(tmp_path, data_type=str) as storage:
+ key = {"type": "place"}
+ storage.set(key, value="café")
+ assert storage.get(key) == "café"
class TestMemoryCacheStorage(CacheStorageTestSuite):
@@ -77,12 +90,14 @@ class TestMemoryCacheStorage(CacheStorageTestSuite):
Tests for MemoryCacheStorage.
"""
- @pytest.fixture
- def storage(self) -> Iterator[CacheStorage[int]]:
+ @contextmanager
+ def create_storage[V](
+ self, tmp_path: Path, data_type: Any
+ ) -> Generator[CacheStorage[V]]:
"""
- Return an empty in-memory cache that returns integer values.
+ Return an empty cache storage backend.
"""
- yield MemoryCacheStorage[int]()
+ yield MemoryCacheStorage[V]()
class TestSQLiteCacheStorage(CacheStorageTestSuite):
@@ -90,12 +105,16 @@ class TestSQLiteCacheStorage(CacheStorageTestSuite):
Tests for SQLiteCacheStorage.
"""
- @pytest.fixture
- def storage(self) -> Iterator[CacheStorage[int]]:
+ @contextmanager
+ def create_storage[V](
+ self, tmp_path: Path, data_type: Any
+ ) -> Generator[CacheStorage[V]]:
"""
- Return an empty in-memory cache that returns integer values.
+ Return an empty cache storage backend.
"""
- yield SQLiteCacheStorage[int](":memory:", data_type=int)
+ storage = SQLiteCacheStorage(":memory:", data_type=data_type)
+ yield storage
+ storage.close()
def test_operations_persist(self, tmp_path: Path) -> None:
"""