From f0cf13488cdbf0578f9cce0789cb1e7a7c999bc7 Mon Sep 17 00:00:00 2001 From: nperraud Date: Thu, 28 May 2026 12:18:35 +0200 Subject: [PATCH 1/3] Replace frozendict (LGPL-3.0) with stdlib tuple in lru_cache Fixes the LGPL-3.0 license propagation reported in issue #34. frozendict was used solely to make dict args hashable as lru_cache keys. A tuple of (key, frozen_value) pairs is equally hashable and correct, requires no third-party dep, and preserves cache-hit semantics since Python 3.7+ guarantees dict insertion order. --- einx/_src/util/lru_cache.py | 3 +-- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/einx/_src/util/lru_cache.py b/einx/_src/util/lru_cache.py index 6a168d2..3f94abd 100644 --- a/einx/_src/util/lru_cache.py +++ b/einx/_src/util/lru_cache.py @@ -5,7 +5,6 @@ from collections import defaultdict import numpy as np from functools import partial -import frozendict import types _thread_local = threading.local() @@ -20,7 +19,7 @@ def _freeze_value(x): elif isinstance(x, list | tuple): return tuple(_freeze_value(x) for x in x) elif isinstance(x, dict): - return frozendict.frozendict({k: _freeze_value(v) for k, v in x.items()}) + return tuple((k, _freeze_value(v)) for k, v in x.items()) elif isinstance(x, types.SimpleNamespace): return _freeze_value(vars(x)) elif isinstance(x, inspect.Parameter): diff --git a/pyproject.toml b/pyproject.toml index 6a98206..523321d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,6 @@ readme = "README.md" dependencies = [ "numpy", "sympy", - "frozendict", ] [project.optional-dependencies] From 62055c99ea7641cf74f56dfbf0ebb7e4f4a8f8a3 Mon Sep 17 00:00:00 2001 From: nperraud Date: Wed, 3 Jun 2026 15:02:54 +0200 Subject: [PATCH 2/3] Make frozen dicts hashable mappings, not plain tuples The previous tuple replacement broke downstream code that reads frozen kwargs back via key access (e.g. kwargs["backend"] in _construct_graph), causing 'TypeError: tuple indices must be integers or slices, not str'. Use a minimal stdlib dict subclass with an order-independent __hash__ so the frozen value stays a real mapping while remaining hashable for functools.lru_cache. Still no frozendict dependency. Co-Authored-By: Claude Opus 4.8 (1M context) --- einx/_src/util/lru_cache.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/einx/_src/util/lru_cache.py b/einx/_src/util/lru_cache.py index 3f94abd..c38d7b0 100644 --- a/einx/_src/util/lru_cache.py +++ b/einx/_src/util/lru_cache.py @@ -13,13 +13,22 @@ max_cache_size = int(os.environ.get("EINX_CACHE_SIZE", -1)) +class _FrozenDict(dict): + # A hashable dict used to build lru_cache keys. It still behaves like a + # mapping, since the frozen value is read back via key access downstream, + # but is hashable. Hashing is order-independent so that two equal dicts + # always produce the same cache key. + def __hash__(self): + return hash(frozenset(self.items())) + + def _freeze_value(x): if isinstance(x, np.ndarray): return _freeze_value(x.tolist()) elif isinstance(x, list | tuple): return tuple(_freeze_value(x) for x in x) elif isinstance(x, dict): - return tuple((k, _freeze_value(v)) for k, v in x.items()) + return _FrozenDict((k, _freeze_value(v)) for k, v in x.items()) elif isinstance(x, types.SimpleNamespace): return _freeze_value(vars(x)) elif isinstance(x, inspect.Parameter): From 49993c46c6459d2cd4eeddcec0e67cbe5d4ef17e Mon Sep 17 00:00:00 2001 From: nperraud Date: Wed, 3 Jun 2026 15:13:45 +0200 Subject: [PATCH 3/3] Add CHANGELOG entry for dropping frozendict Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b14b9..96ed0e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [Unreleased] + +### Changed + +- Remove the ``frozendict`` dependency, replacing it with a stdlib-only hashable ``dict`` subclass used to build ``lru_cache`` keys. This drops the last LGPL-3.0 dependency from einx without changing caching behaviour (https://github.com/fferflo/einx/pull/35). + ## [0.4.3] ### Fixed