Skip to content

symfonic.kernel.contracts.freeze

freeze

Deep-freeze conversion and the structural immutability check (IPL-3).

Two functions with one relationship: :func:deep_freeze is what the compiler runs over every value it accepts, and :func:is_deeply_frozen is what CI runs over the plan it produced. The compiler converts rather than trusting its inputs to already be immutable, so a caller that hands in a list and then mutates it cannot change a compiled plan.

The recursion deliberately stops at objects that are neither containers nor frozen dataclasses. That is IPL-4, not an oversight: plan group G6 holds live port objects whose identity is frozen and whose internal state is not. A connection pool is not immutable and pretending otherwise would make the rule unimplementable.

deep_freeze

deep_freeze(value: Any) -> Any

Return an immutable equivalent of value, transitively.

list/tuple become tuple, set/frozenset become frozenset, mappings become read-only proxies over a fresh dict, and everything else is returned unchanged.

Source code in src/symfonic/kernel/contracts/freeze.py
def deep_freeze(value: Any) -> Any:
    """Return an immutable equivalent of ``value``, transitively.

    ``list``/``tuple`` become ``tuple``, ``set``/``frozenset`` become
    ``frozenset``, mappings become read-only proxies over a fresh dict, and
    everything else is returned unchanged.
    """
    if isinstance(value, MappingProxyType):
        return MappingProxyType({key: deep_freeze(item) for key, item in value.items()})
    if isinstance(value, Mapping):
        return MappingProxyType({key: deep_freeze(item) for key, item in value.items()})
    if isinstance(value, list | tuple):
        return tuple(deep_freeze(item) for item in value)
    if isinstance(value, set | frozenset):
        return frozenset(deep_freeze(item) for item in value)
    if isinstance(value, bytearray):
        return bytes(value)
    return value

freeze_mapping

freeze_mapping(value: Mapping[str, Any] | None) -> Mapping[str, Any]

Deep-freeze a mapping, treating None as empty.

Source code in src/symfonic/kernel/contracts/freeze.py
def freeze_mapping(value: Mapping[str, Any] | None) -> Mapping[str, Any]:
    """Deep-freeze a mapping, treating ``None`` as empty."""
    if not value:
        return MappingProxyType({})
    frozen = deep_freeze(dict(value))
    return frozen  # type: ignore[return-value]

is_deeply_frozen

is_deeply_frozen(value: Any) -> bool

Return True when no mutable container is reachable from value.

"Frozen at the top, list underneath" is exactly what this rejects.

Source code in src/symfonic/kernel/contracts/freeze.py
def is_deeply_frozen(value: Any) -> bool:
    """Return ``True`` when no mutable container is reachable from ``value``.

    "Frozen at the top, ``list`` underneath" is exactly what this rejects.
    """
    if isinstance(value, MappingProxyType):
        return all(is_deeply_frozen(item) for item in value.values())
    if isinstance(value, _MUTABLE_CONTAINERS):
        return False
    if isinstance(value, str | bytes):
        return True
    if isinstance(value, tuple | frozenset):
        return all(is_deeply_frozen(item) for item in value)
    if dataclasses.is_dataclass(value) and not isinstance(value, type):
        params = getattr(type(value), "__dataclass_params__", None)
        if params is not None and not params.frozen:
            return False
        return all(
            is_deeply_frozen(getattr(value, field.name))
            for field in dataclasses.fields(value)
        )
    return True