diff --git a/src/runtimes/python/provider.go b/src/runtimes/python/provider.go index dff2aa3..21da569 100644 --- a/src/runtimes/python/provider.go +++ b/src/runtimes/python/provider.go @@ -12,6 +12,7 @@ import ( "regexp" goruntime "runtime" "sort" + "strconv" "strings" "time" @@ -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") } @@ -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) @@ -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 diff --git a/src/runtimes/python/provider_test.go b/src/runtimes/python/provider_test.go index cda74cb..8585a54 100644 --- a/src/runtimes/python/provider_test.go +++ b/src/runtimes/python/provider_test.go @@ -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) + } + }) + } +}