Invalidate dynamic namespace mutations
This commit is contained in:
@@ -2125,6 +2125,54 @@ RET.clear()
|
|||||||
self.assertEqual({}, result["nodes"])
|
self.assertEqual({}, result["nodes"])
|
||||||
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_getattr_return_types_mutation_after_mapping_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class GetattrReturnTypesNode:
|
||||||
|
RETURN_TYPES = ["IMAGE"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"GetattrReturnTypesNode": GetattrReturnTypesNode,
|
||||||
|
}
|
||||||
|
getattr(GetattrReturnTypesNode, "RETURN_TYPES").clear()
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "getattr-return-types-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_globals_class_return_types_mutation_after_mapping_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class GlobalsClassReturnTypesNode:
|
||||||
|
RETURN_TYPES = ["IMAGE"]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"GlobalsClassReturnTypesNode": GlobalsClassReturnTypesNode,
|
||||||
|
}
|
||||||
|
globals()["GlobalsClassReturnTypesNode"].RETURN_TYPES.clear()
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "globals-class-return-types-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_module_class_tuple_alias_patch_after_mapping_skips_node(self):
|
def test_module_class_tuple_alias_patch_after_mapping_skips_node(self):
|
||||||
source = '''
|
source = '''
|
||||||
class TupleAliasPatchedNode:
|
class TupleAliasPatchedNode:
|
||||||
@@ -2225,6 +2273,30 @@ NODE_CLASS_MAPPINGS.clear()
|
|||||||
self.assertEqual({}, result["nodes"])
|
self.assertEqual({}, result["nodes"])
|
||||||
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
|
def test_globals_mutation_invalidates_static_node_mapping(self):
|
||||||
|
source = '''
|
||||||
|
class GlobalMutatedMappingNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"GlobalMutatedMappingNode": GlobalMutatedMappingNode,
|
||||||
|
}
|
||||||
|
globals()["NODE_CLASS_MAPPINGS"].clear()
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "global-mutated-mapping-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_unpacked_alias_mutation_invalidates_static_node_mapping(self):
|
def test_unpacked_alias_mutation_invalidates_static_node_mapping(self):
|
||||||
source = '''
|
source = '''
|
||||||
class UnpackedAliasMutatedMappingNode:
|
class UnpackedAliasMutatedMappingNode:
|
||||||
|
|||||||
@@ -83,6 +83,20 @@ def _is_mutable_static_value(value):
|
|||||||
return isinstance(value, (dict, list, set))
|
return isinstance(value, (dict, list, set))
|
||||||
|
|
||||||
|
|
||||||
|
def _namespace_subscript_name(node):
|
||||||
|
if not isinstance(node, ast.Subscript):
|
||||||
|
return None
|
||||||
|
if not isinstance(node.value, ast.Call) or not isinstance(node.value.func, ast.Name):
|
||||||
|
return None
|
||||||
|
if node.value.func.id not in {"globals", "locals", "vars"}:
|
||||||
|
return None
|
||||||
|
if node.value.args or node.value.keywords:
|
||||||
|
return None
|
||||||
|
if isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, str):
|
||||||
|
return node.slice.value
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _target_names(target):
|
def _target_names(target):
|
||||||
if isinstance(target, ast.Name):
|
if isinstance(target, ast.Name):
|
||||||
return {target.id}
|
return {target.id}
|
||||||
@@ -93,23 +107,56 @@ def _target_names(target):
|
|||||||
return names
|
return names
|
||||||
if isinstance(target, ast.Starred):
|
if isinstance(target, ast.Starred):
|
||||||
return _target_names(target.value)
|
return _target_names(target.value)
|
||||||
if isinstance(target, (ast.Attribute, ast.Subscript)):
|
if isinstance(target, ast.Attribute):
|
||||||
|
return _target_names(target.value)
|
||||||
|
if isinstance(target, ast.Subscript):
|
||||||
|
name = _namespace_subscript_name(target)
|
||||||
|
if name is not None:
|
||||||
|
return {name}
|
||||||
return _target_names(target.value)
|
return _target_names(target.value)
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|
||||||
def _root_name(node):
|
def _root_name(node):
|
||||||
while isinstance(node, (ast.Attribute, ast.Subscript)):
|
while True:
|
||||||
|
name = _namespace_subscript_name(node)
|
||||||
|
if name is not None:
|
||||||
|
return name
|
||||||
|
if not isinstance(node, (ast.Attribute, ast.Subscript)):
|
||||||
|
break
|
||||||
node = node.value
|
node = node.value
|
||||||
if isinstance(node, ast.Name):
|
if isinstance(node, ast.Name):
|
||||||
return node.id
|
return node.id
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _getattr_signature_target_names(node):
|
||||||
|
if not isinstance(node, ast.Call):
|
||||||
|
return set()
|
||||||
|
if not isinstance(node.func, ast.Name) or node.func.id != "getattr":
|
||||||
|
return set()
|
||||||
|
if len(node.args) < 2:
|
||||||
|
return set()
|
||||||
|
name = _root_name(node.args[0])
|
||||||
|
if name is None:
|
||||||
|
return set()
|
||||||
|
attr = node.args[1]
|
||||||
|
if (
|
||||||
|
isinstance(attr, ast.Constant)
|
||||||
|
and isinstance(attr.value, str)
|
||||||
|
and attr.value not in _CLASS_SIGNATURE_ATTRS
|
||||||
|
):
|
||||||
|
return set()
|
||||||
|
return {name}
|
||||||
|
|
||||||
|
|
||||||
def _attribute_target_base_names(target):
|
def _attribute_target_base_names(target):
|
||||||
if isinstance(target, ast.Attribute):
|
if isinstance(target, ast.Attribute):
|
||||||
name = _root_name(target.value)
|
name = _root_name(target.value)
|
||||||
return {name} if name else set()
|
return {name} if name else set()
|
||||||
|
names = _getattr_signature_target_names(target)
|
||||||
|
if names:
|
||||||
|
return names
|
||||||
if isinstance(target, ast.Subscript):
|
if isinstance(target, ast.Subscript):
|
||||||
return _attribute_target_base_names(target.value)
|
return _attribute_target_base_names(target.value)
|
||||||
if isinstance(target, (ast.List, ast.Tuple)):
|
if isinstance(target, (ast.List, ast.Tuple)):
|
||||||
|
|||||||
Reference in New Issue
Block a user