Skip to content

Commit 5eb70ef

Browse files
authored
Merge branch 'main' into renovate/first-party-cachekit
2 parents d4539be + d9bf881 commit 5eb70ef

10 files changed

Lines changed: 67 additions & 135 deletions

‎.release-please-manifest.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "0.18.0"
2+
".": "0.19.0"
33
}

‎CHANGELOG.md‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
# Changelog
22

3+
## [0.19.0](https://github.com/cachekit-io/cachekit-py/compare/v0.18.0...v0.19.0) (2026-09-22)
4+
5+
6+
### ⚠ BREAKING CHANGES
7+
8+
* **logging:** remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) ([#316](https://github.com/cachekit-io/cachekit-py/issues/316))
9+
* **logging:** `cachekit.logging.UltraOptimizedStructuredLogger` is now `StructuredLogger`; the `cachekit.logging.StructuredRedisLogger` alias is removed; `cachekit.monitoring.pool_monitor.OptimizedPoolMonitor` is now `PoolMonitor`. No deprecation aliases are provided. `get_structured_logger()` is unchanged and remains the supported entry point.
10+
* **logging:** UltraOptimizedStructuredLogger.__init__ no longer accepts mask_sensitive; get_structured_logger() no longer accepts mask_sensitive and now keys _logger_instances on name alone; mask_sensitive_patterns is removed; ProfileConfig.mask_sensitive_data and ProfileConfig.lazy_pii_masking are removed. All were read by nothing and toggled no behavior. Constructors/callers passing them now raise TypeError instead of silently no-op'ing. Same removal shape as L1CacheConfig.namespace_index in v0.18.0 and L1CacheConfig.invalidation_enabled in v0.16.0 (LAB-520).
11+
12+
### Features
13+
14+
* **backend:** bound L1 backfill by the server's remaining freshness (LAB-557) ([#268](https://github.com/cachekit-io/cachekit-py/issues/268)) ([7bd5abf](https://github.com/cachekit-io/cachekit-py/commit/7bd5abf4ba838600b7ae333715a3575dc587143e))
15+
* **concurrency:** free-threaded CPython support — memory-ordering fixes, gil_used=false, CI lane (LAB-511) ([#265](https://github.com/cachekit-io/cachekit-py/issues/265)) ([bda770b](https://github.com/cachekit-io/cachekit-py/commit/bda770bce822d9a6eff98e555c5f6fd92e509a9c))
16+
17+
18+
### Bug Fixes
19+
20+
* **ci:** fail loudly on attestation lookup failure; decide the codecov pair (LAB-2528) ([#270](https://github.com/cachekit-io/cachekit-py/issues/270)) ([2a8b941](https://github.com/cachekit-io/cachekit-py/commit/2a8b941043e8bc3d9a143428495e13226aed4901))
21+
* **ci:** make the Atheris fuzz job capable of failing + repair its dead targets (LAB-1140) ([#269](https://github.com/cachekit-io/cachekit-py/issues/269)) ([6ab0c28](https://github.com/cachekit-io/cachekit-py/commit/6ab0c289f8fe1e8891c30b94faed7a3501809e16))
22+
* **decorators:** async get hits record serializer/size/hit like the sync path (LAB-3765) ([#297](https://github.com/cachekit-io/cachekit-py/issues/297)) ([9b96fd2](https://github.com/cachekit-io/cachekit-py/commit/9b96fd2b90364bd36ef3268bca6ff4df2daafe40))
23+
* **decorators:** async lock double-check L2 hits record get telemetry (LAB-3769) ([#303](https://github.com/cachekit-io/cachekit-py/issues/303)) ([683b1c7](https://github.com/cachekit-io/cachekit-py/commit/683b1c7cc178948c922683551ae41e6c77beef24))
24+
* **decorators:** honour set_default_backend() when called after decoration (LAB-4457) ([#313](https://github.com/cachekit-io/cachekit-py/issues/313)) ([d20204c](https://github.com/cachekit-io/cachekit-py/commit/d20204c4f16270e75ffa69e601995bdcc51f700c))
25+
* **file:** guard eviction unlink against a concurrent rename (LAB-2685) ([#285](https://github.com/cachekit-io/cachekit-py/issues/285)) ([e917a57](https://github.com/cachekit-io/cachekit-py/commit/e917a57c4e6065eb161da8011a26d6805d49c327))
26+
* **file:** write every byte or fail; evict a payload that shrank under read (LAB-2682) ([#272](https://github.com/cachekit-io/cachekit-py/issues/272)) ([068adb2](https://github.com/cachekit-io/cachekit-py/commit/068adb276c2b1f552b527e4ba91fc2904cd75be5))
27+
* **logging:** redact raw cache keys on all log paths (LAB-304) ([#264](https://github.com/cachekit-io/cachekit-py/issues/264)) ([81f97fb](https://github.com/cachekit-io/cachekit-py/commit/81f97fb76245e3924bf91b61996e1b4e7639f702))
28+
* **logging:** sanitise error kwarg at the structured cache-operation sinks (LAB-3666) ([#301](https://github.com/cachekit-io/cachekit-py/issues/301)) ([d27ec29](https://github.com/cachekit-io/cachekit-py/commit/d27ec29e0dbdfdf64e253a6fb5665b4d30639869))
29+
* **redis:** stop lock waiters pinning executor threads (LAB-3596) ([#290](https://github.com/cachekit-io/cachekit-py/issues/290)) ([ddbeb91](https://github.com/cachekit-io/cachekit-py/commit/ddbeb910a9de7932ed93ebbcad4c8435278831e2))
30+
* **serializers:** bound forged-entry error echoes; retire columnar dead code (LAB-3131) ([#289](https://github.com/cachekit-io/cachekit-py/issues/289)) ([10a1049](https://github.com/cachekit-io/cachekit-py/commit/10a10498d406bce7b4fd8dff6cb2e88e80f9b0a2))
31+
* **serializers:** bound untrusted msgpack decode depth and header allocation (LAB-2503) ([#276](https://github.com/cachekit-io/cachekit-py/issues/276)) ([f7c087d](https://github.com/cachekit-io/cachekit-py/commit/f7c087dfbd7972bd711f32054949c1b739e00146))
32+
* **serializers:** fail closed on unverified DataFrame/Series envelopes (LAB-2736) ([#304](https://github.com/cachekit-io/cachekit-py/issues/304)) ([60da54f](https://github.com/cachekit-io/cachekit-py/commit/60da54fd614717735f831c27831ed4cfe758ea35))
33+
* **serializers:** take the format from the envelope, not the header; drop the header-gated fall-through (LAB-2736) ([#308](https://github.com/cachekit-io/cachekit-py/issues/308)) ([69db1c5](https://github.com/cachekit-io/cachekit-py/commit/69db1c5ef768195471c0e971965c73e77d2da9cd))
34+
* **serializers:** type ByteStorage.retrieve failures and collapse duplicated columnar decode (LAB-2736) ([#287](https://github.com/cachekit-io/cachekit-py/issues/287)) ([0aa78ca](https://github.com/cachekit-io/cachekit-py/commit/0aa78ca8dc0749492e6078cdcfe2330743bb202f))
35+
36+
37+
### Performance Improvements
38+
39+
* **decorators:** backfill L1 on sync L2 hits; size stats by envelope length (LAB-348) ([#294](https://github.com/cachekit-io/cachekit-py/issues/294)) ([c019b26](https://github.com/cachekit-io/cachekit-py/commit/c019b2659eb964408facb6d7191b85a26177342a))
40+
41+
42+
### Code Refactoring
43+
44+
* **logging:** remove dead compat surface and Redis branding from StructuredLogger (LAB-4621) ([#316](https://github.com/cachekit-io/cachekit-py/issues/316)) ([98d616e](https://github.com/cachekit-io/cachekit-py/commit/98d616e6e5f5aec79ba7fe285909f662e8722c00))
45+
* **logging:** remove dead PII-masking knobs (LAB-3797) ([#300](https://github.com/cachekit-io/cachekit-py/issues/300)) ([705640b](https://github.com/cachekit-io/cachekit-py/commit/705640bde14b73c249ed6d8b30733eb46752a9a2))
46+
* **logging:** rename UltraOptimizedStructuredLogger to StructuredLogger, OptimizedPoolMonitor to PoolMonitor (LAB-4617) ([#314](https://github.com/cachekit-io/cachekit-py/issues/314)) ([2f7c979](https://github.com/cachekit-io/cachekit-py/commit/2f7c9799d5f9aa46db4134febb6710b72c15d457))
47+
348
## [0.18.0](https://github.com/cachekit-io/cachekit-py/compare/v0.17.1...v0.18.0) (2026-09-03)
449

550

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "maturin"
44

55
[project]
66
name = "cachekit"
7-
version = "0.18.0"
7+
version = "0.19.0"
88
description = "Backend-agnostic caching for Python — intent-based decorators with circuit breaker, distributed locking, Prometheus metrics, and optional zero-knowledge AES-256-GCM encryption, on a Rust-powered core. Zero-config L1 in-memory; scales to Redis, Memcached, File, or CachekitIO."
99
readme = "README.md"
1010
license = {text = "MIT"}

‎rust/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cachekit-rs"
3-
version = "0.18.0"
3+
version = "0.19.0"
44
edition = "2021"
55
authors = ["cachekit Contributors"]
66
description = "High-performance storage engine for caching with compression and encryption"

‎src/cachekit/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def custom_function():
6868
```
6969
"""
7070

71-
__version__ = "0.18.0"
71+
__version__ = "0.19.0"
7272

7373
from collections.abc import Callable
7474
from typing import Any, TypeVar

‎src/cachekit/logging.py‎

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class AsyncLogWriter(threading.Thread):
113113
"""Background thread for async log writing."""
114114

115115
def __init__(self, buffer: LockFreeRingBuffer):
116-
super().__init__(daemon=True, name="RedisCache-LogWriter")
116+
super().__init__(daemon=True, name="cachekit-LogWriter")
117117
self.buffer = buffer
118118
self.running = True
119119
self._stop_event = threading.Event()
@@ -414,70 +414,12 @@ def _get_context(self) -> dict[str, Any]:
414414
context["correlation_id"] = self._context.correlation_id
415415
return context
416416

417-
# Compatibility methods for tests
418-
def redis_operation_failed(self, operation: str, key: str, error: Exception, **kwargs):
419-
"""Log Redis operation failure. ``cache_operation`` renders the error key-free (CWE-532)."""
420-
self.cache_operation(operation, key, error=error, error_type=type(error).__name__, **kwargs)
421-
422-
def cache_hit(self, key: str, **kwargs):
423-
"""Log cache hit."""
424-
self.cache_operation("get", key, hit=True, **kwargs)
425-
426-
def cache_miss(self, key: str, **kwargs):
427-
"""Log cache miss."""
428-
self.cache_operation("get", key, hit=False, **kwargs)
429-
430-
def cache_stored(self, key: str, **kwargs):
431-
"""Log cache store operation."""
432-
self.cache_operation("set", key, **kwargs)
433-
434-
def serialization_fallback(self, from_serializer: str, to_serializer: str, reason: str, **kwargs):
435-
"""Log serialization fallback event."""
436-
437-
# Log the fallback
438-
self.warning(
439-
f"Serialization fallback: {from_serializer} -> {to_serializer}",
440-
from_serializer=from_serializer,
441-
to_serializer=to_serializer,
442-
reason=reason,
443-
**kwargs,
444-
)
445-
446-
def create_span(self, name: str, **kwargs):
447-
"""Create a simple tracing span context manager."""
448-
return SimpleSpan(self, name, **kwargs)
449-
450417
def __del__(self):
451418
"""Cleanup on deletion."""
452419
if hasattr(self, "writer"):
453420
self.writer.stop()
454421

455422

456-
class SimpleSpan:
457-
"""Simple span implementation for tracing integration."""
458-
459-
def __init__(self, logger: StructuredLogger, name: str, **kwargs):
460-
self.logger = logger
461-
self.name = name
462-
self.kwargs = kwargs
463-
self.start_time = None
464-
465-
def __enter__(self):
466-
"""Start the span."""
467-
self.start_time = time.time()
468-
# Generate a simple trace ID if not set
469-
if not hasattr(self.logger._context, "trace_id") or not self.logger._context.trace_id:
470-
trace_id = f"span-{int(time.time() * 1000000)}"
471-
self.logger.set_trace_id(trace_id)
472-
return self
473-
474-
def __exit__(self, exc_type, exc_val, exc_tb):
475-
"""End the span."""
476-
if self.start_time:
477-
duration = time.time() - self.start_time
478-
self.logger.debug(f"Span completed: {self.name}", span_name=self.name, duration_ms=duration * 1000, **self.kwargs)
479-
480-
481423
# Global logger instances cache
482424
_logger_instances: dict[str, StructuredLogger] = {}
483425
_logger_lock = threading.Lock()

‎tests/unit/test_error_path_key_redaction.py‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,9 @@ async def test_async_get_failure_redacts_key_in_exception_text(self, caplog: pyt
285285

286286

287287
class TestStructuredLoggerCacheOperationRedaction:
288-
"""``StructuredLogger.cache_operation`` is a direct sink.
288+
"""``StructuredLogger.cache_operation`` is the only structured sink on the class.
289289
290-
``cache_hit``/``cache_miss``/``cache_stored`` all funnel through it, so this
291-
one method is the whole surface. It must apply the *same* pass-through policy
290+
It must apply the *same* pass-through policy
292291
as the orchestrator sink: a value that arrives already redacted, or is a known
293292
sentinel, is emitted verbatim. Hashing it a second time would mint a different
294293
digest for the same key and break correlation between the two sinks
@@ -409,11 +408,13 @@ def test_arbitrary_exception_reduced_to_type_name(self) -> None:
409408
class TestErrorKwargSanitisedAtSink:
410409
"""An ``error`` kwarg is sanitised once, at each structured sink (CWE-532, defence in depth).
411410
412-
Three in-tree callers, three shapes: ``handle_cache_error`` pre-renders to a str,
413-
``redis_operation_failed`` passes the exception object, and the circuit-breaker path
414-
in ``wrapper.py`` passes a str literal. The sink must emit all three key-free, and a
415-
str must pass through untouched (re-sanitising one would emit the literal ``"str"``).
416-
The assertion runs over the whole payload, not the ``error`` field alone.
411+
Two in-tree callers, both str: ``handle_cache_error`` pre-renders via
412+
``redact_error_for_log``, and the circuit-breaker path in ``wrapper.py`` passes a str
413+
literal. The raw-exception shape is kept as defence in depth — ``cache_operation``
414+
takes ``**kwargs``, so nothing stops a future caller handing it an exception object,
415+
and the sink must render that key-free too. A str must pass through untouched
416+
(re-sanitising one would emit the literal ``"str"``). The assertion runs over the
417+
whole payload, not the ``error`` field alone.
417418
"""
418419

419420
@pytest.mark.parametrize(("error", "rendered"), ERROR_KWARGS)

‎tests/unit/test_structured_logging.py‎

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import pytest
1010

11-
from cachekit.backends.errors import BackendError, BackendErrorType
1211
from cachekit.logging import (
1312
JsonFormatter,
1413
StructuredLogger,
@@ -116,61 +115,6 @@ def test_cache_operation_error_logging(self, mock_log, logger):
116115
assert extra["error"] == "Connection timeout"
117116
assert extra["error_type"] == "TimeoutError"
118117

119-
@pytest.mark.parametrize(
120-
("error", "rendered", "error_type"),
121-
[
122-
(ValueError("Test error"), "ValueError", "ValueError"),
123-
(BackendError("Redis timeout", error_type=BackendErrorType.TIMEOUT), "BackendError(timeout)", "BackendError"),
124-
],
125-
ids=["provider_exception", "backend_error"],
126-
)
127-
@patch("cachekit.logging.logging.Logger.log")
128-
def test_redis_operation_failed_override(self, mock_log, logger, error, rendered, error_type):
129-
"""redis_operation_failed emits a key-free error representation (CWE-532).
130-
131-
A non-BackendError's str() has unknown provenance and may echo the raw cache
132-
key, so only its type name reaches the log; a BackendError renders as
133-
``TypeName(error_type)``. error_type still carries the Python type.
134-
"""
135-
logger.redis_operation_failed("get", "test_key", error)
136-
137-
mock_log.assert_called_once()
138-
extra = mock_log.call_args[1]["extra"]["structured"]
139-
assert extra["operation"] == "get"
140-
assert extra["error"] == rendered # never the raw message
141-
assert extra["error_type"] == error_type
142-
143-
@patch("cachekit.logging.logging.Logger.log")
144-
def test_cache_hit_override(self, mock_log, logger):
145-
"""Test cache_hit override."""
146-
logger.cache_hit("test_key", source="memory")
147-
148-
mock_log.assert_called_once()
149-
extra = mock_log.call_args[1]["extra"]["structured"]
150-
assert extra["operation"] == "get"
151-
assert extra["hit"] is True
152-
assert extra["source"] == "memory"
153-
154-
@patch("cachekit.logging.logging.Logger.log")
155-
def test_cache_miss_override(self, mock_log, logger):
156-
"""Test cache_miss override."""
157-
logger.cache_miss("test_key")
158-
159-
mock_log.assert_called_once()
160-
extra = mock_log.call_args[1]["extra"]["structured"]
161-
assert extra["operation"] == "get"
162-
assert extra["hit"] is False
163-
164-
@patch("cachekit.logging.logging.Logger.log")
165-
def test_cache_stored_override(self, mock_log, logger):
166-
"""Test cache_stored override."""
167-
logger.cache_stored("test_key", ttl=300)
168-
169-
mock_log.assert_called_once()
170-
extra = mock_log.call_args[1]["extra"]["structured"]
171-
assert extra["operation"] == "set"
172-
assert extra["ttl"] == 300
173-
174118
def test_thread_safety(self, logger):
175119
"""Test thread-local context isolation."""
176120
results = {}

‎uv.lock‎

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)