Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 6 additions & 33 deletions pkg/inference/backends/diffusers/diffusers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/docker/model-runner/pkg/diskusage"
Expand All @@ -28,7 +26,6 @@
var (
ErrNotImplemented = errors.New("not implemented")
ErrDiffusersNotFound = errors.New("diffusers package not installed")
ErrPythonNotFound = errors.New("python3 not found in PATH")
ErrNoDDUFFile = errors.New("no DDUF file found in model bundle")
)

Expand Down Expand Up @@ -91,37 +88,22 @@
return ErrNotImplemented
}

var pythonPath string

// Use custom python path if specified
if d.customPythonPath != "" {
pythonPath = d.customPythonPath
} else {
venvPython := filepath.Join(diffusersDir, "bin", "python3")
pythonPath = venvPython

if _, err := os.Stat(venvPython); err != nil {
// Fall back to system Python
systemPython, err := exec.LookPath("python3")
if err != nil {
d.status = ErrPythonNotFound.Error()
return ErrPythonNotFound
}
pythonPath = systemPython
}
pythonPath, err := backends.FindPythonPath(d.customPythonPath, diffusersDir)
if err != nil {
d.status = err.Error()
return err
}

d.pythonPath = pythonPath

// Check if diffusers is installed
if err := d.pythonCmd("-c", "import diffusers").Run(); err != nil {
if err := backends.NewPythonCmd(d.pythonPath, "-c", "import diffusers").Run(); err != nil {
d.status = "diffusers package not installed"
d.log.Warnf("diffusers package not found. Install with: uv pip install diffusers torch")
return ErrDiffusersNotFound
}

// Get version
output, err := d.pythonCmd("-c", "import diffusers; print(diffusers.__version__)").Output()
output, err := backends.NewPythonCmd(d.pythonPath, "-c", "import diffusers; print(diffusers.__version__)").Output()
if err != nil {
d.log.Warnf("could not get diffusers version: %v", err)
d.status = "running diffusers version: unknown"
Expand Down Expand Up @@ -225,13 +207,4 @@
VRAM: 6 * 1024 * 1024 * 1024, // 6GB VRAM (average estimate)
}, nil
}

Check failure on line 210 in pkg/inference/backends/diffusers/diffusers.go

View workflow job for this annotation

GitHub Actions / lint (darwin)

File is not properly formatted (gci)
// pythonCmd creates an exec.Cmd that runs python with the given arguments.
// It uses the configured pythonPath if available, otherwise falls back to "python3".
func (d *diffusers) pythonCmd(args ...string) *exec.Cmd {
pythonBinary := "python3"
if d.pythonPath != "" {
pythonBinary = d.pythonPath
}
return exec.Command(pythonBinary, args...)
}
19 changes: 4 additions & 15 deletions pkg/inference/backends/mlx/mlx.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,11 @@ func (m *mlx) Install(ctx context.Context, httpClient *http.Client) error {
return errors.New("MLX is only available on macOS ARM64")
}

var pythonPath string

// Use custom python path if specified
if m.customPythonPath != "" {
pythonPath = m.customPythonPath
} else {
// Check if Python 3 is available
var err error
pythonPath, err = exec.LookPath("python3")
if err != nil {
m.status = ErrStatusNotFound.Error()
return ErrStatusNotFound
}
pythonPath, err := backends.FindPythonPath(m.customPythonPath, "")
if err != nil {
m.status = ErrStatusNotFound.Error()
return ErrStatusNotFound
}

// Store the python path for later use
m.pythonPath = pythonPath
Comment on lines +83 to 88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Preserve the underlying Python resolution error instead of always mapping to ErrStatusNotFound

Now that FindPythonPath can return a more specific ErrPythonNotFound, consider either propagating err directly (as in the diffusers/sglang backends) or setting m.status = err.Error() instead of ErrStatusNotFound.Error(). This would preserve the more detailed failure information (e.g., “MLX not available” vs. “python3 not found”), improving diagnosis of misconfigured environments.

Suggested change
pythonPath, err := backends.FindPythonPath(m.customPythonPath, "")
if err != nil {
m.status = ErrStatusNotFound.Error()
return ErrStatusNotFound
}
// Store the python path for later use
m.pythonPath = pythonPath
pythonPath, err := backends.FindPythonPath(m.customPythonPath, "")
if err != nil {
m.status = err.Error()
return err
}
m.pythonPath = pythonPath


// Check if mlx-lm package is installed by attempting to import it
Expand Down
42 changes: 42 additions & 0 deletions pkg/inference/backends/python.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package backends

import (
"errors"
"os"
"os/exec"
"path/filepath"
)
Comment on lines +3 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add the context package to the imports to support context-aware command creation helpers.

Suggested change
import (
"errors"
"os"
"os/exec"
"path/filepath"
)
import (
"context"
"errors"
"os"
"os/exec"
"path/filepath"
)


// ErrPythonNotFound is returned when python3 cannot be found.
var ErrPythonNotFound = errors.New("python3 not found in PATH")

// FindPythonPath returns the path to a python3 binary.
// If customPath is non-empty, it is returned directly.
// If envDir is non-empty and envDir/bin/python3 exists, that is returned.
// Otherwise, python3 is looked up in PATH. Returns ErrPythonNotFound if none found.
func FindPythonPath(customPath, envDir string) (string, error) {
if customPath != "" {
return customPath, nil
}
if envDir != "" {
venvPython := filepath.Join(envDir, "bin", "python3")
if _, err := os.Stat(venvPython); err == nil {
return venvPython, nil
}
}
systemPython, err := exec.LookPath("python3")
if err != nil {
return "", ErrPythonNotFound
}
return systemPython, nil
}

// NewPythonCmd creates an exec.Cmd that runs python3 with the given arguments.
// If pythonPath is empty, "python3" is used.
func NewPythonCmd(pythonPath string, args ...string) *exec.Cmd {
binary := "python3"
if pythonPath != "" {
binary = pythonPath
}
return exec.Command(binary, args...)
}
Comment on lines +36 to +42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Consider adding a context-aware version of this helper (e.g., NewPythonCmdContext) that uses exec.CommandContext. Since the Install methods in the backends already receive a context.Context, leveraging it for these check commands would allow for better process management and cancellation support during the installation phase.

39 changes: 6 additions & 33 deletions pkg/inference/backends/sglang/sglang.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
"fmt"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"

"github.com/docker/model-runner/pkg/diskusage"
Expand All @@ -27,7 +25,6 @@
var (
ErrNotImplemented = errors.New("not implemented")
ErrSGLangNotFound = errors.New("sglang package not installed")
ErrPythonNotFound = errors.New("python3 not found in PATH")
)

// sglang is the SGLang-based backend implementation.
Expand Down Expand Up @@ -86,37 +83,22 @@
return ErrNotImplemented
}

var pythonPath string

// Use custom python path if specified
if s.customPythonPath != "" {
pythonPath = s.customPythonPath
} else {
venvPython := filepath.Join(sglangDir, "bin", "python3")
pythonPath = venvPython

if _, err := os.Stat(venvPython); err != nil {
// Fall back to system Python
systemPython, err := exec.LookPath("python3")
if err != nil {
s.status = ErrPythonNotFound.Error()
return ErrPythonNotFound
}
pythonPath = systemPython
}
pythonPath, err := backends.FindPythonPath(s.customPythonPath, sglangDir)
if err != nil {
s.status = err.Error()
return err
}

s.pythonPath = pythonPath

// Check if sglang is installed
if err := s.pythonCmd("-c", "import sglang").Run(); err != nil {
if err := backends.NewPythonCmd(s.pythonPath, "-c", "import sglang").Run(); err != nil {
s.status = "sglang package not installed"
s.log.Warnf("sglang package not found. Install with: uv pip install sglang")
return ErrSGLangNotFound
}

// Get version
output, err := s.pythonCmd("-c", "import sglang; print(sglang.__version__)").Output()
output, err := backends.NewPythonCmd(s.pythonPath, "-c", "import sglang; print(sglang.__version__)").Output()
if err != nil {
s.log.Warnf("could not get sglang version: %v", err)
s.status = "running sglang version: unknown"
Expand Down Expand Up @@ -203,13 +185,4 @@
VRAM: 1,
}, nil
}

Check failure on line 188 in pkg/inference/backends/sglang/sglang.go

View workflow job for this annotation

GitHub Actions / lint (darwin)

File is not properly formatted (gci)
// pythonCmd creates an exec.Cmd that runs python with the given arguments.
// It uses the configured pythonPath if available, otherwise falls back to "python3".
func (s *sglang) pythonCmd(args ...string) *exec.Cmd {
pythonBinary := "python3"
if s.pythonPath != "" {
pythonBinary = s.pythonPath
}
return exec.Command(pythonBinary, args...)
}
Loading