diff --git a/comfy_execution/caching.py b/comfy_execution/caching.py index d6561ab94..3e3bfcdab 100644 --- a/comfy_execution/caching.py +++ b/comfy_execution/caching.py @@ -124,14 +124,8 @@ def _sanitize_signature_input(obj, depth=0, max_depth=32): # the foreign object and risk crashing on custom container semantics. return Unhashable() -def to_hashable(obj, depth=0, max_depth=32, seen=None): +def to_hashable(obj): """Convert prompt-safe built-in values into a stable hashable representation.""" - if depth >= max_depth: - return Unhashable() - - if seen is None: - seen = set() - # Restrict recursion to plain built-in containers. Some custom nodes insert # runtime objects into prompt inputs for dynamic graph paths; walking those # objects as generic Mappings / Sequences is unsafe and can destabilize the @@ -139,32 +133,16 @@ def to_hashable(obj, depth=0, max_depth=32, seen=None): obj_type = type(obj) if obj_type in (int, float, str, bool, bytes, type(None)): return obj - - if obj_type in (dict, list, tuple, set, frozenset): - obj_id = id(obj) - if obj_id in seen: - return Unhashable() - seen = seen | {obj_id} - - if obj_type is dict: - return ( - "dict", - frozenset( - ( - to_hashable(k, depth + 1, max_depth, seen), - to_hashable(v, depth + 1, max_depth, seen), - ) - for k, v in obj.items() - ), - ) + elif obj_type is dict: + return ("dict", frozenset((to_hashable(k), to_hashable(v)) for k, v in obj.items())) elif obj_type is list: - return ("list", tuple(to_hashable(i, depth + 1, max_depth, seen) for i in obj)) + return ("list", tuple(to_hashable(i) for i in obj)) elif obj_type is tuple: - return ("tuple", tuple(to_hashable(i, depth + 1, max_depth, seen) for i in obj)) + return ("tuple", tuple(to_hashable(i) for i in obj)) elif obj_type is set: - return ("set", frozenset(to_hashable(i, depth + 1, max_depth, seen) for i in obj)) + return ("set", frozenset(to_hashable(i) for i in obj)) elif obj_type is frozenset: - return ("frozenset", frozenset(to_hashable(i, depth + 1, max_depth, seen) for i in obj)) + return ("frozenset", frozenset(to_hashable(i) for i in obj)) else: return Unhashable() diff --git a/tests-unit/execution_test/caching_hashable_test.py b/tests-unit/execution_test/caching_hashable_test.py deleted file mode 100644 index 5613ac6c4..000000000 --- a/tests-unit/execution_test/caching_hashable_test.py +++ /dev/null @@ -1,35 +0,0 @@ -from comfy_execution.caching import Unhashable, to_hashable - - -def test_to_hashable_returns_unhashable_for_cyclic_builtin_containers(): - """Ensure self-referential built-in containers terminate as Unhashable.""" - cyclic_list = [] - cyclic_list.append(cyclic_list) - - result = to_hashable(cyclic_list) - - assert result[0] == "list" - assert len(result[1]) == 1 - assert isinstance(result[1][0], Unhashable) - - -def test_to_hashable_returns_unhashable_when_max_depth_is_reached(): - """Ensure deeply nested built-in containers stop at the configured depth limit.""" - nested = current = [] - for _ in range(32): - next_item = [] - current.append(next_item) - current = next_item - - result = to_hashable(nested) - - depth = 0 - current = result - while isinstance(current, tuple): - assert current[0] == "list" - assert len(current[1]) == 1 - current = current[1][0] - depth += 1 - - assert depth == 32 - assert isinstance(current, Unhashable)