Source code for qs_codec.models.weak_wrapper
"""A wrapper that allows weak references to be used as dictionary keys."""
import typing as t
import weakref
from collections.abc import Mapping
from dataclasses import dataclass
# weak-value dictionary: values are kept **weakly**
_proxy_cache: "weakref.WeakValueDictionary[int, _Refable]" = weakref.WeakValueDictionary()
class _Refable:
__slots__ = ("value", "__weakref__") # allow weak-refs
def __init__(self, value: t.Any):
self.value = value
[docs]
@dataclass(frozen=True)
class WeakWrapper:
"""Weakly wraps *any* object (even dicts/lists) with deep-content hashing and identity equality."""
_proxy: _Refable # strong ref while the wrapper lives
_value_id: int
_wref: weakref.ReferenceType["_Refable"] # weak ref for hash/GC callbacks
[docs]
def __init__(self, value: t.Any):
"""Initialize the WeakWrapper with a value."""
# obtain (or create) a shared proxy for this value
proxy = _proxy_cache.get(id(value))
if proxy is None:
proxy = _Refable(value)
_proxy_cache[id(value)] = proxy
object.__setattr__(self, "_proxy", proxy) # strong
object.__setattr__(self, "_value_id", id(value))
object.__setattr__(self, "_wref", weakref.ref(proxy)) # weak
# Equality / hash
def __eq__(self, other: object) -> bool:
"""Compare two `WeakWrapper` objects."""
return isinstance(other, WeakWrapper) and self._value_id == other._value_id
def __hash__(self) -> int:
"""Return the hash of the value."""
return self._hash_recursive(self._proxy.value, seen=set(), stack=set())
# Recursive hash with cycle/Depth checks
def _hash_recursive(
self,
value: t.Any,
seen: t.Set[int],
stack: t.Set[int],
depth: int = 0,
max_depth: int = 400, # default recursion limit
) -> int:
"""Recursively hash a value."""
vid = id(value)
if vid in stack:
raise ValueError("Circular reference detected")
if depth > max_depth:
raise RecursionError("Maximum recursion depth exceeded")
stack.add(vid)
try:
if isinstance(value, Mapping):
return hash(
tuple(sorted((k, self._hash_recursive(v, seen, stack, depth + 1)) for k, v in value.items()))
)
elif isinstance(value, (list, set, tuple)):
seq = (
self._hash_recursive(v, seen, stack, depth + 1)
for v in (sorted(value) if isinstance(value, set) else value)
)
return hash(tuple(seq))
else:
return hash(value)
finally:
stack.remove(vid)
# Helpful property
@property
def value(self) -> t.Any:
"""Return the value of the weak reference."""
proxy = self._wref() # dereference weakly
if proxy is None:
raise ReferenceError("original object has been garbage-collected")
return proxy.value