CoolFace
Apppublic

fred-dev/comfy_ui_ali

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
execution.py1002 linesDownload Raw Back to root
1import sys2import copy3import logging4import threading5import heapq6import time7import traceback8from enum import Enum9import inspect10from typing import List, Literal, NamedTuple, Optional11 12import torch13import nodes14 15import comfy.model_management16from comfy_execution.graph import get_input_info, ExecutionList, DynamicPrompt, ExecutionBlocker17from comfy_execution.graph_utils import is_link, GraphBuilder18from comfy_execution.caching import HierarchicalCache, LRUCache, CacheKeySetInputSignature, CacheKeySetID19from comfy_execution.validation import validate_node_input20 21class ExecutionResult(Enum):22    SUCCESS = 023    FAILURE = 124    PENDING = 225 26class DuplicateNodeError(Exception):27    pass28 29class IsChangedCache:30    def __init__(self, dynprompt, outputs_cache):31        self.dynprompt = dynprompt32        self.outputs_cache = outputs_cache33        self.is_changed = {}34 35    def get(self, node_id):36        if node_id in self.is_changed:37            return self.is_changed[node_id]38 39        node = self.dynprompt.get_node(node_id)40        class_type = node["class_type"]41        class_def = nodes.NODE_CLASS_MAPPINGS[class_type]42        if not hasattr(class_def, "IS_CHANGED"):43            self.is_changed[node_id] = False44            return self.is_changed[node_id]45 46        if "is_changed" in node:47            self.is_changed[node_id] = node["is_changed"]48            return self.is_changed[node_id]49 50        # Intentionally do not use cached outputs here. We only want constants in IS_CHANGED51        input_data_all, _ = get_input_data(node["inputs"], class_def, node_id, None)52        try:53            is_changed = _map_node_over_list(class_def, input_data_all, "IS_CHANGED")54            node["is_changed"] = [None if isinstance(x, ExecutionBlocker) else x for x in is_changed]55        except Exception as e:56            logging.warning("WARNING: {}".format(e))57            node["is_changed"] = float("NaN")58        finally:59            self.is_changed[node_id] = node["is_changed"]60        return self.is_changed[node_id]61 62class CacheSet:63    def __init__(self, lru_size=None):64        if lru_size is None or lru_size == 0:65            self.init_classic_cache()66        else:67            self.init_lru_cache(lru_size)68        self.all = [self.outputs, self.ui, self.objects]69 70    # Useful for those with ample RAM/VRAM -- allows experimenting without71    # blowing away the cache every time72    def init_lru_cache(self, cache_size):73        self.outputs = LRUCache(CacheKeySetInputSignature, max_size=cache_size)74        self.ui = LRUCache(CacheKeySetInputSignature, max_size=cache_size)75        self.objects = HierarchicalCache(CacheKeySetID)76 77    # Performs like the old cache -- dump data ASAP78    def init_classic_cache(self):79        self.outputs = HierarchicalCache(CacheKeySetInputSignature)80        self.ui = HierarchicalCache(CacheKeySetInputSignature)81        self.objects = HierarchicalCache(CacheKeySetID)82 83    def recursive_debug_dump(self):84        result = {85            "outputs": self.outputs.recursive_debug_dump(),86            "ui": self.ui.recursive_debug_dump(),87        }88        return result89 90def get_input_data(inputs, class_def, unique_id, outputs=None, dynprompt=None, extra_data={}):91    valid_inputs = class_def.INPUT_TYPES()92    input_data_all = {}93    missing_keys = {}94    for x in inputs:95        input_data = inputs[x]96        input_type, input_category, input_info = get_input_info(class_def, x, valid_inputs)97        def mark_missing():98            missing_keys[x] = True99            input_data_all[x] = (None,)100        if is_link(input_data) and (not input_info or not input_info.get("rawLink", False)):101            input_unique_id = input_data[0]102            output_index = input_data[1]103            if outputs is None:104                mark_missing()105                continue # This might be a lazily-evaluated input106            cached_output = outputs.get(input_unique_id)107            if cached_output is None:108                mark_missing()109                continue110            if output_index >= len(cached_output):111                mark_missing()112                continue113            obj = cached_output[output_index]114            input_data_all[x] = obj115        elif input_category is not None:116            input_data_all[x] = [input_data]117 118    if "hidden" in valid_inputs:119        h = valid_inputs["hidden"]120        for x in h:121            if h[x] == "PROMPT":122                input_data_all[x] = [dynprompt.get_original_prompt() if dynprompt is not None else {}]123            if h[x] == "DYNPROMPT":124                input_data_all[x] = [dynprompt]125            if h[x] == "EXTRA_PNGINFO":126                input_data_all[x] = [extra_data.get('extra_pnginfo', None)]127            if h[x] == "UNIQUE_ID":128                input_data_all[x] = [unique_id]129    return input_data_all, missing_keys130 131map_node_over_list = None #Don't hook this please132 133def _map_node_over_list(obj, input_data_all, func, allow_interrupt=False, execution_block_cb=None, pre_execute_cb=None):134    # check if node wants the lists135    input_is_list = getattr(obj, "INPUT_IS_LIST", False)136 137    if len(input_data_all) == 0:138        max_len_input = 0139    else:140        max_len_input = max(len(x) for x in input_data_all.values())141 142    # get a slice of inputs, repeat last input when list isn't long enough143    def slice_dict(d, i):144        return {k: v[i if len(v) > i else -1] for k, v in d.items()}145 146    results = []147    def process_inputs(inputs, index=None, input_is_list=False):148        if allow_interrupt:149            nodes.before_node_execution()150        execution_block = None151        for k, v in inputs.items():152            if input_is_list:153                for e in v:154                    if isinstance(e, ExecutionBlocker):155                        v = e156                        break157            if isinstance(v, ExecutionBlocker):158                execution_block = execution_block_cb(v) if execution_block_cb else v159                break160        if execution_block is None:161            if pre_execute_cb is not None and index is not None:162                pre_execute_cb(index)163            results.append(getattr(obj, func)(**inputs))164        else:165            results.append(execution_block)166 167    if input_is_list:168        process_inputs(input_data_all, 0, input_is_list=input_is_list)169    elif max_len_input == 0:170        process_inputs({})171    else:172        for i in range(max_len_input):173            input_dict = slice_dict(input_data_all, i)174            process_inputs(input_dict, i)175    return results176 177def merge_result_data(results, obj):178    # check which outputs need concatenating179    output = []180    output_is_list = [False] * len(results[0])181    if hasattr(obj, "OUTPUT_IS_LIST"):182        output_is_list = obj.OUTPUT_IS_LIST183 184    # merge node execution results185    for i, is_list in zip(range(len(results[0])), output_is_list):186        if is_list:187            value = []188            for o in results:189                if isinstance(o[i], ExecutionBlocker):190                    value.append(o[i])191                else:192                    value.extend(o[i])193            output.append(value)194        else:195            output.append([o[i] for o in results])196    return output197 198def get_output_data(obj, input_data_all, execution_block_cb=None, pre_execute_cb=None):199    results = []200    uis = []201    subgraph_results = []202    return_values = _map_node_over_list(obj, input_data_all, obj.FUNCTION, allow_interrupt=True, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb)203    has_subgraph = False204    for i in range(len(return_values)):205        r = return_values[i]206        if isinstance(r, dict):207            if 'ui' in r:208                uis.append(r['ui'])209            if 'expand' in r:210                # Perform an expansion, but do not append results211                has_subgraph = True212                new_graph = r['expand']213                result = r.get("result", None)214                if isinstance(result, ExecutionBlocker):215                    result = tuple([result] * len(obj.RETURN_TYPES))216                subgraph_results.append((new_graph, result))217            elif 'result' in r:218                result = r.get("result", None)219                if isinstance(result, ExecutionBlocker):220                    result = tuple([result] * len(obj.RETURN_TYPES))221                results.append(result)222                subgraph_results.append((None, result))223        else:224            if isinstance(r, ExecutionBlocker):225                r = tuple([r] * len(obj.RETURN_TYPES))226            results.append(r)227            subgraph_results.append((None, r))228 229    if has_subgraph:230        output = subgraph_results231    elif len(results) > 0:232        output = merge_result_data(results, obj)233    else:234        output = []235    ui = dict()236    if len(uis) > 0:237        ui = {k: [y for x in uis for y in x[k]] for k in uis[0].keys()}238    return output, ui, has_subgraph239 240def format_value(x):241    if x is None:242        return None243    elif isinstance(x, (int, float, bool, str)):244        return x245    else:246        return str(x)247 248def execute(server, dynprompt, caches, current_item, extra_data, executed, prompt_id, execution_list, pending_subgraph_results):249    unique_id = current_item250    real_node_id = dynprompt.get_real_node_id(unique_id)251    display_node_id = dynprompt.get_display_node_id(unique_id)252    parent_node_id = dynprompt.get_parent_node_id(unique_id)253    inputs = dynprompt.get_node(unique_id)['inputs']254    class_type = dynprompt.get_node(unique_id)['class_type']255    class_def = nodes.NODE_CLASS_MAPPINGS[class_type]256    if caches.outputs.get(unique_id) is not None:257        if server.client_id is not None:258            cached_output = caches.ui.get(unique_id) or {}259            server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": cached_output.get("output",None), "prompt_id": prompt_id }, server.client_id)260        return (ExecutionResult.SUCCESS, None, None)261 262    input_data_all = None263    try:264        if unique_id in pending_subgraph_results:265            cached_results = pending_subgraph_results[unique_id]266            resolved_outputs = []267            for is_subgraph, result in cached_results:268                if not is_subgraph:269                    resolved_outputs.append(result)270                else:271                    resolved_output = []272                    for r in result:273                        if is_link(r):274                            source_node, source_output = r[0], r[1]275                            node_output = caches.outputs.get(source_node)[source_output]276                            for o in node_output:277                                resolved_output.append(o)278 279                        else:280                            resolved_output.append(r)281                    resolved_outputs.append(tuple(resolved_output))282            output_data = merge_result_data(resolved_outputs, class_def)283            output_ui = []284            has_subgraph = False285        else:286            input_data_all, missing_keys = get_input_data(inputs, class_def, unique_id, caches.outputs, dynprompt, extra_data)287            if server.client_id is not None:288                server.last_node_id = display_node_id289                server.send_sync("executing", { "node": unique_id, "display_node": display_node_id, "prompt_id": prompt_id }, server.client_id)290 291            obj = caches.objects.get(unique_id)292            if obj is None:293                obj = class_def()294                caches.objects.set(unique_id, obj)295 296            if hasattr(obj, "check_lazy_status"):297                required_inputs = _map_node_over_list(obj, input_data_all, "check_lazy_status", allow_interrupt=True)298                required_inputs = set(sum([r for r in required_inputs if isinstance(r,list)], []))299                required_inputs = [x for x in required_inputs if isinstance(x,str) and (300                    x not in input_data_all or x in missing_keys301                )]302                if len(required_inputs) > 0:303                    for i in required_inputs:304                        execution_list.make_input_strong_link(unique_id, i)305                    return (ExecutionResult.PENDING, None, None)306 307            def execution_block_cb(block):308                if block.message is not None:309                    mes = {310                        "prompt_id": prompt_id,311                        "node_id": unique_id,312                        "node_type": class_type,313                        "executed": list(executed),314 315                        "exception_message": f"Execution Blocked: {block.message}",316                        "exception_type": "ExecutionBlocked",317                        "traceback": [],318                        "current_inputs": [],319                        "current_outputs": [],320                    }321                    server.send_sync("execution_error", mes, server.client_id)322                    return ExecutionBlocker(None)323                else:324                    return block325            def pre_execute_cb(call_index):326                GraphBuilder.set_default_prefix(unique_id, call_index, 0)327            output_data, output_ui, has_subgraph = get_output_data(obj, input_data_all, execution_block_cb=execution_block_cb, pre_execute_cb=pre_execute_cb)328        if len(output_ui) > 0:329            caches.ui.set(unique_id, {330                "meta": {331                    "node_id": unique_id,332                    "display_node": display_node_id,333                    "parent_node": parent_node_id,334                    "real_node_id": real_node_id,335                },336                "output": output_ui337            })338            if server.client_id is not None:339                server.send_sync("executed", { "node": unique_id, "display_node": display_node_id, "output": output_ui, "prompt_id": prompt_id }, server.client_id)340        if has_subgraph:341            cached_outputs = []342            new_node_ids = []343            new_output_ids = []344            new_output_links = []345            for i in range(len(output_data)):346                new_graph, node_outputs = output_data[i]347                if new_graph is None:348                    cached_outputs.append((False, node_outputs))349                else:350                    # Check for conflicts351                    for node_id in new_graph.keys():352                        if dynprompt.has_node(node_id):353                            raise DuplicateNodeError(f"Attempt to add duplicate node {node_id}. Ensure node ids are unique and deterministic or use graph_utils.GraphBuilder.")354                    for node_id, node_info in new_graph.items():355                        new_node_ids.append(node_id)356                        display_id = node_info.get("override_display_id", unique_id)357                        dynprompt.add_ephemeral_node(node_id, node_info, unique_id, display_id)358                        # Figure out if the newly created node is an output node359                        class_type = node_info["class_type"]360                        class_def = nodes.NODE_CLASS_MAPPINGS[class_type]361                        if hasattr(class_def, 'OUTPUT_NODE') and class_def.OUTPUT_NODE == True:362                            new_output_ids.append(node_id)363                    for i in range(len(node_outputs)):364                        if is_link(node_outputs[i]):365                            from_node_id, from_socket = node_outputs[i][0], node_outputs[i][1]366                            new_output_links.append((from_node_id, from_socket))367                    cached_outputs.append((True, node_outputs))368            new_node_ids = set(new_node_ids)369            for cache in caches.all:370                cache.ensure_subcache_for(unique_id, new_node_ids).clean_unused()371            for node_id in new_output_ids:372                execution_list.add_node(node_id)373            for link in new_output_links:374                execution_list.add_strong_link(link[0], link[1], unique_id)375            pending_subgraph_results[unique_id] = cached_outputs376            return (ExecutionResult.PENDING, None, None)377        caches.outputs.set(unique_id, output_data)378    except comfy.model_management.InterruptProcessingException as iex:379        logging.info("Processing interrupted")380 381        # skip formatting inputs/outputs382        error_details = {383            "node_id": real_node_id,384        }385 386        return (ExecutionResult.FAILURE, error_details, iex)387    except Exception as ex:388        typ, _, tb = sys.exc_info()389        exception_type = full_type_name(typ)390        input_data_formatted = {}391        if input_data_all is not None:392            input_data_formatted = {}393            for name, inputs in input_data_all.items():394                input_data_formatted[name] = [format_value(x) for x in inputs]395 396        logging.error(f"!!! Exception during processing !!! {ex}")397        logging.error(traceback.format_exc())398 399        error_details = {400            "node_id": real_node_id,401            "exception_message": str(ex),402            "exception_type": exception_type,403            "traceback": traceback.format_tb(tb),404            "current_inputs": input_data_formatted405        }406        if isinstance(ex, comfy.model_management.OOM_EXCEPTION):407            logging.error("Got an OOM, unloading all loaded models.")408            comfy.model_management.unload_all_models()409 410        return (ExecutionResult.FAILURE, error_details, ex)411 412    executed.add(unique_id)413 414    return (ExecutionResult.SUCCESS, None, None)415 416class PromptExecutor:417    def __init__(self, server, lru_size=None):418        self.lru_size = lru_size419        self.server = server420        self.reset()421 422    def reset(self):423        self.caches = CacheSet(self.lru_size)424        self.status_messages = []425        self.success = True426 427    def add_message(self, event, data: dict, broadcast: bool):428        data = {429            **data,430            "timestamp": int(time.time() * 1000),431        }432        self.status_messages.append((event, data))433        if self.server.client_id is not None or broadcast:434            self.server.send_sync(event, data, self.server.client_id)435 436    def handle_execution_error(self, prompt_id, prompt, current_outputs, executed, error, ex):437        node_id = error["node_id"]438        class_type = prompt[node_id]["class_type"]439 440        # First, send back the status to the frontend depending441        # on the exception type442        if isinstance(ex, comfy.model_management.InterruptProcessingException):443            mes = {444                "prompt_id": prompt_id,445                "node_id": node_id,446                "node_type": class_type,447                "executed": list(executed),448            }449            self.add_message("execution_interrupted", mes, broadcast=True)450        else:451            mes = {452                "prompt_id": prompt_id,453                "node_id": node_id,454                "node_type": class_type,455                "executed": list(executed),456                "exception_message": error["exception_message"],457                "exception_type": error["exception_type"],458                "traceback": error["traceback"],459                "current_inputs": error["current_inputs"],460                "current_outputs": list(current_outputs),461            }462            self.add_message("execution_error", mes, broadcast=False)463 464    def execute(self, prompt, prompt_id, extra_data={}, execute_outputs=[]):465        nodes.interrupt_processing(False)466 467        if "client_id" in extra_data:468            self.server.client_id = extra_data["client_id"]469        else:470            self.server.client_id = None471 472        self.status_messages = []473        self.add_message("execution_start", { "prompt_id": prompt_id}, broadcast=False)474 475        with torch.inference_mode():476            dynamic_prompt = DynamicPrompt(prompt)477            is_changed_cache = IsChangedCache(dynamic_prompt, self.caches.outputs)478            for cache in self.caches.all:479                cache.set_prompt(dynamic_prompt, prompt.keys(), is_changed_cache)480                cache.clean_unused()481 482            cached_nodes = []483            for node_id in prompt:484                if self.caches.outputs.get(node_id) is not None:485                    cached_nodes.append(node_id)486 487            comfy.model_management.cleanup_models_gc()488            self.add_message("execution_cached",489                          { "nodes": cached_nodes, "prompt_id": prompt_id},490                          broadcast=False)491            pending_subgraph_results = {}492            executed = set()493            execution_list = ExecutionList(dynamic_prompt, self.caches.outputs)494            current_outputs = self.caches.outputs.all_node_ids()495            for node_id in list(execute_outputs):496                execution_list.add_node(node_id)497 498            while not execution_list.is_empty():499                node_id, error, ex = execution_list.stage_node_execution()500                if error is not None:501                    self.handle_execution_error(prompt_id, dynamic_prompt.original_prompt, current_outputs, executed, error, ex)502                    break503 504                result, error, ex = execute(self.server, dynamic_prompt, self.caches, node_id, extra_data, executed, prompt_id, execution_list, pending_subgraph_results)505                self.success = result != ExecutionResult.FAILURE506                if result == ExecutionResult.FAILURE:507                    self.handle_execution_error(prompt_id, dynamic_prompt.original_prompt, current_outputs, executed, error, ex)508                    break509                elif result == ExecutionResult.PENDING:510                    execution_list.unstage_node_execution()511                else: # result == ExecutionResult.SUCCESS:512                    execution_list.complete_node_execution()513            else:514                # Only execute when the while-loop ends without break515                self.add_message("execution_success", { "prompt_id": prompt_id }, broadcast=False)516 517            ui_outputs = {}518            meta_outputs = {}519            all_node_ids = self.caches.ui.all_node_ids()520            for node_id in all_node_ids:521                ui_info = self.caches.ui.get(node_id)522                if ui_info is not None:523                    ui_outputs[node_id] = ui_info["output"]524                    meta_outputs[node_id] = ui_info["meta"]525            self.history_result = {526                "outputs": ui_outputs,527                "meta": meta_outputs,528            }529            self.server.last_node_id = None530            if comfy.model_management.DISABLE_SMART_MEMORY:531                comfy.model_management.unload_all_models()532 533 534def validate_inputs(prompt, item, validated):535    unique_id = item536    if unique_id in validated:537        return validated[unique_id]538 539    inputs = prompt[unique_id]['inputs']540    class_type = prompt[unique_id]['class_type']541    obj_class = nodes.NODE_CLASS_MAPPINGS[class_type]542 543    class_inputs = obj_class.INPUT_TYPES()544    valid_inputs = set(class_inputs.get('required',{})).union(set(class_inputs.get('optional',{})))545 546    errors = []547    valid = True548 549    validate_function_inputs = []550    validate_has_kwargs = False551    if hasattr(obj_class, "VALIDATE_INPUTS"):552        argspec = inspect.getfullargspec(obj_class.VALIDATE_INPUTS)553        validate_function_inputs = argspec.args554        validate_has_kwargs = argspec.varkw is not None555    received_types = {}556 557    for x in valid_inputs:558        type_input, input_category, extra_info = get_input_info(obj_class, x, class_inputs)559        assert extra_info is not None560        if x not in inputs:561            if input_category == "required":562                error = {563                    "type": "required_input_missing",564                    "message": "Required input is missing",565                    "details": f"{x}",566                    "extra_info": {567                        "input_name": x568                    }569                }570                errors.append(error)571            continue572 573        val = inputs[x]574        info = (type_input, extra_info)575        if isinstance(val, list):576            if len(val) != 2:577                error = {578                    "type": "bad_linked_input",579                    "message": "Bad linked input, must be a length-2 list of [node_id, slot_index]",580                    "details": f"{x}",581                    "extra_info": {582                        "input_name": x,583                        "input_config": info,584                        "received_value": val585                    }586                }587                errors.append(error)588                continue589 590            o_id = val[0]591            o_class_type = prompt[o_id]['class_type']592            r = nodes.NODE_CLASS_MAPPINGS[o_class_type].RETURN_TYPES593            received_type = r[val[1]]594            received_types[x] = received_type595            if 'input_types' not in validate_function_inputs and not validate_node_input(received_type, type_input):596                details = f"{x}, received_type({received_type}) mismatch input_type({type_input})"597                error = {598                    "type": "return_type_mismatch",599                    "message": "Return type mismatch between linked nodes",600                    "details": details,601                    "extra_info": {602                        "input_name": x,603                        "input_config": info,604                        "received_type": received_type,605                        "linked_node": val606                    }607                }608                errors.append(error)609                continue610            try:611                r = validate_inputs(prompt, o_id, validated)612                if r[0] is False:613                    # `r` will be set in `validated[o_id]` already614                    valid = False615                    continue616            except Exception as ex:617                typ, _, tb = sys.exc_info()618                valid = False619                exception_type = full_type_name(typ)620                reasons = [{621                    "type": "exception_during_inner_validation",622                    "message": "Exception when validating inner node",623                    "details": str(ex),624                    "extra_info": {625                        "input_name": x,626                        "input_config": info,627                        "exception_message": str(ex),628                        "exception_type": exception_type,629                        "traceback": traceback.format_tb(tb),630                        "linked_node": val631                    }632                }]633                validated[o_id] = (False, reasons, o_id)634                continue635        else:636            try:637                # Unwraps values wrapped in __value__ key. This is used to pass638                # list widget value to execution, as by default list value is639                # reserved to represent the connection between nodes.640                if isinstance(val, dict) and "__value__" in val:641                    val = val["__value__"]642                    inputs[x] = val643 644                if type_input == "INT":645                    val = int(val)646                    inputs[x] = val647                if type_input == "FLOAT":648                    val = float(val)649                    inputs[x] = val650                if type_input == "STRING":651                    val = str(val)652                    inputs[x] = val653                if type_input == "BOOLEAN":654                    val = bool(val)655                    inputs[x] = val656            except Exception as ex:657                error = {658                    "type": "invalid_input_type",659                    "message": f"Failed to convert an input value to a {type_input} value",660                    "details": f"{x}, {val}, {ex}",661                    "extra_info": {662                        "input_name": x,663                        "input_config": info,664                        "received_value": val,665                        "exception_message": str(ex)666                    }667                }668                errors.append(error)669                continue670 671            if x not in validate_function_inputs and not validate_has_kwargs:672                if "min" in extra_info and val < extra_info["min"]:673                    error = {674                        "type": "value_smaller_than_min",675                        "message": "Value {} smaller than min of {}".format(val, extra_info["min"]),676                        "details": f"{x}",677                        "extra_info": {678                            "input_name": x,679                            "input_config": info,680                            "received_value": val,681                        }682                    }683                    errors.append(error)684                    continue685                if "max" in extra_info and val > extra_info["max"]:686                    error = {687                        "type": "value_bigger_than_max",688                        "message": "Value {} bigger than max of {}".format(val, extra_info["max"]),689                        "details": f"{x}",690                        "extra_info": {691                            "input_name": x,692                            "input_config": info,693                            "received_value": val,694                        }695                    }696                    errors.append(error)697                    continue698 699                if isinstance(type_input, list):700                    if val not in type_input:701                        input_config = info702                        list_info = ""703 704                        # Don't send back gigantic lists like if they're lots of705                        # scanned model filepaths706                        if len(type_input) > 20:707                            list_info = f"(list of length {len(type_input)})"708                            input_config = None709                        else:710                            list_info = str(type_input)711 712                        error = {713                            "type": "value_not_in_list",714                            "message": "Value not in list",715                            "details": f"{x}: '{val}' not in {list_info}",716                            "extra_info": {717                                "input_name": x,718                                "input_config": input_config,719                                "received_value": val,720                            }721                        }722                        errors.append(error)723                        continue724 725    if len(validate_function_inputs) > 0 or validate_has_kwargs:726        input_data_all, _ = get_input_data(inputs, obj_class, unique_id)727        input_filtered = {}728        for x in input_data_all:729            if x in validate_function_inputs or validate_has_kwargs:730                input_filtered[x] = input_data_all[x]731        if 'input_types' in validate_function_inputs:732            input_filtered['input_types'] = [received_types]733 734        #ret = obj_class.VALIDATE_INPUTS(**input_filtered)735        ret = _map_node_over_list(obj_class, input_filtered, "VALIDATE_INPUTS")736        for x in input_filtered:737            for i, r in enumerate(ret):738                if r is not True and not isinstance(r, ExecutionBlocker):739                    details = f"{x}"740                    if r is not False:741                        details += f" - {str(r)}"742 743                    error = {744                        "type": "custom_validation_failed",745                        "message": "Custom validation failed for node",746                        "details": details,747                        "extra_info": {748                            "input_name": x,749                        }750                    }751                    errors.append(error)752                    continue753 754    if len(errors) > 0 or valid is not True:755        ret = (False, errors, unique_id)756    else:757        ret = (True, [], unique_id)758 759    validated[unique_id] = ret760    return ret761 762def full_type_name(klass):763    module = klass.__module__764    if module == 'builtins':765        return klass.__qualname__766    return module + '.' + klass.__qualname__767 768def validate_prompt(prompt):769    outputs = set()770    for x in prompt:771        if 'class_type' not in prompt[x]:772            error = {773                "type": "invalid_prompt",774                "message": "Cannot execute because a node is missing the class_type property.",775                "details": f"Node ID '#{x}'",776                "extra_info": {}777            }778            return (False, error, [], [])779 780        class_type = prompt[x]['class_type']781        class_ = nodes.NODE_CLASS_MAPPINGS.get(class_type, None)782        if class_ is None:783            error = {784                "type": "invalid_prompt",785                "message": f"Cannot execute because node {class_type} does not exist.",786                "details": f"Node ID '#{x}'",787                "extra_info": {}788            }789            return (False, error, [], [])790 791        if hasattr(class_, 'OUTPUT_NODE') and class_.OUTPUT_NODE is True:792            outputs.add(x)793 794    if len(outputs) == 0:795        error = {796            "type": "prompt_no_outputs",797            "message": "Prompt has no outputs",798            "details": "",799            "extra_info": {}800        }801        return (False, error, [], [])802 803    good_outputs = set()804    errors = []805    node_errors = {}806    validated = {}807    for o in outputs:808        valid = False809        reasons = []810        try:811            m = validate_inputs(prompt, o, validated)812            valid = m[0]813            reasons = m[1]814        except Exception as ex:815            typ, _, tb = sys.exc_info()816            valid = False817            exception_type = full_type_name(typ)818            reasons = [{819                "type": "exception_during_validation",820                "message": "Exception when validating node",821                "details": str(ex),822                "extra_info": {823                    "exception_type": exception_type,824                    "traceback": traceback.format_tb(tb)825                }826            }]827            validated[o] = (False, reasons, o)828 829        if valid is True:830            good_outputs.add(o)831        else:832            logging.error(f"Failed to validate prompt for output {o}:")833            if len(reasons) > 0:834                logging.error("* (prompt):")835                for reason in reasons:836                    logging.error(f"  - {reason['message']}: {reason['details']}")837            errors += [(o, reasons)]838            for node_id, result in validated.items():839                valid = result[0]840                reasons = result[1]841                # If a node upstream has errors, the nodes downstream will also842                # be reported as invalid, but there will be no errors attached.843                # So don't return those nodes as having errors in the response.844                if valid is not True and len(reasons) > 0:845                    if node_id not in node_errors:846                        class_type = prompt[node_id]['class_type']847                        node_errors[node_id] = {848                            "errors": reasons,849                            "dependent_outputs": [],850                            "class_type": class_type851                        }852                        logging.error(f"* {class_type} {node_id}:")853                        for reason in reasons:854                            logging.error(f"  - {reason['message']}: {reason['details']}")855                    node_errors[node_id]["dependent_outputs"].append(o)856            logging.error("Output will be ignored")857 858    if len(good_outputs) == 0:859        errors_list = []860        for o, errors in errors:861            for error in errors:862                errors_list.append(f"{error['message']}: {error['details']}")863        errors_list = "\n".join(errors_list)864 865        error = {866            "type": "prompt_outputs_failed_validation",867            "message": "Prompt outputs failed validation",868            "details": errors_list,869            "extra_info": {}870        }871 872        return (False, error, list(good_outputs), node_errors)873 874    return (True, None, list(good_outputs), node_errors)875 876MAXIMUM_HISTORY_SIZE = 10000877 878class PromptQueue:879    def __init__(self, server):880        self.server = server881        self.mutex = threading.RLock()882        self.not_empty = threading.Condition(self.mutex)883        self.task_counter = 0884        self.queue = []885        self.currently_running = {}886        self.history = {}887        self.flags = {}888        server.prompt_queue = self889 890    def put(self, item):891        with self.mutex:892            heapq.heappush(self.queue, item)893            self.server.queue_updated()894            self.not_empty.notify()895 896    def get(self, timeout=None):897        with self.not_empty:898            while len(self.queue) == 0:899                self.not_empty.wait(timeout=timeout)900                if timeout is not None and len(self.queue) == 0:901                    return None902            item = heapq.heappop(self.queue)903            i = self.task_counter904            self.currently_running[i] = copy.deepcopy(item)905            self.task_counter += 1906            self.server.queue_updated()907            return (item, i)908 909    class ExecutionStatus(NamedTuple):910        status_str: Literal['success', 'error']911        completed: bool912        messages: List[str]913 914    def task_done(self, item_id, history_result,915                  status: Optional['PromptQueue.ExecutionStatus']):916        with self.mutex:917            prompt = self.currently_running.pop(item_id)918            if len(self.history) > MAXIMUM_HISTORY_SIZE:919                self.history.pop(next(iter(self.history)))920 921            status_dict: Optional[dict] = None922            if status is not None:923                status_dict = copy.deepcopy(status._asdict())924 925            self.history[prompt[1]] = {926                "prompt": prompt,927                "outputs": {},928                'status': status_dict,929            }930            self.history[prompt[1]].update(history_result)931            self.server.queue_updated()932 933    def get_current_queue(self):934        with self.mutex:935            out = []936            for x in self.currently_running.values():937                out += [x]938            return (out, copy.deepcopy(self.queue))939 940    def get_tasks_remaining(self):941        with self.mutex:942            return len(self.queue) + len(self.currently_running)943 944    def wipe_queue(self):945        with self.mutex:946            self.queue = []947            self.server.queue_updated()948 949    def delete_queue_item(self, function):950        with self.mutex:951            for x in range(len(self.queue)):952                if function(self.queue[x]):953                    if len(self.queue) == 1:954                        self.wipe_queue()955                    else:956                        self.queue.pop(x)957                        heapq.heapify(self.queue)958                    self.server.queue_updated()959                    return True960        return False961 962    def get_history(self, prompt_id=None, max_items=None, offset=-1):963        with self.mutex:964            if prompt_id is None:965                out = {}966                i = 0967                if offset < 0 and max_items is not None:968                    offset = len(self.history) - max_items969                for k in self.history:970                    if i >= offset:971                        out[k] = self.history[k]972                        if max_items is not None and len(out) >= max_items:973                            break974                    i += 1975                return out976            elif prompt_id in self.history:977                return {prompt_id: copy.deepcopy(self.history[prompt_id])}978            else:979                return {}980 981    def wipe_history(self):982        with self.mutex:983            self.history = {}984 985    def delete_history_item(self, id_to_delete):986        with self.mutex:987            self.history.pop(id_to_delete, None)988 989    def set_flag(self, name, data):990        with self.mutex:991            self.flags[name] = data992            self.not_empty.notify()993 994    def get_flags(self, reset=True):995        with self.mutex:996            if reset:997                ret = self.flags998                self.flags = {}999                return ret1000            else:1001                return self.flags.copy()1002