From e8f0fea7972a69a62c3d48fdd1f0c5fee5236bfc Mon Sep 17 00:00:00 2001 From: Kennedy Richard Date: Fri, 3 May 2024 12:53:54 -0300 Subject: [PATCH] wip: depth-first topological sort for graph exec --- nodezator/graphman/callablenode/execution.py | 508 +++++++------------ nodezator/graphman/exception.py | 22 +- nodezator/graphman/execution.py | 190 ++++--- nodezator/graphman/operatornode/execution.py | 133 +---- 4 files changed, 292 insertions(+), 561 deletions(-) diff --git a/nodezator/graphman/callablenode/execution.py b/nodezator/graphman/callablenode/execution.py index 710ed775..44751aad 100644 --- a/nodezator/graphman/callablenode/execution.py +++ b/nodezator/graphman/callablenode/execution.py @@ -2,7 +2,6 @@ from ..exception import ( MissingInputError, - WaitingInputException, PositionalSubparameterUnpackingError, KeywordSubparameterUnpackingError, ) @@ -18,29 +17,16 @@ def create_execution_support_objects(self): ### (the input comes from default values, widgets ### or other nodes) - self.argument_map = {param_name: {} for param_name in self.var_kind_map} - - ### create a set to store the names of all - ### pending parameters, that is, parameters - ### which are still waiting for input from - ### other nodes - self.pending_param_names = set() - - ### create a map to store whether or not - ### input sockets are expecting input from - ### another node - self.expects_input_map = {} + self.argument_map = { + param_name: {} + for param_name in self.var_kind_map + } def perform_execution_setup(self): """Clear input slots, set finished flag to False. Performed before and after a node execution. """ - ### update the pending parameter names set with the - ### name of all existing parameters - - self.pending_param_names.update(self.signature_obj.parameters.keys()) - ### clear the argument map and insert empty dicts ### for subparameters @@ -49,408 +35,254 @@ def perform_execution_setup(self): for param_name in self.var_kind_map: self.argument_map[param_name] = {} - ### setup the expects input map - - self.expects_input_map.clear() - - for input_socket in self.input_socket_live_flmap.flat_values: - - ### use the parameter name as key - key = input_socket.parameter_name - - ### unless the subparameter index is not None, - ### in which case the key must be a tuple - ### containing the parameter name and - ### subparameter index - - if input_socket.subparameter_index is not None: - - key = ( - key, - input_socket.subparameter_index, - ) - - ### use the key to store whether the input - ### socket has a 'parent' attribute or not - ### in the map + def check_and_setup_parameters(self): + """Perform checks/setups on parameters. - self.expects_input_map[key] = hasattr( - input_socket, - "parent", - ) + Here we check each parameter (and corresponding subparameters, if any) + to see whether whether they expect... - ### set a state as 'ready - self.state = "ready" + - expect incoming data from another node + - have available data from widgets + - lack input source - def check_pending_parameters(self): - """Perform checks on pending parameters. - - Here we check pending parameters to see whether we - - 1. should raise a WaitingInputException, meaning the - node must wait for inputs from other nodes before - executing. - 2. only if there's no incoming data from other - nodes, whether we should use data provided by the - user via embedded widgets or default values from - the callable itself. - 3. in case there's no source of data for a - required parameter, raise an MissingInputError - explaining the situation. + In case one or more of them lack a source of data, we raise a + MissingInputError explaining the situation. """ - ### create a set to hold names of parameters - ### which are not pending anymore - ready_param_names = set() - - ### create a list to hold names of (sub)parameters - ### waiting for input - waiting_input = [] - ### create a list to hold names of (sub)parameters ### lacking a data source lacking_data_source = [] - ### alias the argument map using a variable of - ### smaller character count for better code layout + ### reference/alias relevant maps locally for quicker + ### and easier access + arg_map = self.argument_map + var_kind_map = self.var_kind_map + isl_flmap = self.input_socket_live_flmap - ### iterate over each pending parameter, performing + ### iterate over the name of each parameter, performing ### checks and acting according to their results - for param_name in self.pending_param_names: - - ## check whether the parameter is or not of - ## variable kind, by trying to retrieve which - ## kind of variable parameter it is - try: - kind = self.var_kind_map[param_name] - - ## if it fails, it means it is not a variable - ## parameter, so we perform suitable checks - - except KeyError: - - ## if the parameter is present in the - ## argument map, we deem it ready - - if param_name in arg_map: - ready_param_names.add(param_name) - - ## if the parameter has data set to be - ## delivered from another node output, - ## we store its name on the 'waiting_input' - ## list - - elif self.expects_input_map[param_name]: - waiting_input.append(param_name) - - ## otherwise, try retrieving values from - ## other sources - - else: - - # try assigning the default value for - # the input, if it exists, in which - # case you can deem the parameter as - # ready - - try: - arg_map[param_name] = self.default_map[param_name] - - except KeyError: - pass - - else: - ready_param_names.add(param_name) - - # try assigning a value from an - # embedded widget if such value - # exists in which case you can deem - # the parameter as ready (note that - # this is tried regardless of whether - # the previous try statement succeeds, - # because values set on widgets by - # users have precedence over the - # actual default values defined in the - # callables) - - try: - - arg_map[param_name] = self.data["param_widget_value_map"][ - param_name - ] - - except KeyError: - pass - - else: - ready_param_names.add(param_name) - - # if, after the attempted assignments - # above, the input still isn't - # available, then it can only mean the - # node is inexecutable due to lack of - # input sources; we store its name on - # the 'lacking_data_source' list - - if param_name not in arg_map: - - lacking_data_source.append(param_name) + for param_name in self.signature_obj.parameters.keys(): ## if parameter is of variable kind, check ## whether it has subparameters and their ## respective data sources (incoming data ## or data from a widget) - else: + if param_name in var_kind_map: + + ## retrieve kind + kind = var_kind_map[param_name] ## retrieve a list of subparameters - - subparams = self.data["subparam_map"][param_name] + subparams = self.data['subparam_map'][param_name] ## if it has subparameters (list isn't - ## empty), check whether the subparameters - ## have appropriate data sources (incoming - ## data from another node or data from a - ## widget) + ## empty), check whether we need to retrieve data + ## from its widget or not if subparams: - # iterate over each subparameter index, - # checking its availability/solving - # problems, keeping track of whether - # there subparams waiting for input - - subparams_waiting = False - for subparam_index in subparams: - # check presence of subparameter + # if respective input socket doesn't have a parent, + # store data from widget as the argument - try: - (arg_map[param_name][subparam_index]) + input_socket = isl_flmap[param_name][subparam_index] - # if subparameter is not present + if not hasattr(input_socket, 'parent'): - except KeyError: + # retrieve value from a widget - # if there's incoming data for - # the subparameter, store a - # tuple with names of parameter - # and subparameter on the - # waiting_input list and set - # the subparams_waiting flag - # to True - - if self.expects_input_map[(param_name, subparam_index)]: - - waiting_input.append((param_name, subparam_index)) + arg_map[param_name][subparam_index] = ( + self.data + ['subparam_widget_map'] + [param_name] + [subparam_index] + ['widget_kwargs'] + ['value'] + ) - subparams_waiting = True + ## otherwise, if it doesn't have any subparameters, we assign + ## the default value for the parameter, according to + ## its specific variable kind: + ## + ## - an empty tuple for a positional-variable parameter, or + ## - an empty dict for a keyword-variable parameter - # otherwise, try retrieving - # a value from a widget + else: + arg_map[param_name] = () if kind == "var_pos" else {} - else: + ## if the param name is not in the variable kind map, + ## it means it is not a variable parameter, + ## so we perform suitable checks - try: + else: - (arg_map[param_name][subparam_index]) = self.data[ - "subparam_widget_map" - ][param_name][subparam_index]["widget_kwargs"][ - "value" - ] + ## if the parameter's input socket lacks a parent, + ## try retrieving values from other sources - # if the attempt fails - # (the subparam widget map - # doesn't have a sub key w/ - # the subparameter's name) - # it means the subparameter - # has no input source; - # we store the parameter - # name and subparameter - # index in the - # 'lacking_data_source' list + input_socket = isl_flmap[param_name] - except KeyError: + if not hasattr(input_socket, 'parent'): - ( - lacking_data_source.append( - ( - param_name, - subparam_index, - ) - ) - ) + # try assigning the default value for the input, + # if it exists - # if there are no subparameters waiting, - # it means they are all available so we - # can finally deem the parameter ready + try: + arg_map[param_name] = self.default_map[param_name] - if not subparams_waiting: + except KeyError: + pass - # deem the parameter ready - ready_param_names.add(param_name) + # try assigning a value from an embedded widget + # if such value exists, (note that this is tried + # regardless of whether the previous try statement + # succeeds, because values set on widgets by users + # have precedence over the actual default values + # defined in the callables) - # retrieve subparam value map - subparam_value_map = arg_map[param_name] + try: - # perform additional setups - # depending on the specific kind - # of variable parameter we have + arg_map[param_name] = ( + self.data + ['param_widget_value_map'] + [param_name] + ) - if kind == "var_pos": + except KeyError: + pass - # obtain a list which represents - # the subparameters' indices, - # which are keys, sorted + # if, after the attempted assignments above, the input + # still isn't available, then it can only mean the + # node is inexecutable due to lack of input sources; + # + # we store its name on the 'lacking_data_source' list - sorted_indices = sorted(subparam_value_map) + if param_name not in arg_map: + lacking_data_source.append(param_name) - # retrieve the list of - # subparameters that must - # be unpacked + ### if parameters lacking data sources were found, + ### raise a MissingInputError - subparams_for_unpacking = self.data[ - "subparam_unpacking_map" - ][param_name] + if lacking_data_source: + raise MissingInputError(self, lacking_data_source) - # retrieve the values of each - # subparameter in the order - # defined in the 'sorted_indices' - # list, building a list with - # the values + def perform_pre_execution_setups(self): + """If unpack data received in subparameters if requested.""" - param_args = [] + ### reference and alias argument map locally for quicker + ### and easier access + arg_map = self.argument_map - for index in sorted_indices: + ### iterate over the name of each parameter, performing + ### checks and acting according to their results - if index in subparams_for_unpacking: + for param_name, kind in self.var_kind_map.items(): - try: + ## retrieve subparam value map + subparam_value_map = arg_map[param_name] - param_args.extend(subparam_value_map[index]) + ## iterate over existing subparameters (if any), + ## unpacking the argument if requested - except Exception as err: + for subparam_index in self.data['subparam_map'][param_name]: - raise PositionalSubparameterUnpackingError( - self, - param_name, - index, - ) from err + # perform additional setups depending on the + # specific kind of variable parameter we have - else: + if kind == 'var_pos': - param_args.append(subparam_value_map[index]) + # retrieve the list of subparameters that must + # be unpacked + subparams_for_unpacking = ( + self.data['subparam_unpacking_map'][param_name] + ) - # replace the parameter data in - # the argument map by the list - # we just created - arg_map[param_name] = param_args + # retrieve the values of each subparameter in the + # order defined by the subparameter indices + # building a list with the values - elif kind == "var_key": + param_args = [] - # retrieve map containing - # name of keyword for each - # subparameter + for index in sorted(subparam_value_map): - subparam_keywords = self.data["subparam_keyword_map"] + if index in subparams_for_unpacking: - # now build a new dictionary - # with the sorted keys from the - # subparam value map and its - # values + try: + param_args.extend(subparam_value_map[index]) - param_args = {} + except Exception as err: - for index in sorted(subparam_value_map): + raise PositionalSubparameterUnpackingError( + self, + param_name, + index, + ) from err - value = subparam_value_map.pop(index) + else: + param_args.append(subparam_value_map[index]) - if index in subparam_keywords: + # replace the parameter data in the argument map + # by the list we populated + arg_map[param_name] = param_args - param_args[subparam_keywords[index]] = value + elif kind == 'var_key': - else: + # retrieve map containing name of keyword for each + # subparameter + subparam_keywords = self.data['subparam_keyword_map'] - # note that we use '.update(**value)' - # instead of '.update(value)'; this is - # so because '.update(value)' would be - # more lenient than the '**', and thus - # not equivalent ('**' only accepts - # mappings, while .update accepts - # other iterables as well); - # - # since we are emulating the behaviour - # of '**', we go with the less lenient - # behaviour of only accepting mappings + # now build a new dictionary with the sorted keys + # from the subparam value map and its values - try: - param_args.update(**value) + param_args = {} - except Exception as err: + for index in sorted(subparam_value_map): - raise KeywordSubparameterUnpackingError( - self, - param_name, - index, - ) from err + value = subparam_value_map.pop(index) - # replace the parameter data in - # the argument map by the dict - # we just created - arg_map[param_name] = param_args + if index in subparam_keywords: + param_args[subparam_keywords[index]] = value else: - raise RuntimeError( - "there shouldn't be possible" - " to specify a variable" - " parameter of a kind which" - " is neither 'var_pos' or" - " 'var_key'" - ) - - ## otherwise, if it doesn't have any - ## subparameters, we deem the parameter - ## ready and assign the default value for - ## the parameter, according to its specific - ## variable kind: (an empty tuple for a - ## positional-variable parameter or an - ## empty dict for a keyword-variable - ## parameter) + # note that we use '.update(**value)' + # instead of '.update(value)'; this is + # so because '.update(value)' would be + # more lenient than the '**', and thus + # not equivalent ('**' only accepts + # mappings, while .update accepts + # other iterables as well); + # + # since we are emulating the behaviour + # of '**', we go with the less lenient + # behaviour of only accepting mappings + + try: + param_args.update(**value) + + except Exception as err: + + raise KeywordSubparameterUnpackingError( + self, + param_name, + index, + ) from err + + # replace the parameter data in + # the argument map by the dict + # we just created + arg_map[param_name] = param_args else: - # deem the parameter ready - ready_param_names.add(param_name) - - # define and store a default value - # according to the kind of parameter - - default = () if kind == "var_pos" else {} - - arg_map[param_name] = default - - ### remove ready parameter names from pending set - self.pending_param_names -= ready_param_names - - ### if parameters lacking data sources are found, - ### raise MissingInputError - - if lacking_data_source: - - raise MissingInputError( - self, - lacking_data_source, - ) - - ### if there are (sub)parameters waiting for input, - ### raise the WaitingInputException - - if waiting_input: + raise RuntimeError( + "there shouldn't be possible to specify a variable" + " parameter of a kind which is neither 'var_pos' or" + " 'var_key'" + ) - raise WaitingInputException("node has (sub)parameters waiting for input") def receive_input(self, data, param_name, subparam_index=None): """Store given data diff --git a/nodezator/graphman/exception.py b/nodezator/graphman/exception.py index 879971f3..b80074a6 100644 --- a/nodezator/graphman/exception.py +++ b/nodezator/graphman/exception.py @@ -141,17 +141,17 @@ def __init__(self, missing_ids): super().__init__(msg) -### raised during graph execution - - -class WaitingInputException(Exception): - """Raised whenever a node is waiting for arguments. - - It is a normal situation, where we can do nothing but - wait for the needed argument to arrive from another - node. - """ +### raised preparing to execute graph or during its execution +# TODO we should probably rename MissingInputError +# +# althoug one or more inputs are indeed missing, it would be +# more accurate to say that they were never set from the beginning. +# +# thus, it may be more accurate to say that the node is lacking +# input(s) +# +# ponder. class MissingInputError(Exception): """Raised whenever a (sub)parameter has no data.""" @@ -188,7 +188,7 @@ def __init__( # - is not a mapping and # - doesn't contain the expected key # -# ponder +# ponder. class MissingOutputError(Exception): """Raised whenever the output misses data. diff --git a/nodezator/graphman/execution.py b/nodezator/graphman/execution.py index e5eb2d7d..7339df4d 100644 --- a/nodezator/graphman/execution.py +++ b/nodezator/graphman/execution.py @@ -41,7 +41,6 @@ from .exception import ( MissingInputError, - WaitingInputException, NodeCallableError, MissingOutputError, PositionalSubparameterUnpackingError, @@ -223,35 +222,41 @@ def execute_graph(self, requested_nodes=None): add_node_for_execution = self.add_node_for_execution a_set = add_node_for_execution.__self__ - ## iterate over nodes, adding them to specific sets and + ## try iterating over nodes, adding them to specific sets and ## performing extra tasks as needed - for node in nodes_to_visit: + try: + + for node in nodes_to_visit: + + if not hasattr(node, 'main_callable'): + + if 'source_name' in node.data: + add_redirect_node(node) - if not hasattr(node, 'main_callable'): + else: + add_data_node(node) - if 'source_name' in node.data: - add_redirect_node(node) + elif node.data.get('mode') == 'callable': + add_callable_mode_node(node) else: - add_data_node(node) - elif node.data.get('mode') == 'callable': - add_callable_mode_node(node) + add_node_for_execution(node) - else: + ### perform extra setups/checks needed to ensure the + ### node is able and ready to be executed - add_node_for_execution(node) + node.perform_execution_setup() + node.check_and_setup_parameters() - ### perform extra setups/checks needed to ensure the - ### node is able and ready to be executed - ### - ### TODO another method must be called to ensure the - ### node isn't missing inputs (it will very likely be created - ### by relocating some logic out of the current method of the - ### node for executing it) - node.perform_execution_setup() + ## if one of the nodes for execution is lacking input(s), notify user + ## via dialog and cancel operation by returning earlier + + except MissingInputError as err: + create_and_show_dialog(str(err), level_name='error') + return ### perform checks and redirect data from the corresponding ### set of nodes @@ -300,123 +305,111 @@ def execute_graph(self, requested_nodes=None): try: - ## check whether node is ready for - ## execution + ## first, perform pre-execution setups + node.perform_pre_execution_setups() - try: - node.check_pending_parameters() + ## check whether node has a callable used as a + ## backdoor to retrieve visuals from it, storing + ## such backdoor if so - ## if a node is just waiting input - ## from another one, just pass - except WaitingInputException: - pass + for var_name in BACKDOOR_INDICATIVE_VAR_NAMES: - ## if check doesn't raise an error... + if hasattr(node, var_name): + backdoor = getattr(node, var_name) + break else: + backdoor = None - ## check whether node has a callable used as a - ## backdoor to retrieve visuals from it, storing - ## such backdoor if so - - for var_name in BACKDOOR_INDICATIVE_VAR_NAMES: + ## pick appropriate callable object depending on + ## whether the backdoor exists + callable_obj = backdoor if backdoor else node.main_callable - if hasattr(node, var_name): - backdoor = getattr(node, var_name) - break - - else: - backdoor = None + ### try executing the callable by passing the needed + ### arguments to a function that will execute it and + ### return the callable's return value - ## pick appropriate callable object depending on - ## whether the backdoor exists - callable_obj = backdoor if backdoor else node.main_callable - - ### try executing the callable by passing the needed - ### arguments to a function that will execute it and - ### return the callable's return value - - try: + try: - ## execute + ## execute - node_exec_start = time() + node_exec_start = time() - return_value = lay_arguments_and_execute( - callable_obj, node.argument_map, node.signature_obj - ) + return_value = lay_arguments_and_execute( + callable_obj, node.argument_map, node.signature_obj + ) - node_exec_time_map[node.id] = time() - node_exec_start + node_exec_time_map[node.id] = time() - node_exec_start - output_to_send = return_value + output_to_send = return_value - ### if an unexpected error occurs, - ### raise a custom error from the - ### original one + ### if an unexpected error occurs, + ### raise a custom error from the + ### original one - except Exception as err: - raise NodeCallableError(node) from err + except Exception as err: + raise NodeCallableError(node) from err - ### if needed: - ### - redefine output to be sent to downstream nodes - ### - set visuals/looping + ### if needed: + ### - redefine output to be sent to downstream nodes + ### - set visuals/looping - try: + try: - if backdoor: + if backdoor: - node.set_visual(return_value['in_graph_visual']) + node.set_visual(return_value['in_graph_visual']) - if hasattr(node, 'loop_entering_command'): + if hasattr(node, 'loop_entering_command'): - node.loop_data = return_value['loop_data'] + node.loop_data = return_value['loop_data'] - if node.preview_toolbar.check_button.get(): - node.loop_entering_command() + if node.preview_toolbar.check_button.get(): + node.loop_entering_command() - output_to_send = return_value.get('output') + output_to_send = return_value.get('output') - elif hasattr(node, SIDEVIZ_FROM_OUTPUT_VAR_NAME): + elif hasattr(node, SIDEVIZ_FROM_OUTPUT_VAR_NAME): - node.set_visual( - node.get_sideviz_from_output(return_value) - ) + node.set_visual( + node.get_sideviz_from_output(return_value) + ) - ### + ### - loop_data_retrieval_op = ( - getattr(node, LOOPVIZ_FROM_OUTPUT_VAR_NAME, None) - ) + loop_data_retrieval_op = ( + getattr(node, LOOPVIZ_FROM_OUTPUT_VAR_NAME, None) + ) - if loop_data_retrieval_op: + if loop_data_retrieval_op: - node.loop_data = loop_data_retrieval_op(return_value) + node.loop_data = loop_data_retrieval_op(return_value) - if node.preview_toolbar.check_button.get(): - node.loop_entering_command() + if node.preview_toolbar.check_button.get(): + node.loop_entering_command() - except Exception as err: + except Exception as err: - raise RuntimeError( - "Error while setting visual/looping/output." - ) from err + raise RuntimeError( + "Error while setting visual/looping/output." + ) from err - ### + ### - # send its return value to - # other nodes as needed + # send its return value to + # other nodes as needed - _send_output_from_executed( - node, - output_to_send, - ) + _send_output_from_executed( + node, + output_to_send, + ) - # perform its execution setup - node.perform_execution_setup() + # perform its execution setup + node.perform_execution_setup() - # append node to list of executed ones - executed_nodes.append(node) + # append node to list of executed ones + executed_nodes.append(node) ### if an error is thrown, act according to its @@ -435,7 +428,6 @@ def execute_graph(self, requested_nodes=None): if isinstance( err, ( - MissingInputError, MissingOutputError, PositionalSubparameterUnpackingError, KeywordSubparameterUnpackingError, diff --git a/nodezator/graphman/operatornode/execution.py b/nodezator/graphman/operatornode/execution.py index 6fa09d5a..3a02931e 100644 --- a/nodezator/graphman/operatornode/execution.py +++ b/nodezator/graphman/operatornode/execution.py @@ -1,9 +1,9 @@ + ### local imports -from ..exception import ( - MissingInputError, - WaitingInputException, -) +from ..exception import MissingInputError + +from ...ourstdlibs.behaviour import empty_function class Execution: @@ -11,133 +11,40 @@ class Execution: def __init__(self): """Create objects to help during execution.""" - ### create a map to store input for parameters ### (the input comes only from other nodes) self.argument_map = {} - ### create a set to store the names of all - ### pending parameters, that is, parameters - ### which are still waiting for input from - ### other nodes - self.pending_param_names = set() - - ### create a map to store whether or not - ### input sockets are expecting input from - ### another node - self.expects_input_map = {} - - def perform_execution_setup(self): - """Clear input slots, set finished flag to False. - - Performed before and after a node execution. - """ - ### update the pending parameter names set with the - ### name of all existing parameters - - self.pending_param_names.update(self.signature_obj.parameters.keys()) - - ### clear the argument map - self.argument_map.clear() - - ### setup the expects input map - - self.expects_input_map.clear() - - for input_socket in self.input_sockets: - - ### use the parameter name as key - key = input_socket.parameter_name - - ### use the key to store whether the input - ### socket has a 'parent' attribute or not - ### in the map + ### set the clear method of argument map as the callable + ### for the 'perform_execution_setup' routine + self.perform_execution_setup = self.argument_map.clear - self.expects_input_map[key] = hasattr( - input_socket, - "parent", - ) + ### set an empty function as the 'perform_pre_execution_setups' + ### routine + self.perform_pre_execution_setups = empty_function - ### set a state as 'ready - self.state = "ready" + def check_and_setup_parameters(self): + """Check whether parameters lack a source of daa. - def check_pending_parameters(self): - """Perform checks on pending parameters. - - Here we check pending parameters to see whether we - - 1. should raise a WaitingInputException, meaning the - node must wait for inputs from other nodes before - executing. - 2. only if there's no incoming data from other - nodes, whether we should use data provided by the - user via embedded widgets or default values from - the callable itself. - 3. in case there's no source of data for a - required parameter, raise an MissingInputError - explaining the situation. + In case there's no source of data for one or more parameters, + we raise a MissingInputError explaining the situation. """ - ### create a set to hold names of parameters - ### which are not pending anymore - ready_param_names = set() - - ### create a list to hold names of (sub)parameters - ### waiting for input - waiting_input = [] - - ### create a list to hold names of (sub)parameters - ### lacking a data source + ### create a list to hold names of parameters lacking a data source lacking_data_source = [] - ### alias the argument map using a variable of - ### smaller character count for better code layout - arg_map = self.argument_map - - ### iterate over each pending parameter, performing + ### iterate over each parameter, performing ### checks and acting according to their results - for param_name in self.pending_param_names: - - ## if the parameter is present in the - ## argument map, we deem it ready - - if param_name in arg_map: - ready_param_names.add(param_name) - - ## if the parameter has data set to be - ## delivered from another node output, - ## we store its name on the 'waiting_input' - ## list - - elif self.expects_input_map[param_name]: - waiting_input.append(param_name) - - ## otherwise, it can only mean the node is - ## inexecutable due to lack of input sources; - ## we store its name on the - ## 'lacking_data_source' list - else: - lacking_data_source.append(param_name) + for input_socket in self.input_sockets: - ### remove ready parameter names from pending set - self.pending_param_names -= ready_param_names + if not hasattr(input_socket, 'parent'): + lacking_data_source.append(input_socket.parameter_name) ### if parameters lacking data sources are found, ### raise MissingInputError if lacking_data_source: - - raise MissingInputError( - self, - lacking_data_source, - ) - - ### if there are parameters waiting for input, - ### raise the WaitingInputException - - if waiting_input: - - raise WaitingInputException("node has parameters waiting for input") + raise MissingInputError(self, lacking_data_source) def receive_input( self,