From 7377add8be41dad37005cb6cfe6d275ee64b1f33 Mon Sep 17 00:00:00 2001 From: Dima Gerasimov Date: Wed, 12 Aug 2026 15:47:27 +0100 Subject: [PATCH] core: retire legacy serializer from the active package The legacy `NTBinder` implementation is no longer used and performs worse than the current serializer. Remove it from the installed package and benchmark matrix while preserving the source under `misc/legacy` for historical reference and future schema-fingerprint and positional-encoding ideas. --- doc/serialization.org | 5 +++++ misc/legacy/README.md | 16 ++++++++++++++++ {src/cachew => misc/legacy}/legacy.py | 0 pyproject.toml | 4 ++-- src/cachew/pytest.py | 20 -------------------- src/cachew/tests/benchmarks/common.py | 19 ------------------- 6 files changed, 23 insertions(+), 41 deletions(-) create mode 100644 misc/legacy/README.md rename {src/cachew => misc/legacy}/legacy.py (100%) delete mode 100644 src/cachew/pytest.py diff --git a/doc/serialization.org b/doc/serialization.org index 4c0065a..246d491 100644 --- a/doc/serialization.org +++ b/doc/serialization.org @@ -1,3 +1,8 @@ +#+begin_quote +Historical note: this document describes the structural SQLite serializer retired from the installed package in 2026. +Its frozen source is preserved in [[file:../misc/legacy/README.md][misc/legacy]], while current Cachew marshals each item into a single serialized payload. +#+end_quote + Cachew works kinda like =functools.lru_cache=, but it also works in-between program runs. For that, it needs to somehow persist the objects on the disk (unlike =lru_cache= which just keeps references to the objects already in process memory). diff --git a/misc/legacy/README.md b/misc/legacy/README.md new file mode 100644 index 0000000..ca27709 --- /dev/null +++ b/misc/legacy/README.md @@ -0,0 +1,16 @@ +# Legacy structural SQLite serializer + +This directory preserves Cachew's former `NTBinder` implementation for design reference. +It flattened annotated Python values into schema-derived SQLite columns. +Cachew replaced it with single-column serialized payloads in 2023 and retired it from the installed package, tests, and active benchmark matrix in 2026. + +This is frozen historical source, not a supported backend or public API. +Its imports and embedded tests reflect its former `cachew.legacy` location and may no longer run. +Direct imports such as `from cachew.legacy import NTBinder` are no longer supported. + +The old representation contributed two ideas that remain worth exploring independently: recursive structural schema fingerprints and schema-ordered positional records. +Those experiments belong in the current marshaller rather than reviving this implementation. + +See the [historical comparison](../../doc/benchmarks/20230912-comparison-with-legacy.org) and the committed [baseline benchmark](../../.benchmarks/Linux-CPython-3.14-64bit/0007_baseline.json) for recorded results. + +Cachew still recognizes the old SQLite table name when refreshing caches, so archiving this Python source does not remove on-disk cleanup compatibility. diff --git a/src/cachew/legacy.py b/misc/legacy/legacy.py similarity index 100% rename from src/cachew/legacy.py rename to misc/legacy/legacy.py diff --git a/pyproject.toml b/pyproject.toml index 6e92c93..5e02228 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -115,7 +115,7 @@ enable_error_code = [ [tool.ty.src] exclude = [ "doc/test_serialization.py", - "src/cachew/legacy.py", + "misc/legacy/", ] @@ -233,7 +233,7 @@ lint.ignore = [ extend-exclude = [ - "src/cachew/legacy.py", # TODO dunno, remove it for good? + "misc/legacy/", # frozen historical source, intentionally outside active checks ] [tool.ruff.format] diff --git a/src/cachew/pytest.py b/src/cachew/pytest.py deleted file mode 100644 index ff83bae..0000000 --- a/src/cachew/pytest.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Helpers to prevent depending on pytest in runtime -""" - -import sys -import typing - -under_pytest = 'pytest' in sys.modules - -if typing.TYPE_CHECKING or under_pytest: - import pytest - - parametrize = pytest.mark.parametrize -else: - - def parametrize(*_args, **_kwargs): - def wrapper(f): - return f - - return wrapper diff --git a/src/cachew/tests/benchmarks/common.py b/src/cachew/tests/benchmarks/common.py index dbde052..83d82d2 100644 --- a/src/cachew/tests/benchmarks/common.py +++ b/src/cachew/tests/benchmarks/common.py @@ -27,7 +27,6 @@ type Impl = Literal[ 'cachew', 'cattrs', - 'legacy', 'pickle', 'msgspec-json', 'msgspec-msgpack', @@ -179,18 +178,7 @@ def make_marshaller_impl( # union=Type, # converter=converter, # ) - return unstruct_func, lambda x: struct_func(x, Type), orjson.dumps, orjson.loads - elif impl == 'legacy': - from ...legacy import NTBinder - - # NOTE: legacy binder emits a tuple which can be inserted directly into - # the database. So blob/sqlite stages are not directly comparable to the - # JSON-like implementations, where you first marshal and then encode. - # That also gives legacy a bit of an advantage for custom types, since - # those would otherwise normally be handled by SQLAlchemy instead. - binder = NTBinder.make(Type) - return binder.to_row, binder.from_row, orjson.dumps, orjson.loads elif impl == 'pickle': # Keep the protocol explicit so cross-version benchmark results are # comparable even if pickle defaults change in the future. @@ -216,13 +204,6 @@ def _validate_sample_equal(_impl: Impl, result: list[Any], expected: list[Any]) def _validate_datetimes(impl: Impl, result: list[Any], expected: list[Any]) -> None: - if impl == 'legacy': - # Legacy relies on SQLAlchemy's datetime adapter in the real sqlite path. - # In these raw blob/json pipeline stages, orjson turns top-level datetime - # payloads into strings, and NTBinder.from_row() does not convert them - # back. Keep legacy in the benchmark for throughput numbers, but skip the - # stronger round-trip assertion for this benchmark-only transport path. - return assert _sample(result) == _sample(expected) if not _is_msgspec_impl(impl): # msgspec reconstructs fixed-offset tzinfo from RFC3339 rather than the