Skip to content
Merged
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
25 changes: 22 additions & 3 deletions src/runtimes/python/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"regexp"
goruntime "runtime"
"sort"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -117,7 +118,9 @@ func (p *Provider) installPipIfNeeded(version string) {
pipSpinner.Start()
if err := p.installPip(version); err != nil {
pipSpinner.Warning("Failed to install pip")
ui.Info("You can install it manually by running: python -m ensurepip")
ui.Info("To install pip manually:")
ui.Info(" 1. Download: %s", p.getPipURL(version))
ui.Info(" 2. Run: python get-pip.py")
} else {
pipSpinner.Success("pip installed successfully")
}
Expand Down Expand Up @@ -306,8 +309,8 @@ func (p *Provider) installPip(version string) error {
return fmt.Errorf("failed to enable site-packages: %w", err)
}

// Step 2: Download get-pip.py
getPipURL := "https://bootstrap.pypa.io/get-pip.py"
// Step 2: Download get-pip.py (use version-specific URL for older Python)
getPipURL := p.getPipURL(version)
getPipPath := filepath.Join(installPath, "get-pip.py")
if err := download.File(getPipURL, getPipPath); err != nil {
return fmt.Errorf("failed to download get-pip.py: %w", err)
Expand All @@ -325,6 +328,22 @@ func (p *Provider) installPip(version string) error {
return nil
}

// getPipURL returns the appropriate get-pip.py URL for the given Python version.
// Older Python versions (3.8 and below) require version-specific URLs since the
// main get-pip.py no longer supports end-of-life Python versions.
func (p *Provider) getPipURL(version string) string {
parts := strings.Split(version, ".")
if len(parts) >= 2 && parts[0] == "3" {
minor, err := strconv.Atoi(parts[1])
if err == nil && minor <= 8 {
// Use version-specific URL for Python 3.8 and below
return fmt.Sprintf("https://bootstrap.pypa.io/pip/%s.%s/get-pip.py", parts[0], parts[1])
}
}
// Default URL for Python 3.9+
return "https://bootstrap.pypa.io/get-pip.py"
}

// enableSitePackages modifies the ._pth file to enable site-packages
func (p *Provider) enableSitePackages(pthFile string) error {
// Read the file
Expand Down
61 changes: 61 additions & 0 deletions src/runtimes/python/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,64 @@ func TestPythonProvider_InstallPath(t *testing.T) {
}
}
}

// TestPythonProvider_GetPipURL tests the version-specific pip URL selection
func TestPythonProvider_GetPipURL(t *testing.T) {
provider := NewProvider()

tests := []struct {
name string
version string
expectedURL string
}{
{
name: "Python 3.12 uses default URL",
version: "3.12.0",
expectedURL: "https://bootstrap.pypa.io/get-pip.py",
},
{
name: "Python 3.11 uses default URL",
version: "3.11.5",
expectedURL: "https://bootstrap.pypa.io/get-pip.py",
},
{
name: "Python 3.10 uses default URL",
version: "3.10.0",
expectedURL: "https://bootstrap.pypa.io/get-pip.py",
},
{
name: "Python 3.9 uses default URL",
version: "3.9.18",
expectedURL: "https://bootstrap.pypa.io/get-pip.py",
},
{
name: "Python 3.8 uses version-specific URL",
version: "3.8.9",
expectedURL: "https://bootstrap.pypa.io/pip/3.8/get-pip.py",
},
{
name: "Python 3.7 uses version-specific URL",
version: "3.7.12",
expectedURL: "https://bootstrap.pypa.io/pip/3.7/get-pip.py",
},
{
name: "Python 3.6 uses version-specific URL",
version: "3.6.15",
expectedURL: "https://bootstrap.pypa.io/pip/3.6/get-pip.py",
},
{
name: "Python 2.7 uses version-specific URL",
version: "2.7.18",
expectedURL: "https://bootstrap.pypa.io/get-pip.py", // 2.x uses default (not 3.x)
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
url := provider.getPipURL(tt.version)
if url != tt.expectedURL {
t.Errorf("getPipURL(%q) = %q, want %q", tt.version, url, tt.expectedURL)
}
})
}
}