A custom node's own range decides the packages it names

TripoSR's checkpoint loads with transformers 4, and ComfyUI runs 5: of a
package a node names, its range decides, and of everything else what
ComfyUI runs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-10 18:54:15 +02:00
1 parent 4b68328e3e
commit dfc2f0bf15
6 files changed
+51 -11

No files matched your search

+1 -1
View File
@@ -8626,7 +8626,7 @@
"items": {
"type": "string"
},
"description": "Requirements are the PyPI packages the node needs that ComfyUI does not have, by name and version range only. They are installed beside the replica, never into ComfyUI's environment, at the versions that environment already has of anything it shares."
"description": "Requirements are the PyPI packages the node needs, by name and version range only. They are installed beside the replica, never into ComfyUI's environment: a package named here at the range named, and every other one at the version ComfyUI already runs. One named here is first on ComfyUI's path, so its range must suit ComfyUI too."
}
}
},
+5 -4
View File
@@ -912,10 +912,11 @@ node that changed under a model is a model that changed unasked.
`[]string`
Requirements are the PyPI packages the node needs that ComfyUI does
not have, by name and version range only. They are installed beside
the replica, never into ComfyUI's environment, at the versions that
environment already has of anything it shares.
Requirements are the PyPI packages the node needs, by name and version
range only. They are installed beside the replica, never into
ComfyUI's environment: a package named here at the range named, and
every other one at the version ComfyUI already runs. One named here
is first on ComfyUI's path, so its range must suit ComfyUI too.
## Endpoint
+30 -1
View File
@@ -83,7 +83,7 @@ func installNodes(ctx context.Context, state, python string, nodes []Node, uv st
return "", fmt.Errorf("reading ComfyUI's packages: %w", err)
}
constraints := filepath.Join(state, "constraints.txt")
if err := os.WriteFile(constraints, freeze, 0o644); err != nil { //nolint:gosec // not secret
if err := os.WriteFile(constraints, withoutNamed(freeze, reqs), 0o644); err != nil { //nolint:gosec // not secret
return "", err
}
_ = os.RemoveAll(site)
@@ -136,3 +136,32 @@ func lastLines(b []byte) string {
}
return strings.Join(lines, " | ")
}
// withoutNamed is ComfyUI's packages less those a node names itself: of a
// package a node asks for by name, the node's range decides — TripoSR's
// checkpoint loads with transformers 4 and not 5 — and of everything else,
// what ComfyUI runs. A node that names a package must name a range ComfyUI
// can run with too: the copy it gets is first on ComfyUI's path.
func withoutNamed(freeze []byte, reqs []string) []byte {
named := map[string]bool{}
for _, r := range reqs {
named[packageName(r)] = true
}
var out []string
for _, line := range strings.Split(string(freeze), "\n") {
if strings.TrimSpace(line) == "" || named[packageName(line)] {
continue
}
out = append(out, line)
}
return []byte(strings.Join(out, "\n") + "\n")
}
// packageName is a requirement's package, as PyPI compares them.
func packageName(spec string) string {
s := strings.TrimSpace(spec)
if i := strings.IndexAny(s, "<>=!~;[ @"); i >= 0 {
s = s[:i]
}
return strings.NewReplacer("_", "-", ".", "-").Replace(strings.ToLower(s))
}
+9
View File
@@ -181,3 +181,12 @@ func TestANodeIsPutAtItsCommit(t *testing.T) {
t.Errorf("packages without a uv: %v", err)
}
}
func TestANodesOwnRangeDecidesThePackagesItNames(t *testing.T) {
freeze := []byte("numpy==2.3.1\ntransformers==5.19.0\nhuggingface-hub==1.33.0\nPillow==12.3.0\n")
got := string(withoutNamed(freeze, []string{"transformers>=4.50.3,<5", "Huggingface_Hub<1", "trimesh"}))
if strings.Contains(got, "transformers") || strings.Contains(got, "huggingface") ||
!strings.Contains(got, "numpy==2.3.1") || !strings.Contains(got, "Pillow==12.3.0") {
t.Errorf("constraints %q", got)
}
}
+5 -4
View File
@@ -564,10 +564,11 @@ type CustomNode struct {
// Commit is the full commit the node is used at: a branch moves, and a
// node that changed under a model is a model that changed unasked.
Commit string `json:"commit" koanf:"commit"`
// Requirements are the PyPI packages the node needs that ComfyUI does
// not have, by name and version range only. They are installed beside
// the replica, never into ComfyUI's environment, at the versions that
// environment already has of anything it shares.
// Requirements are the PyPI packages the node needs, by name and version
// range only. They are installed beside the replica, never into
// ComfyUI's environment: a package named here at the range named, and
// every other one at the version ComfyUI already runs. One named here
// is first on ComfyUI's path, so its range must suit ComfyUI too.
Requirements []string `json:"requirements,omitempty" koanf:"requirements"`
}
+1 -1
View File
@@ -93,7 +93,7 @@ var Reference = []Block{
{Package: "catalog", Name: "CustomNode", Title: "CustomNode", Doc: "", Fields: []Field{
{Key: "repo", Type: "string", Doc: "Repo is the repository, https only.", Nested: ""},
{Key: "commit", Type: "string", Doc: "Commit is the full commit the node is used at: a branch moves, and a\nnode that changed under a model is a model that changed unasked.", Nested: ""},
{Key: "requirements", Type: "[]string", Doc: "Requirements are the PyPI packages the node needs that ComfyUI does\nnot have, by name and version range only. They are installed beside\nthe replica, never into ComfyUI's environment, at the versions that\nenvironment already has of anything it shares.", Nested: ""},
{Key: "requirements", Type: "[]string", Doc: "Requirements are the PyPI packages the node needs, by name and version\nrange only. They are installed beside the replica, never into\nComfyUI's environment: a package named here at the range named, and\nevery other one at the version ComfyUI already runs. One named here\nis first on ComfyUI's path, so its range must suit ComfyUI too.", Nested: ""},
}},
{Package: "config", Name: "Endpoint", Title: "Endpoint", Doc: "", Fields: []Field{
{Key: "id", Type: "string", Doc: "", Nested: ""},