Fail closed on duplicate keys and observed calls
This commit is contained in:
@@ -260,6 +260,49 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
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_duplicate_node_id_with_unsupported_mapping_value_skips_static_node(self):
|
||||||
|
source_a = '''
|
||||||
|
class StaticDupNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"DupNode": StaticDupNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
source_b = '''
|
||||||
|
def build_node():
|
||||||
|
return object()
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"DupNode": build_node(),
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
Path(tmp, "a.py").write_text(textwrap.dedent(source_a), encoding="utf-8")
|
||||||
|
Path(tmp, "b.py").write_text(textwrap.dedent(source_b), encoding="utf-8")
|
||||||
|
result = extract_repo_signatures(
|
||||||
|
Path(tmp),
|
||||||
|
{
|
||||||
|
"id": "unsupported-duplicate-node-pack",
|
||||||
|
"title": "Unsupported Duplicate Node Pack",
|
||||||
|
"repository": "https://github.com/example/unsupported-duplicate-node-pack",
|
||||||
|
"rank": 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_unsupported_reassignment_invalidates_static_env_value(self):
|
def test_unsupported_reassignment_invalidates_static_env_value(self):
|
||||||
source = '''
|
source = '''
|
||||||
def build_inputs():
|
def build_inputs():
|
||||||
@@ -767,6 +810,33 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
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_arbitrary_call_observing_mutable_env_value_invalidates_static_env_value(self):
|
||||||
|
source = '''
|
||||||
|
INPUTS = {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
observe(INPUTS)
|
||||||
|
|
||||||
|
|
||||||
|
class ObservedInputEnvNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return INPUTS
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"ObservedInputEnvNode": ObservedInputEnvNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "observed-input-env-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_function_default_mutation_invalidates_static_env_value(self):
|
def test_function_default_mutation_invalidates_static_env_value(self):
|
||||||
source = '''
|
source = '''
|
||||||
INPUTS = {
|
INPUTS = {
|
||||||
@@ -1685,6 +1755,30 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
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_return_types_used_as_callee_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class CalleeObservedReturnTypesNode:
|
||||||
|
RETURN_TYPES = ["IMAGE"]
|
||||||
|
RETURN_TYPES()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"CalleeObservedReturnTypesNode": CalleeObservedReturnTypesNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "callee-observed-return-types-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_return_types_function_default_arbitrary_call_skips_node(self):
|
def test_return_types_function_default_arbitrary_call_skips_node(self):
|
||||||
source = '''
|
source = '''
|
||||||
class DefaultArbitraryCallReturnTypesNode:
|
class DefaultArbitraryCallReturnTypesNode:
|
||||||
@@ -3735,6 +3829,31 @@ NODE_CLASS_MAPPINGS = {
|
|||||||
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_input_types_used_as_callee_skips_node(self):
|
||||||
|
source = '''
|
||||||
|
class CalleeObservedInputTypesNode:
|
||||||
|
RETURN_TYPES = ("IMAGE",)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def INPUT_TYPES(cls):
|
||||||
|
return {
|
||||||
|
"required": {
|
||||||
|
"image": ("IMAGE",),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
INPUT_TYPES()
|
||||||
|
|
||||||
|
|
||||||
|
NODE_CLASS_MAPPINGS = {
|
||||||
|
"CalleeObservedInputTypesNode": CalleeObservedInputTypesNode,
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
result = self._extract_source(source, "callee-observed-input-types-pack")
|
||||||
|
|
||||||
|
self.assertEqual({}, result["nodes"])
|
||||||
|
self.assertEqual("no_static_nodes", result["pack"]["status"])
|
||||||
|
|
||||||
def test_input_types_alias_observed_by_arbitrary_call_skips_node(self):
|
def test_input_types_alias_observed_by_arbitrary_call_skips_node(self):
|
||||||
source = '''
|
source = '''
|
||||||
class AliasObservedInputTypesNode:
|
class AliasObservedInputTypesNode:
|
||||||
|
|||||||
@@ -635,6 +635,7 @@ def _arbitrary_call_observed_names(stmt):
|
|||||||
self.visit(node.args)
|
self.visit(node.args)
|
||||||
|
|
||||||
def visit_Call(self, node):
|
def visit_Call(self, node):
|
||||||
|
names.update(_referenced_names(node.func))
|
||||||
if isinstance(node.func, ast.Attribute):
|
if isinstance(node.func, ast.Attribute):
|
||||||
names.update(_referenced_names(node.func.value))
|
names.update(_referenced_names(node.func.value))
|
||||||
for arg in node.args:
|
for arg in node.args:
|
||||||
@@ -779,6 +780,10 @@ def _apply_module_stmt_to_env(stmt, env, class_bindings=None):
|
|||||||
else:
|
else:
|
||||||
_invalidate_class_bindings(class_bindings, names)
|
_invalidate_class_bindings(class_bindings, names)
|
||||||
_invalidate_env_names(env, names)
|
_invalidate_env_names(env, names)
|
||||||
|
observed_names = _arbitrary_call_observed_names(stmt)
|
||||||
|
for name in observed_names:
|
||||||
|
if name in env and _is_mutable_static_value(env[name]):
|
||||||
|
_invalidate_env_name(env, name)
|
||||||
if isinstance(stmt, ast.ClassDef):
|
if isinstance(stmt, ast.ClassDef):
|
||||||
if class_bindings is not None:
|
if class_bindings is not None:
|
||||||
if _is_trivially_safe_class_def(stmt):
|
if _is_trivially_safe_class_def(stmt):
|
||||||
@@ -1782,6 +1787,19 @@ def _node_class_mappings(tree):
|
|||||||
return {node_type: binding for node_type, (_class_name, binding) in mappings.items() if node_type}
|
return {node_type: binding for node_type, (_class_name, binding) in mappings.items() if node_type}
|
||||||
|
|
||||||
|
|
||||||
|
def _node_class_mapping_keys(tree):
|
||||||
|
if _has_module_wildcard_import(tree):
|
||||||
|
return set()
|
||||||
|
mappings = _final_module_dict(
|
||||||
|
tree,
|
||||||
|
"NODE_CLASS_MAPPINGS",
|
||||||
|
lambda _value, _env, _class_bindings: True,
|
||||||
|
)
|
||||||
|
if not all(isinstance(node_type, str) for node_type in mappings):
|
||||||
|
return set()
|
||||||
|
return {node_type for node_type in mappings if node_type}
|
||||||
|
|
||||||
|
|
||||||
def _display_mappings(tree):
|
def _display_mappings(tree):
|
||||||
displays = _final_module_dict(
|
displays = _final_module_dict(
|
||||||
tree,
|
tree,
|
||||||
@@ -1880,7 +1898,15 @@ def extract_repo_signatures(repo_dir, pack_meta):
|
|||||||
continue
|
continue
|
||||||
env = _collect_module_env(tree)
|
env = _collect_module_env(tree)
|
||||||
mappings = _node_class_mappings(tree)
|
mappings = _node_class_mappings(tree)
|
||||||
|
mapping_node_types = _node_class_mapping_keys(tree)
|
||||||
displays = _display_mappings(tree)
|
displays = _display_mappings(tree)
|
||||||
|
for node_type in sorted(mapping_node_types):
|
||||||
|
prior_path = node_sources.get(node_type)
|
||||||
|
if prior_path is not None and prior_path != path:
|
||||||
|
duplicate_node_types.add(node_type)
|
||||||
|
nodes.pop(node_type, None)
|
||||||
|
continue
|
||||||
|
node_sources.setdefault(node_type, path)
|
||||||
if displays is _INVALID:
|
if displays is _INVALID:
|
||||||
continue
|
continue
|
||||||
for node_type, binding in sorted(mappings.items()):
|
for node_type, binding in sorted(mappings.items()):
|
||||||
|
|||||||
Reference in New Issue
Block a user