Download ComfyUI-MiniMaxH3-Context-Loop/tools/workflow_schema.py from dzmdd/Fast_deploy: direct link, hf CLI and curl.
- Browser
- Download file 5.13 kB
-
https://huggingface.co/dzmdd/Fast_deploy/resolve/main/ComfyUI-MiniMaxH3-Context-Loop/tools/workflow_schema.py
- Command line
-
hf download hf://dzmdd/Fast_deploy/ComfyUI-MiniMaxH3-Context-Loop/tools/workflow_schema.py
-
curl -L -o workflow_schema.py https://huggingface.co/dzmdd/Fast_deploy/resolve/main/ComfyUI-MiniMaxH3-Context-Loop/tools/workflow_schema.py
5.13 kB
| """Offline release-workflow contracts. Local H3 definitions are authoritative. | |
| External contracts are a sanitized snapshot: no local model/media inventory. | |
| This imports schema methods only; it never loads weights or starts ComfyUI. | |
| Run as a standalone tooling process because ComfyUI imports are stubbed. | |
| """ | |
| from __future__ import annotations | |
| import copy | |
| import importlib | |
| import json | |
| import sys | |
| import types | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parents[1] | |
| DATA = ROOT / 'tools' / 'v06' | |
| PRIMITIVES = {'INT', 'FLOAT', 'STRING', 'BOOLEAN', 'COMBO'} | |
| def load_schemas(): | |
| package_name = '_h3_release_workflow_schema' | |
| package = types.ModuleType(package_name) | |
| package.__path__ = [str(ROOT)] | |
| sys.modules[package_name] = package | |
| folder_paths = types.ModuleType('folder_paths') | |
| for name in ('get_output_directory', 'get_temp_directory', 'get_input_directory'): | |
| setattr(folder_paths, name, lambda: str(ROOT)) | |
| folder_paths.get_annotated_filepath = lambda value: str(value) | |
| sys.modules['folder_paths'] = folder_paths | |
| for name in ('comfy', 'comfy.utils', 'comfy.ldm', 'comfy.ldm.minimax', | |
| 'comfy.ldm.minimax.model', 'comfy.model_base', 'node_helpers'): | |
| sys.modules.setdefault(name, types.ModuleType(name)) | |
| if '.' in name: | |
| parent, child = name.rsplit('.', 1) | |
| setattr(sys.modules[parent], child, sys.modules[name]) | |
| classes = {} | |
| for module, mapping in ( | |
| ('nodes', 'NODE_CLASS_MAPPINGS'), | |
| ('chain_nodes', 'CHAIN_NODE_CLASS_MAPPINGS'), | |
| ('upscale_nodes', 'UPSCALE_NODE_CLASS_MAPPINGS'), | |
| ('masking_nodes', 'NODE_CLASS_MAPPINGS'), | |
| ('masked_bridge', 'NODE_CLASS_MAPPINGS'), | |
| ('source_av_target', 'NODE_CLASS_MAPPINGS')): | |
| imported = importlib.import_module(package_name + '.' + module) | |
| classes.update(getattr(imported, mapping)) | |
| schemas = json.loads((DATA / 'external_schemas.json').read_text())['nodes'] | |
| for name, cls in classes.items(): | |
| inputs = cls.INPUT_TYPES() | |
| schemas[name] = { | |
| 'input': inputs, 'output': list(cls.RETURN_TYPES), | |
| 'output_name': list(getattr(cls, 'RETURN_NAMES', cls.RETURN_TYPES)), | |
| 'output_is_list': list(getattr(cls, 'OUTPUT_IS_LIST', [False]*len(cls.RETURN_TYPES))), | |
| } | |
| # Match /object_info serialization, including wildcard str subclasses whose | |
| # overloaded comparisons must not be used as schema-type comparisons. | |
| return json.loads(json.dumps(schemas)) | |
| def fields(schema): | |
| return {**schema['input'].get('required', {}), **schema['input'].get('optional', {})} | |
| def options(spec): | |
| return spec[1] if len(spec) > 1 else {} | |
| def is_widget(spec): | |
| return (isinstance(spec[0], (list, tuple)) or spec[0] in PRIMITIVES | |
| or spec[0] == 'COMFY_DYNAMICCOMBO_V3') and not options(spec).get('forceInput') | |
| def choices(spec): | |
| if isinstance(spec[0], (list, tuple)): | |
| return list(spec[0]) | |
| return options(spec).get('options') if spec[0] == 'COMBO' else None | |
| def default(spec): | |
| opts = options(spec) | |
| if 'default' in opts: | |
| return copy.deepcopy(opts['default']) | |
| values = choices(spec) | |
| if values: | |
| return values[0] | |
| return {'STRING':'', 'BOOLEAN':False, 'INT':0, 'FLOAT':0.0}.get(spec[0], '') | |
| def widget_fields(schema, values): | |
| """Include converted widgets, control-after-generate and V3 dynamic children.""" | |
| for name, spec in fields(schema).items(): | |
| if not is_widget(spec): | |
| continue | |
| if spec[0] == 'COMFY_DYNAMICCOMBO_V3': | |
| variants = options(spec)['options'] | |
| yield name, [[v['key'] for v in variants], {}] | |
| chosen = values.get(name, variants[0]['key']) | |
| variant = next(v for v in variants if v['key'] == chosen) | |
| for child, child_spec in widget_fields({'input':variant['inputs']}, values): | |
| yield name + '.' + child, child_spec | |
| else: | |
| yield name, spec | |
| if options(spec).get('control_after_generate') or name in ('seed', 'noise_seed'): | |
| yield 'control_after_generate', [['fixed', 'increment', 'decrement', 'randomize'], {'default':'fixed'}] | |
| def validate_value(value, spec, label): | |
| kind, opts = spec[0], options(spec) | |
| values = choices(spec) | |
| if values is not None: | |
| assert value in values, (label, value, 'not in allowed choices', values) | |
| elif kind == 'INT': | |
| assert isinstance(value, int) and not isinstance(value, bool), (label, value, 'expected integer') | |
| elif kind == 'FLOAT': | |
| assert isinstance(value, (int, float)) and not isinstance(value, bool), (label, value, 'expected number') | |
| elif kind == 'BOOLEAN': | |
| assert isinstance(value, bool), (label, value, 'expected boolean') | |
| elif kind == 'STRING': | |
| assert isinstance(value, str), (label, value, 'expected string') | |
| if kind in ('INT', 'FLOAT') and isinstance(value, (int, float)): | |
| assert opts.get('min', float('-inf')) <= value <= opts.get('max', float('inf')), (label, value, 'out of range') | |