diff --git a/tests/test_generate_popular_node_signatures.py b/tests/test_generate_popular_node_signatures.py index 082a17b..41c9600 100644 --- a/tests/test_generate_popular_node_signatures.py +++ b/tests/test_generate_popular_node_signatures.py @@ -523,6 +523,68 @@ NODE_CLASS_MAPPINGS[get_id()] = DynamicDupNode self.assertEqual({}, result["nodes"]) self.assertEqual("no_static_nodes", result["pack"]["status"]) + def _assert_namespace_mapping_replacement_duplicate_skips_static_node(self, replacement, pack_id): + source_a = ''' +class StaticDupNode: + RETURN_TYPES = ("IMAGE",) + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "image": ("IMAGE",), + }, + } + + +NODE_CLASS_MAPPINGS = { + "DupNode": StaticDupNode, +} +''' + source_b = f''' +class DynamicDupNode: + RETURN_TYPES = ("MASK",) + + @classmethod + def INPUT_TYPES(cls): + return {{ + "required": {{ + "mask": ("MASK",), + }}, + }} + + +{replacement} +''' + result = self._extract_two_sources(source_a, source_b, pack_id) + + self.assertEqual({}, result["nodes"]) + self.assertEqual("no_static_nodes", result["pack"]["status"]) + + def test_duplicate_node_id_from_globals_update_mapping_replacement_skips_static_node(self): + self._assert_namespace_mapping_replacement_duplicate_skips_static_node( + 'globals().update(NODE_CLASS_MAPPINGS={"DupNode": DynamicDupNode})', + "globals-update-replacement-duplicate-node-pack", + ) + + def test_duplicate_node_id_from_globals_dunder_setitem_mapping_replacement_skips_static_node(self): + self._assert_namespace_mapping_replacement_duplicate_skips_static_node( + 'globals().__setitem__("NODE_CLASS_MAPPINGS", {"DupNode": DynamicDupNode})', + "globals-dunder-setitem-replacement-duplicate-node-pack", + ) + + def test_duplicate_node_id_from_namespace_alias_update_mapping_replacement_skips_static_node(self): + self._assert_namespace_mapping_replacement_duplicate_skips_static_node( + 'ns = globals()\nns.update(NODE_CLASS_MAPPINGS={"DupNode": DynamicDupNode})', + "namespace-alias-update-replacement-duplicate-node-pack", + ) + + def test_duplicate_node_id_from_namespace_alias_dunder_setitem_mapping_replacement_skips_static_node(self): + self._assert_namespace_mapping_replacement_duplicate_skips_static_node( + 'ns = globals()\nns.__setitem__("NODE_CLASS_MAPPINGS", {"DupNode": DynamicDupNode})', + "namespace-alias-dunder-setitem-replacement-duplicate-node-pack", + ) + def test_unsupported_reassignment_invalidates_static_env_value(self): source = ''' def build_inputs(): diff --git a/tools/generate_popular_node_signatures.py b/tools/generate_popular_node_signatures.py index 5a52d1b..c7aa565 100644 --- a/tools/generate_popular_node_signatures.py +++ b/tools/generate_popular_node_signatures.py @@ -1856,6 +1856,7 @@ def _namespace_alias_mutation_target_names(stmt, aliases): names.update(_namespace_alias_target_names(target, aliases)) def visit_Call(self, node): + names.update(_namespace_mutating_call_target_names(node)) if isinstance(node.func, ast.Attribute): if isinstance(node.func.value, ast.Name) and node.func.value.id in aliases: if node.func.attr in _NAMESPACE_DUNDER_MUTATORS: @@ -2302,6 +2303,9 @@ def _node_class_mapping_keys(tree): if mutation_keys is _INVALID: return _INVALID keys.update(mutation_keys) + namespace_mutations = _namespace_alias_mutation_target_names(stmt, namespace_aliases) + if _name_invalidated_by("NODE_CLASS_MAPPINGS", namespace_mutations): + return _INVALID _apply_module_stmt_to_env(stmt, env, class_bindings) _update_module_dict_aliases( stmt,