ComfyNode.GET_SCHEMA() OUTPUT_IS_LIST cache may cause nested inherited ComfyNode classes get bad schema related to the initialization order
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 158
Description
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api/latest/_io.py#L2216-L2263
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/execution.py#L322-L327
## Reproduce Steps
Create `ComfyUI/custom_nodes/ComfyUI-EmptyImage/__init__.py`
```python
import torch
import comfy.model_management
from comfy_api.latest import ComfyExtension, io
class EmptyImage1(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="EmptyImage1",
category="EmptyImage",
inputs=[
io.Int.Input("width", default=512, min=1, max=16384),
io.Int.Input("height", default=512, min=1, max=16384),
io.Int.Input("batch_size", default=1, min=1, max=4096),
io.Int.Input("color", default=0, min=0, max=0xFFFFFF),
],
outputs=[
io.Image.Output(),
],
)
@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
dtype = comfy.model_management.intermediate_dtype()
device = comfy.model_management.intermediate_device()
r = torch.full([batch_size, height, width, 1], ((color >> 16) & 0xFF) / 0xFF, device=device, dtype=dtype)
g = torch.full([batch_size, height, width, 1], ((color >> 8) & 0xFF) / 0xFF, device=device, dtype=dtype)
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF, device=device, dtype=dtype)
return io.NodeOutput(torch.cat((r, g, b), dim=-1))
class EmptyImage2(EmptyImage1):
@classmethod
def define_schema(cls):
schema = super().define_schema()
schema.node_id = "EmptyImage2"
schema.outputs[0].is_output_list = True
return schema
@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
return io.NodeOutput([super().execute(width, height, batch_size, color)[0]])
class EmptyImageExtension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
EmptyImage1,
EmptyImage2,
]
async def comfy_entrypoint() -> EmptyImageExtension:
return EmptyImageExtension()
```
If change the init order to
```python
class EmptyImageExtension(ComfyExtension):
async def get_node_list(self) -> list[type[io.ComfyNode]]:
return [
EmptyImage2,
EmptyImage1,
]
```
it works.
## Simplified Code
```python
class A:
@classmethod
def define_schema(cls):
return {
'OUTPUT_IS_LIST': [False],
}
SCHEMA = None
@classmethod
def GET_SCHEMA(cls):
if cls.SCHEMA is None:
cls.SCHEMA = cls.define_schema()
class B(A):
@classmethod
def define_schema(cls):
schema = super().define_schema()
schema['OUTPUT_IS_LIST'] = [True]
return schema
A.GET_SCHEMA()
B.GET_SCHEMA()
print('A', A.SCHEMA)
print('B', B.SCHEMA)
```
Output:
```plain
A {'OUTPUT_IS_LIST': [False]}
B {'OUTPUT_IS_LIST': [False]}
```
If change the code to:
```python
B.GET_SCHEMA()
A.GET_SCHEMA()
print('A', A.SCHEMA)
print('B', B.SCHEMA)
```
got output:
```plain
A {'OUTPUT_IS_LIST': [False]}
B {'OUTPUT_IS_LIST': [True]}
```
## Workaround
Prevent any class inheritance
```python
class EmptyImage2(io.ComfyNode):
@classmethod
def define_schema(cls):
schema = EmptyImage1.define_schema()
schema.node_id = "EmptyImage2"
schema.outputs[0].is_output_list = True
return schema
@classmethod
def execute(cls, width, height, batch_size, color) -> io.NodeOutput:
return io.NodeOutput([EmptyImage1.execute(width, height, batch_size, color)[0]])
```
## Related nodes
(This nodes doesn't change outputs schema, so it runs normally at current time)
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_extras/nodes_lt_audio.py#L37
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api_nodes/nodes_bfl.py#L277
https://github.com/Comfy-Org/ComfyUI/blob/3216c62e9962c3babd28a4dfea6e5aef50b8fe16/comfy_api_nodes/nodes_bfl.py#L818
Contributor guide
Research direction
Start with comfy_api/latest/_io.py lines 2216-2263 and execution.py lines 322-327, then run the supplied nested ComfyNode reproduction with both registration orders. Done means inherited classes receive the correct independent schema regardless of initialization order, including the OUTPUT_IS_LIST setting, without breaking the related nodes mentioned.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend-api-design
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100