comfy-ui-api-workflow 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comfy_ui_api_workflow/__about__.py +1 -0
- comfy_ui_api_workflow/__init__.py +9 -0
- comfy_ui_api_workflow/_impl/__init__.py +0 -0
- comfy_ui_api_workflow/_impl/data_structure.py +182 -0
- comfy_ui_api_workflow/_impl/enums.py +13 -0
- comfy_ui_api_workflow/_impl/errors/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/errors/data_structure.py +34 -0
- comfy_ui_api_workflow/_impl/errors/node.py +63 -0
- comfy_ui_api_workflow/_impl/errors/output.py +5 -0
- comfy_ui_api_workflow/_impl/errors/pipeline.py +14 -0
- comfy_ui_api_workflow/_impl/errors/workflow.py +13 -0
- comfy_ui_api_workflow/_impl/exceptions.py +22 -0
- comfy_ui_api_workflow/_impl/lazy.py +71 -0
- comfy_ui_api_workflow/_impl/nodes/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/debug.py +103 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/guidance.py +281 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/__init__.py +29 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/clip.py +35 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/combine.py +108 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_pair.py +203 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/cond_single.py +143 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/create.py +130 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/hooks/scheduling.py +167 -0
- comfy_ui_api_workflow/_impl/nodes/advanced/multigpu.py +78 -0
- comfy_ui_api_workflow/_impl/nodes/audio.py +260 -0
- comfy_ui_api_workflow/_impl/nodes/base.py +214 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/__init__.py +283 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/attention_experiments.py +156 -0
- comfy_ui_api_workflow/_impl/nodes/experimental/stable_cascade.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/image/__init__.py +601 -0
- comfy_ui_api_workflow/_impl/nodes/image/adjustments.py +46 -0
- comfy_ui_api_workflow/_impl/nodes/image/background_removal.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/image/batch.py +236 -0
- comfy_ui_api_workflow/_impl/nodes/image/color.py +104 -0
- comfy_ui_api_workflow/_impl/nodes/image/compositing.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/image/detection.py +689 -0
- comfy_ui_api_workflow/_impl/nodes/image/filters.py +215 -0
- comfy_ui_api_workflow/_impl/nodes/image/geometry_estimation.py +307 -0
- comfy_ui_api_workflow/_impl/nodes/image/mask.py +291 -0
- comfy_ui_api_workflow/_impl/nodes/image/post_processors.py +45 -0
- comfy_ui_api_workflow/_impl/nodes/image/pre_processors.py +13 -0
- comfy_ui_api_workflow/_impl/nodes/image/transform.py +385 -0
- comfy_ui_api_workflow/_impl/nodes/image/upscaling.py +188 -0
- comfy_ui_api_workflow/_impl/nodes/image/video.py +66 -0
- comfy_ui_api_workflow/_impl/nodes/model/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/__init__.py +395 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/ace.py +232 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/autoregressive.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/controlnet.py +153 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/cosmos.py +108 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/flux.py +121 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/gligen.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hidream.py +31 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_3d.py +44 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_image.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/hunyuan_video.py +243 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/instructpix2pix.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/kandinsky.py +78 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/lotus.py +8 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/ltxv.py +504 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/lumina.py +39 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax.py +110 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/minimax_music.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/photomaker.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/pixart.py +34 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/qwen_image.py +101 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_audio.py +34 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_cascade.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion.py +141 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_diffusion_upscaler.py +37 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_video_3d.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/stable_zero123.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/transform.py +341 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/trellis.py +168 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/triposplat.py +54 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/void.py +61 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/__init__.py +184 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/animate.py +255 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/camera.py +150 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/dancer.py +102 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_control.py +129 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/fun_inpaint.py +68 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/humo.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/move.py +332 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/phantom_subject.py +58 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/scail.py +161 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/sound.py +128 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/wan/vace.py +73 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/yue2.py +189 -0
- comfy_ui_api_workflow/_impl/nodes/model/conditioning/z_image.py +59 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/__init__.py +510 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/ace.py +49 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/__init__.py +210 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/advanced/operations.py +113 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/autoregressive.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/batch.py +93 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/chroma_radiance.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/cosmos.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/flux.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hidream.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_3d.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_image.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/hunyuan_video.py +130 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/ltxv.py +162 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/minimax.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/minimax_music.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/mochi.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/qwen.py +36 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/seedvr.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/stable_cascade.py +60 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/stable_diffusion.py +28 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/transform.py +92 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/trellis.py +91 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/triposplat.py +86 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/void.py +54 -0
- comfy_ui_api_workflow/_impl/nodes/model/latent/yue2.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/model/loaders.py +820 -0
- comfy_ui_api_workflow/_impl/nodes/model/merging/__init__.py +290 -0
- comfy_ui_api_workflow/_impl/nodes/model/merging/model_specific.py +4370 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/__init__.py +463 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/anima.py +53 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/chroma_radiance.py +50 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/flux.py +63 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/hidream.py +72 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/ltxv.py +125 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/minimax.py +92 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/qwen.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/sensenova.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/stable_cascade.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/stable_diffusion.py +21 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/supir.py +57 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/unet.py +281 -0
- comfy_ui_api_workflow/_impl/nodes/model/patch/wan.py +143 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/__init__.py +228 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/custom.py +123 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/guiders.py +252 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/noise.py +52 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/samplers.py +531 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/schedulers.py +462 -0
- comfy_ui_api_workflow/_impl/nodes/model/sampling/sigmas.py +163 -0
- comfy_ui_api_workflow/_impl/nodes/model/training.py +249 -0
- comfy_ui_api_workflow/_impl/nodes/partner/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/audio/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/audio/elevenlabs.py +47 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/bytedance.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/krea.py +31 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/luma.py +27 -0
- comfy_ui_api_workflow/_impl/nodes/partner/image/recraft.py +220 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/gemini.py +25 -0
- comfy_ui_api_workflow/_impl/nodes/partner/text/openai.py +77 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/__init__.py +3 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/bytedance.py +16 -0
- comfy_ui_api_workflow/_impl/nodes/partner/video/pixverse.py +27 -0
- comfy_ui_api_workflow/_impl/nodes/text.py +506 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/__init__.py +563 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/mesh.py +183 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/splat.py +262 -0
- comfy_ui_api_workflow/_impl/nodes/three_d/texturing.py +254 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/__init__.py +404 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/logic.py +44 -0
- comfy_ui_api_workflow/_impl/nodes/utilities/primitive.py +107 -0
- comfy_ui_api_workflow/_impl/nodes/validation.py +126 -0
- comfy_ui_api_workflow/_impl/nodes/video/__init__.py +440 -0
- comfy_ui_api_workflow/_impl/nodes/video/batch.py +45 -0
- comfy_ui_api_workflow/_impl/nodes/video/preprocessors.py +24 -0
- comfy_ui_api_workflow/_impl/nodes/video/transform.py +52 -0
- comfy_ui_api_workflow/_impl/pipeline.py +67 -0
- comfy_ui_api_workflow/_impl/workflow.py +539 -0
- comfy_ui_api_workflow-0.0.1.dist-info/METADATA +123 -0
- comfy_ui_api_workflow-0.0.1.dist-info/RECORD +176 -0
- comfy_ui_api_workflow-0.0.1.dist-info/WHEEL +4 -0
- comfy_ui_api_workflow-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.0.1"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from ._impl import errors, nodes, exceptions, enums
|
|
2
|
+
from ._impl.workflow import Workflow
|
|
3
|
+
from ._impl.pipeline import Pipeline
|
|
4
|
+
from ._impl.lazy import public_modules
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
__all__ = ["errors", "nodes", "exceptions", "enums", "Workflow", "Pipeline"]
|
|
8
|
+
|
|
9
|
+
public_modules(__name__, ["errors", "nodes", "exceptions", "enums"])
|
|
File without changes
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from .errors.data_structure import CycleError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IncrementalDirectedAcyclicGraphNode:
|
|
7
|
+
def __init__(self, data: Any) -> None:
|
|
8
|
+
self.data = data
|
|
9
|
+
|
|
10
|
+
def __repr__(self) -> str:
|
|
11
|
+
return f"{type(self).__name__}({self.data!r})"
|
|
12
|
+
|
|
13
|
+
def __str__(self) -> str:
|
|
14
|
+
return repr(self.data)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IncrementalDirectedAcyclicGraph:
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self._successors: dict[
|
|
20
|
+
IncrementalDirectedAcyclicGraphNode,
|
|
21
|
+
set[IncrementalDirectedAcyclicGraphNode],
|
|
22
|
+
] = {}
|
|
23
|
+
self._predecessors: dict[
|
|
24
|
+
IncrementalDirectedAcyclicGraphNode,
|
|
25
|
+
set[IncrementalDirectedAcyclicGraphNode],
|
|
26
|
+
] = {}
|
|
27
|
+
self._order: dict[IncrementalDirectedAcyclicGraphNode, int] = {}
|
|
28
|
+
self._next = 0
|
|
29
|
+
|
|
30
|
+
def add_node(self, node: IncrementalDirectedAcyclicGraphNode) -> None:
|
|
31
|
+
if node in self._order:
|
|
32
|
+
return
|
|
33
|
+
self._successors[node] = set()
|
|
34
|
+
self._predecessors[node] = set()
|
|
35
|
+
self._order[node] = self._next
|
|
36
|
+
self._next += 1
|
|
37
|
+
|
|
38
|
+
def remove_node(self, node: IncrementalDirectedAcyclicGraphNode) -> None:
|
|
39
|
+
if node not in self._order:
|
|
40
|
+
raise KeyError(node)
|
|
41
|
+
for successor in self._successors[node]:
|
|
42
|
+
self._predecessors[successor].discard(node)
|
|
43
|
+
for predecessor in self._predecessors[node]:
|
|
44
|
+
self._successors[predecessor].discard(node)
|
|
45
|
+
del self._successors[node], self._predecessors[node], self._order[node]
|
|
46
|
+
|
|
47
|
+
def insert_between(
|
|
48
|
+
self,
|
|
49
|
+
a: IncrementalDirectedAcyclicGraphNode,
|
|
50
|
+
b: IncrementalDirectedAcyclicGraphNode,
|
|
51
|
+
node: IncrementalDirectedAcyclicGraphNode,
|
|
52
|
+
remove_original: bool = True,
|
|
53
|
+
) -> None:
|
|
54
|
+
new_nodes = [n for n in (a, b, node) if n not in self]
|
|
55
|
+
new_edge = node not in self._successors.get(a, ())
|
|
56
|
+
try:
|
|
57
|
+
self.add_node(node=node)
|
|
58
|
+
self.add_edge(x=a, y=node)
|
|
59
|
+
self.add_edge(x=node, y=b)
|
|
60
|
+
except CycleError:
|
|
61
|
+
if new_edge and a in self and node in self:
|
|
62
|
+
self.remove_edge(x=a, y=node)
|
|
63
|
+
for new_node in new_nodes:
|
|
64
|
+
if new_node in self:
|
|
65
|
+
self.remove_node(node=new_node)
|
|
66
|
+
raise
|
|
67
|
+
if remove_original:
|
|
68
|
+
self.remove_edge(x=a, y=b)
|
|
69
|
+
|
|
70
|
+
def add_edge(
|
|
71
|
+
self,
|
|
72
|
+
x: IncrementalDirectedAcyclicGraphNode,
|
|
73
|
+
y: IncrementalDirectedAcyclicGraphNode,
|
|
74
|
+
) -> None:
|
|
75
|
+
if x == y:
|
|
76
|
+
raise CycleError([x, x])
|
|
77
|
+
self.add_node(node=x)
|
|
78
|
+
self.add_node(node=y)
|
|
79
|
+
if y in self._successors[x]:
|
|
80
|
+
return
|
|
81
|
+
order_x = self._order[x]
|
|
82
|
+
order_y = self._order[y]
|
|
83
|
+
if order_x < order_y:
|
|
84
|
+
self._link(x=x, y=y)
|
|
85
|
+
return
|
|
86
|
+
forward = self._forward_search(y=y, x=x, upper_bound=order_x)
|
|
87
|
+
backward = self._backward_search(x=x, lower_bound=order_y)
|
|
88
|
+
self._reorder(backward=backward, forward=forward)
|
|
89
|
+
self._link(x=x, y=y)
|
|
90
|
+
|
|
91
|
+
def remove_edge(
|
|
92
|
+
self,
|
|
93
|
+
x: IncrementalDirectedAcyclicGraphNode,
|
|
94
|
+
y: IncrementalDirectedAcyclicGraphNode,
|
|
95
|
+
) -> None:
|
|
96
|
+
self._successors[x].discard(y)
|
|
97
|
+
self._predecessors[y].discard(x)
|
|
98
|
+
|
|
99
|
+
def topological_order(self) -> list[IncrementalDirectedAcyclicGraphNode]:
|
|
100
|
+
return sorted(self._order, key=self._order.__getitem__)
|
|
101
|
+
|
|
102
|
+
def dependencies(
|
|
103
|
+
self, node: IncrementalDirectedAcyclicGraphNode
|
|
104
|
+
) -> set[IncrementalDirectedAcyclicGraphNode]:
|
|
105
|
+
return set(self._successors[node])
|
|
106
|
+
|
|
107
|
+
def dependents(
|
|
108
|
+
self, node: IncrementalDirectedAcyclicGraphNode
|
|
109
|
+
) -> set[IncrementalDirectedAcyclicGraphNode]:
|
|
110
|
+
return set(self._predecessors[node])
|
|
111
|
+
|
|
112
|
+
def __contains__(self, node: object) -> bool:
|
|
113
|
+
return node in self._order
|
|
114
|
+
|
|
115
|
+
def __len__(self) -> int:
|
|
116
|
+
return len(self._order)
|
|
117
|
+
|
|
118
|
+
def _link(
|
|
119
|
+
self,
|
|
120
|
+
x: IncrementalDirectedAcyclicGraphNode,
|
|
121
|
+
y: IncrementalDirectedAcyclicGraphNode,
|
|
122
|
+
) -> None:
|
|
123
|
+
self._successors[x].add(y)
|
|
124
|
+
self._predecessors[y].add(x)
|
|
125
|
+
|
|
126
|
+
def _forward_search(
|
|
127
|
+
self,
|
|
128
|
+
y: IncrementalDirectedAcyclicGraphNode,
|
|
129
|
+
x: IncrementalDirectedAcyclicGraphNode,
|
|
130
|
+
upper_bound: int,
|
|
131
|
+
) -> list[IncrementalDirectedAcyclicGraphNode]:
|
|
132
|
+
parent: dict[
|
|
133
|
+
IncrementalDirectedAcyclicGraphNode,
|
|
134
|
+
IncrementalDirectedAcyclicGraphNode | None,
|
|
135
|
+
] = {y: None}
|
|
136
|
+
stack = [y]
|
|
137
|
+
while stack:
|
|
138
|
+
node = stack.pop()
|
|
139
|
+
for successor in self._successors[node]:
|
|
140
|
+
if successor == x:
|
|
141
|
+
path = [node]
|
|
142
|
+
previous = parent[node]
|
|
143
|
+
while previous is not None:
|
|
144
|
+
path.append(previous)
|
|
145
|
+
previous = parent[previous]
|
|
146
|
+
path.reverse()
|
|
147
|
+
raise CycleError([x] + path + [x])
|
|
148
|
+
if successor not in parent and self._order[successor] < upper_bound:
|
|
149
|
+
parent[successor] = node
|
|
150
|
+
stack.append(successor)
|
|
151
|
+
return list(parent)
|
|
152
|
+
|
|
153
|
+
def _backward_search(
|
|
154
|
+
self,
|
|
155
|
+
x: IncrementalDirectedAcyclicGraphNode,
|
|
156
|
+
lower_bound: int,
|
|
157
|
+
) -> list[IncrementalDirectedAcyclicGraphNode]:
|
|
158
|
+
visited = {x}
|
|
159
|
+
stack = [x]
|
|
160
|
+
while stack:
|
|
161
|
+
node = stack.pop()
|
|
162
|
+
for predecessor in self._predecessors[node]:
|
|
163
|
+
if (
|
|
164
|
+
predecessor not in visited
|
|
165
|
+
and self._order[predecessor] > lower_bound
|
|
166
|
+
):
|
|
167
|
+
visited.add(predecessor)
|
|
168
|
+
stack.append(predecessor)
|
|
169
|
+
return list(visited)
|
|
170
|
+
|
|
171
|
+
def _reorder(
|
|
172
|
+
self,
|
|
173
|
+
backward: list[IncrementalDirectedAcyclicGraphNode],
|
|
174
|
+
forward: list[IncrementalDirectedAcyclicGraphNode],
|
|
175
|
+
) -> None:
|
|
176
|
+
key = self._order.__getitem__
|
|
177
|
+
backward.sort(key=key)
|
|
178
|
+
forward.sort(key=key)
|
|
179
|
+
nodes = backward + forward
|
|
180
|
+
slots = sorted(self._order[node] for node in nodes)
|
|
181
|
+
for node, position in zip(nodes, slots):
|
|
182
|
+
self._order[node] = position
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
# Behaves like StrEnum, which only exists from Python 3.11 (the project supports 3.10).
|
|
5
|
+
class OutputTypes(str, Enum):
|
|
6
|
+
MODEL = "MODEL"
|
|
7
|
+
CONDITIONING = "CONDITIONING"
|
|
8
|
+
LATENT = "LATENT"
|
|
9
|
+
CLIP = "CLIP"
|
|
10
|
+
VAE = "VAE"
|
|
11
|
+
|
|
12
|
+
def __str__(self) -> str:
|
|
13
|
+
return self.value
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from ..exceptions import DataStructureException
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from ..data_structure import IncrementalDirectedAcyclicGraphNode
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CycleError(DataStructureException):
|
|
10
|
+
def __init__(self, cycle: list["IncrementalDirectedAcyclicGraphNode"]) -> None:
|
|
11
|
+
super().__init__(cycle)
|
|
12
|
+
self.cycle = cycle
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def edge(
|
|
16
|
+
self,
|
|
17
|
+
) -> tuple[
|
|
18
|
+
"IncrementalDirectedAcyclicGraphNode", "IncrementalDirectedAcyclicGraphNode"
|
|
19
|
+
]:
|
|
20
|
+
return self.cycle[0], self.cycle[1]
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def path(self) -> list["IncrementalDirectedAcyclicGraphNode"]:
|
|
24
|
+
return self.cycle[1:]
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
source, target = self.edge
|
|
28
|
+
if source == target:
|
|
29
|
+
return f"Cannot add edge {source} -> {target}: a node cannot have an edge to itself."
|
|
30
|
+
return (
|
|
31
|
+
f"Cannot add edge {source} -> {target}: a path from {target} back to {source} already exists "
|
|
32
|
+
f"({' -> '.join(map(str, self.path))}), "
|
|
33
|
+
f"so the new edge would create the cycle {' -> '.join(map(str, self.cycle))}."
|
|
34
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from ..exceptions import NodeException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def quote_names(names: list[str]) -> str:
|
|
5
|
+
quoted = [repr(name) for name in names]
|
|
6
|
+
if len(quoted) == 1:
|
|
7
|
+
return quoted[0]
|
|
8
|
+
return f"{', '.join(quoted[:-1])} and {quoted[-1]}"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def describe_dependents(dependents: list[str]) -> str:
|
|
12
|
+
if len(dependents) == 1:
|
|
13
|
+
return f"node {quote_names(dependents)} depends on it"
|
|
14
|
+
return f"nodes {quote_names(dependents)} depend on it"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidNodeParameterValue(NodeException):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NodeNotHaveOutput(NodeException):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class NodeNotFoundError(NodeException):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class NodeHasDependentsError(NodeException):
|
|
30
|
+
def __init__(self, node_id: str, dependents: list[str]) -> None:
|
|
31
|
+
super().__init__(node_id, dependents)
|
|
32
|
+
self.node_id = node_id
|
|
33
|
+
self.dependents = dependents
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return (
|
|
37
|
+
f"Cannot remove node {self.node_id!r}: "
|
|
38
|
+
f"{describe_dependents(self.dependents)}."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class InvalidNodeChangeError(NodeException):
|
|
43
|
+
def __init__(self, node_id: str, name: str, reason: str) -> None:
|
|
44
|
+
super().__init__(node_id, name, reason)
|
|
45
|
+
self.node_id = node_id
|
|
46
|
+
self.name = name
|
|
47
|
+
self.reason = reason
|
|
48
|
+
|
|
49
|
+
def __str__(self) -> str:
|
|
50
|
+
return f"Cannot change {self.name!r} of node {self.node_id!r}: {self.reason}."
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class InvalidLinkError(NodeException):
|
|
54
|
+
def __init__(self, node_id: str, name: str, reason: str) -> None:
|
|
55
|
+
super().__init__(node_id, name, reason)
|
|
56
|
+
self.node_id = node_id
|
|
57
|
+
self.name = name
|
|
58
|
+
self.reason = reason
|
|
59
|
+
|
|
60
|
+
def __str__(self) -> str:
|
|
61
|
+
return (
|
|
62
|
+
f"Cannot add node {self.node_id!r}: input {self.name!r} uses {self.reason}."
|
|
63
|
+
)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from ..exceptions import PipelineException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class PipelineError(PipelineException):
|
|
5
|
+
def __init__(self, pipeline: str, reason: str) -> None:
|
|
6
|
+
super().__init__(pipeline, reason)
|
|
7
|
+
self.pipeline = pipeline
|
|
8
|
+
self.reason = reason
|
|
9
|
+
|
|
10
|
+
def __str__(self) -> str:
|
|
11
|
+
return (
|
|
12
|
+
f"Cannot add pipeline {self.pipeline!r}: {self.reason}. "
|
|
13
|
+
"No node of the pipeline was added."
|
|
14
|
+
)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from ..exceptions import WorkflowException
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class InvalidWorkflowError(WorkflowException):
|
|
5
|
+
def __init__(self, node_id: str | None, reason: str) -> None:
|
|
6
|
+
super().__init__(node_id, reason)
|
|
7
|
+
self.node_id = node_id
|
|
8
|
+
self.reason = reason
|
|
9
|
+
|
|
10
|
+
def __str__(self) -> str:
|
|
11
|
+
if self.node_id is None:
|
|
12
|
+
return f"Cannot load the workflow: {self.reason}."
|
|
13
|
+
return f"Cannot load node {self.node_id!r}: {self.reason}."
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class ComfyUIAPIWorkflowException(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class DataStructureException(ComfyUIAPIWorkflowException):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WorkflowException(ComfyUIAPIWorkflowException):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class NodeException(ComfyUIAPIWorkflowException):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PipelineException(ComfyUIAPIWorkflowException):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class OutputException(ComfyUIAPIWorkflowException):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.abc
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
6
|
+
from importlib.machinery import ModuleSpec
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def submodules_of(package: str) -> Callable[[str], ModuleType]:
|
|
11
|
+
"""A module __getattr__ that imports the package's submodules on first use."""
|
|
12
|
+
|
|
13
|
+
def __getattr__(name: str) -> ModuleType:
|
|
14
|
+
module = f"{package}.{name}"
|
|
15
|
+
try:
|
|
16
|
+
return importlib.import_module(module)
|
|
17
|
+
except ModuleNotFoundError as error:
|
|
18
|
+
# Only a missing submodule is a missing attribute; a broken import
|
|
19
|
+
# inside an existing module still raises as it is.
|
|
20
|
+
if error.name != module:
|
|
21
|
+
raise
|
|
22
|
+
raise AttributeError(
|
|
23
|
+
f"module {package!r} has no attribute {name!r}"
|
|
24
|
+
) from None
|
|
25
|
+
|
|
26
|
+
return __getattr__
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class _PublicModules(importlib.abc.MetaPathFinder, importlib.abc.Loader):
|
|
30
|
+
"""Imports package.<name>... as the module package._impl.<name>... it stands for."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, package: str, names: Iterable[str]) -> None:
|
|
33
|
+
self.prefixes = tuple(f"{package}.{name}" for name in names)
|
|
34
|
+
self.package = package
|
|
35
|
+
|
|
36
|
+
def find_spec(
|
|
37
|
+
self, fullname: str, path: Sequence[str] | None = None, target: object = None
|
|
38
|
+
) -> ModuleSpec | None:
|
|
39
|
+
if not any(
|
|
40
|
+
fullname == prefix or fullname.startswith(f"{prefix}.")
|
|
41
|
+
for prefix in self.prefixes
|
|
42
|
+
):
|
|
43
|
+
return None
|
|
44
|
+
private = f"{self.package}._impl{fullname.removeprefix(self.package)}"
|
|
45
|
+
spec = importlib.util.find_spec(private)
|
|
46
|
+
if spec is None:
|
|
47
|
+
return None
|
|
48
|
+
return importlib.util.spec_from_loader(
|
|
49
|
+
fullname, self, is_package=spec.submodule_search_locations is not None
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def create_module(self, spec: ModuleSpec) -> ModuleType:
|
|
53
|
+
# The public name gets the module already loaded under _impl, so each
|
|
54
|
+
# class exists once whichever path imported it.
|
|
55
|
+
private = f"{self.package}._impl{spec.name.removeprefix(self.package)}"
|
|
56
|
+
return importlib.import_module(private)
|
|
57
|
+
|
|
58
|
+
def exec_module(self, module: ModuleType) -> None:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def public_modules(package: str, names: Iterable[str]) -> None:
|
|
63
|
+
"""Lets package.<name> be imported by its path, for each name the root exports."""
|
|
64
|
+
finder = _PublicModules(package, names)
|
|
65
|
+
if not any(
|
|
66
|
+
isinstance(other, _PublicModules) and other.package == package
|
|
67
|
+
for other in sys.meta_path
|
|
68
|
+
):
|
|
69
|
+
# First in line, or the path finder would load a second copy of each
|
|
70
|
+
# submodule from the __path__ of the module the public name stands for.
|
|
71
|
+
sys.meta_path.insert(0, finder)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Literal, get_args
|
|
2
|
+
|
|
3
|
+
from ..base import BaseNode
|
|
4
|
+
from ..validation import validate_bool, validate_choice, validate_range
|
|
5
|
+
|
|
6
|
+
ModelComputeDtype_Dtypes = Literal["default", "fp32", "fp16", "bf16"]
|
|
7
|
+
MODELCOMPUTEDTYPE_DTYPES = frozenset(get_args(ModelComputeDtype_Dtypes))
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelComputeDtype(BaseNode):
|
|
11
|
+
__slots__ = ("model", "dtype")
|
|
12
|
+
CATEGORY = "advanced/debug"
|
|
13
|
+
OUTPUTS = {"MODEL": 0}
|
|
14
|
+
RETURN_TYPES = ("MODEL",)
|
|
15
|
+
INPUT_SOCKET_TYPES = {"model": "MODEL", "dtype": "V1 COMBO"}
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
node_id: str,
|
|
20
|
+
model: tuple[str, int],
|
|
21
|
+
dtype: ModelComputeDtype_Dtypes | tuple[str, int],
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(node_id=node_id)
|
|
24
|
+
self.model = model
|
|
25
|
+
self.dtype = dtype
|
|
26
|
+
self.__validate()
|
|
27
|
+
|
|
28
|
+
def __validate(self) -> None:
|
|
29
|
+
validate_choice("dtype", self.dtype, MODELCOMPUTEDTYPE_DTYPES)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class EasyCache(BaseNode):
|
|
33
|
+
__slots__ = ("model", "reuse_threshold", "start_percent", "end_percent", "verbose")
|
|
34
|
+
CATEGORY = "advanced/debug"
|
|
35
|
+
OUTPUTS = {"MODEL": 0}
|
|
36
|
+
RETURN_TYPES = ("MODEL",)
|
|
37
|
+
INPUT_SOCKET_TYPES = {
|
|
38
|
+
"model": "MODEL",
|
|
39
|
+
"reuse_threshold": "FLOAT",
|
|
40
|
+
"start_percent": "FLOAT",
|
|
41
|
+
"end_percent": "FLOAT",
|
|
42
|
+
"verbose": "BOOLEAN",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
node_id: str,
|
|
48
|
+
model: tuple[str, int],
|
|
49
|
+
reuse_threshold: float | tuple[str, int] = 0.2,
|
|
50
|
+
start_percent: float | tuple[str, int] = 0.15,
|
|
51
|
+
end_percent: float | tuple[str, int] = 0.95,
|
|
52
|
+
verbose: bool | tuple[str, int] = False,
|
|
53
|
+
) -> None:
|
|
54
|
+
super().__init__(node_id=node_id)
|
|
55
|
+
self.model = model
|
|
56
|
+
self.reuse_threshold = reuse_threshold
|
|
57
|
+
self.start_percent = start_percent
|
|
58
|
+
self.end_percent = end_percent
|
|
59
|
+
self.verbose = verbose
|
|
60
|
+
self.__validate()
|
|
61
|
+
|
|
62
|
+
def __validate(self) -> None:
|
|
63
|
+
validate_range("reuse_threshold", self.reuse_threshold, 0.0, 3.0)
|
|
64
|
+
validate_range("start_percent", self.start_percent, 0.0, 1.0)
|
|
65
|
+
validate_range("end_percent", self.end_percent, 0.0, 1.0)
|
|
66
|
+
validate_bool("verbose", self.verbose)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class LazyCache(BaseNode):
|
|
70
|
+
__slots__ = ("model", "reuse_threshold", "start_percent", "end_percent", "verbose")
|
|
71
|
+
CATEGORY = "advanced/debug"
|
|
72
|
+
OUTPUTS = {"MODEL": 0}
|
|
73
|
+
RETURN_TYPES = ("MODEL",)
|
|
74
|
+
INPUT_SOCKET_TYPES = {
|
|
75
|
+
"model": "MODEL",
|
|
76
|
+
"reuse_threshold": "FLOAT",
|
|
77
|
+
"start_percent": "FLOAT",
|
|
78
|
+
"end_percent": "FLOAT",
|
|
79
|
+
"verbose": "BOOLEAN",
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
node_id: str,
|
|
85
|
+
model: tuple[str, int],
|
|
86
|
+
reuse_threshold: float | tuple[str, int] = 0.2,
|
|
87
|
+
start_percent: float | tuple[str, int] = 0.15,
|
|
88
|
+
end_percent: float | tuple[str, int] = 0.95,
|
|
89
|
+
verbose: bool | tuple[str, int] = False,
|
|
90
|
+
) -> None:
|
|
91
|
+
super().__init__(node_id=node_id)
|
|
92
|
+
self.model = model
|
|
93
|
+
self.reuse_threshold = reuse_threshold
|
|
94
|
+
self.start_percent = start_percent
|
|
95
|
+
self.end_percent = end_percent
|
|
96
|
+
self.verbose = verbose
|
|
97
|
+
self.__validate()
|
|
98
|
+
|
|
99
|
+
def __validate(self) -> None:
|
|
100
|
+
validate_range("reuse_threshold", self.reuse_threshold, 0.0, 3.0)
|
|
101
|
+
validate_range("start_percent", self.start_percent, 0.0, 1.0)
|
|
102
|
+
validate_range("end_percent", self.end_percent, 0.0, 1.0)
|
|
103
|
+
validate_bool("verbose", self.verbose)
|